ghostrail 0.15.2 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -9
- package/dist/agent/claude-code.d.ts.map +1 -1
- package/dist/agent/claude-code.js +4 -1
- package/dist/agent/claude-code.js.map +1 -1
- package/dist/board/page.d.ts +1 -1
- package/dist/board/page.d.ts.map +1 -1
- package/dist/board/page.js +2 -1
- package/dist/board/page.js.map +1 -1
- package/dist/board/server.d.ts +1 -1
- package/dist/board/server.d.ts.map +1 -1
- package/dist/board/server.js +7 -1
- package/dist/board/server.js.map +1 -1
- package/dist/board/stats.d.ts +1 -1
- package/dist/board/stats.d.ts.map +1 -1
- package/dist/board/stats.js +2 -1
- package/dist/board/stats.js.map +1 -1
- package/dist/claude-profile/index.d.ts +1 -1
- package/dist/claude-profile/index.d.ts.map +1 -1
- package/dist/claude-profile/index.js +1 -1
- package/dist/claude-profile/index.js.map +1 -1
- package/dist/claude-profile/setup.d.ts +7 -0
- package/dist/claude-profile/setup.d.ts.map +1 -1
- package/dist/claude-profile/setup.js +7 -0
- package/dist/claude-profile/setup.js.map +1 -1
- package/dist/cli/commands.d.ts +6 -0
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +98 -2
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +3 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/help.d.ts.map +1 -1
- package/dist/commands/help.js +11 -0
- package/dist/commands/help.js.map +1 -1
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +18 -2
- package/dist/config/load.js.map +1 -1
- package/dist/config/schema.d.ts +8 -2
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/dashboard/page.d.ts +7 -1
- package/dist/dashboard/page.d.ts.map +1 -1
- package/dist/dashboard/page.js +204 -39
- package/dist/dashboard/page.js.map +1 -1
- package/dist/dashboard/routes.d.ts +32 -0
- package/dist/dashboard/routes.d.ts.map +1 -1
- package/dist/dashboard/routes.js +45 -0
- package/dist/dashboard/routes.js.map +1 -1
- package/dist/dashboard/server.d.ts +35 -9
- package/dist/dashboard/server.d.ts.map +1 -1
- package/dist/dashboard/server.js +75 -11
- package/dist/dashboard/server.js.map +1 -1
- package/dist/doctor/checks.d.ts +87 -0
- package/dist/doctor/checks.d.ts.map +1 -0
- package/dist/doctor/checks.js +414 -0
- package/dist/doctor/checks.js.map +1 -0
- package/dist/doctor/index.d.ts +6 -0
- package/dist/doctor/index.d.ts.map +1 -0
- package/dist/doctor/index.js +5 -0
- package/dist/doctor/index.js.map +1 -0
- package/dist/doctor/probes.d.ts +42 -0
- package/dist/doctor/probes.d.ts.map +1 -0
- package/dist/doctor/probes.js +136 -0
- package/dist/doctor/probes.js.map +1 -0
- package/dist/doctor/report.d.ts +13 -0
- package/dist/doctor/report.d.ts.map +1 -0
- package/dist/doctor/report.js +32 -0
- package/dist/doctor/report.js.map +1 -0
- package/dist/doctor/run.d.ts +48 -0
- package/dist/doctor/run.d.ts.map +1 -0
- package/dist/doctor/run.js +109 -0
- package/dist/doctor/run.js.map +1 -0
- package/dist/doctor/types.d.ts +17 -0
- package/dist/doctor/types.d.ts.map +1 -0
- package/dist/doctor/types.js +6 -0
- package/dist/doctor/types.js.map +1 -0
- package/dist/loop/budget.d.ts +11 -2
- package/dist/loop/budget.d.ts.map +1 -1
- package/dist/loop/budget.js +13 -3
- package/dist/loop/budget.js.map +1 -1
- package/dist/loop/comments.d.ts +20 -0
- package/dist/loop/comments.d.ts.map +1 -0
- package/dist/loop/comments.js +35 -0
- package/dist/loop/comments.js.map +1 -0
- package/dist/loop/engine.d.ts.map +1 -1
- package/dist/loop/engine.js +18 -0
- package/dist/loop/engine.js.map +1 -1
- package/dist/loop/index.d.ts +1 -0
- package/dist/loop/index.d.ts.map +1 -1
- package/dist/loop/index.js +1 -0
- package/dist/loop/index.js.map +1 -1
- package/dist/source/dependencies.d.ts +81 -0
- package/dist/source/dependencies.d.ts.map +1 -0
- package/dist/source/dependencies.js +218 -0
- package/dist/source/dependencies.js.map +1 -0
- package/dist/source/index.d.ts +1 -0
- package/dist/source/index.d.ts.map +1 -1
- package/dist/source/index.js +1 -0
- package/dist/source/index.js.map +1 -1
- package/dist/source/linear.d.ts +17 -1
- package/dist/source/linear.d.ts.map +1 -1
- package/dist/source/linear.js +75 -9
- package/dist/source/linear.js.map +1 -1
- package/dist/store/events.d.ts +8 -0
- package/dist/store/events.d.ts.map +1 -1
- package/dist/store/events.js +10 -0
- package/dist/store/events.js.map +1 -1
- package/dist/store/file-store.d.ts +1 -1
- package/dist/store/file-store.d.ts.map +1 -1
- package/dist/store/file-store.js +7 -0
- package/dist/store/file-store.js.map +1 -1
- package/dist/store/index.d.ts +1 -1
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +1 -1
- package/dist/store/index.js.map +1 -1
- package/dist/store/port.d.ts +15 -0
- package/dist/store/port.d.ts.map +1 -1
- package/dist/store/port.js +17 -1
- package/dist/store/port.js.map +1 -1
- package/dist/triage/engine.d.ts.map +1 -1
- package/dist/triage/engine.js +2 -0
- package/dist/triage/engine.js.map +1 -1
- package/package.json +3 -1
- package/skill/ghostrail/SKILL.md +15 -34
- package/templates/code/prompts/resolve-issue.md +7 -0
- package/templates/code/prompts/triage.md +7 -0
- package/templates/content/prompts/draft.md +7 -0
package/dist/dashboard/page.d.ts
CHANGED
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
* The checkbox is desired state and the copy has to keep saying so: a ticked
|
|
6
6
|
* box beside an inactive unit means "will start", not "running". Showing them
|
|
7
7
|
* as one thing would be a lie the operator acts on.
|
|
8
|
+
*
|
|
9
|
+
* Run detail matches the board's (TJ-1640), with two differences the dashboard
|
|
10
|
+
* forces. It renders detail straight from the listing rather than fetching a
|
|
11
|
+
* row per expanded run, because `/api/ghostrails` already carries whole rows.
|
|
12
|
+
* And every request that names a run or an artifact carries the ghostrail's
|
|
13
|
+
* `path`, since nothing here is scoped to a single repo.
|
|
8
14
|
*/
|
|
9
|
-
export declare const DASHBOARD_PAGE_HTML
|
|
15
|
+
export declare const DASHBOARD_PAGE_HTML: string;
|
|
10
16
|
//# sourceMappingURL=page.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../src/dashboard/page.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../src/dashboard/page.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,mBAAmB,QAkP/B,CAAC"}
|
package/dist/dashboard/page.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { IN_PROGRESS_STATE } from "../store/events.js";
|
|
1
2
|
/**
|
|
2
3
|
* The dashboard page. Inline, like the board's, so the package ships one file
|
|
3
4
|
* and the server needs no static asset handling.
|
|
@@ -5,13 +6,19 @@
|
|
|
5
6
|
* The checkbox is desired state and the copy has to keep saying so: a ticked
|
|
6
7
|
* box beside an inactive unit means "will start", not "running". Showing them
|
|
7
8
|
* as one thing would be a lie the operator acts on.
|
|
9
|
+
*
|
|
10
|
+
* Run detail matches the board's (TJ-1640), with two differences the dashboard
|
|
11
|
+
* forces. It renders detail straight from the listing rather than fetching a
|
|
12
|
+
* row per expanded run, because `/api/ghostrails` already carries whole rows.
|
|
13
|
+
* And every request that names a run or an artifact carries the ghostrail's
|
|
14
|
+
* `path`, since nothing here is scoped to a single repo.
|
|
8
15
|
*/
|
|
9
16
|
export const DASHBOARD_PAGE_HTML = `<!doctype html>
|
|
10
17
|
<meta charset="utf-8">
|
|
11
18
|
<title>ghostrails</title>
|
|
12
19
|
<style>
|
|
13
20
|
:root { color-scheme: light dark; }
|
|
14
|
-
body { font: 15px/1.5 ui-sans-serif, system-ui, sans-serif; margin: 2rem auto; max-width:
|
|
21
|
+
body { font: 15px/1.5 ui-sans-serif, system-ui, sans-serif; margin: 2rem auto; max-width: 72rem; padding: 0 1rem; }
|
|
15
22
|
h1 { font-size: 1.3rem; margin: 0 0 .25rem; }
|
|
16
23
|
.sub { opacity: .7; margin: 0 0 1.5rem; }
|
|
17
24
|
.gr { border: 1px solid color-mix(in oklab, currentColor 20%, transparent); border-radius: 8px; padding: .9rem 1rem; margin-bottom: .75rem; }
|
|
@@ -19,11 +26,27 @@ export const DASHBOARD_PAGE_HTML = `<!doctype html>
|
|
|
19
26
|
.name { font-weight: 600; }
|
|
20
27
|
.path { opacity: .6; font-family: ui-monospace, monospace; font-size: .85em; }
|
|
21
28
|
.pill { font-size: .75em; padding: .1rem .45rem; border-radius: 99px; border: 1px solid currentColor; opacity: .8; }
|
|
22
|
-
.runs { margin: .6rem 0 0; padding-left: 1.1rem; opacity: .8; font-size: .9em; }
|
|
23
|
-
.runs li { margin: .1rem 0; }
|
|
24
29
|
.none { opacity: .55; font-size: .9em; margin: .6rem 0 0; }
|
|
25
30
|
label { display: inline-flex; align-items: center; gap: .4rem; cursor: pointer; }
|
|
26
31
|
.err { color: #b00; }
|
|
32
|
+
table.runs { border-collapse: collapse; width: 100%; margin: .6rem 0 0; font-size: .9em; }
|
|
33
|
+
table.runs th, table.runs td { text-align: left; padding: .3rem .5rem; border-bottom: 1px solid color-mix(in oklab, currentColor 15%, transparent); }
|
|
34
|
+
table.runs th { font-weight: 600; opacity: .7; font-size: .85em; }
|
|
35
|
+
a { color: inherit; }
|
|
36
|
+
.state { font-weight: 600; }
|
|
37
|
+
.done { color: #2a9d3a; } .failed { color: #d33; } .blocked { color: #d90; }
|
|
38
|
+
.noop, .skipped { opacity: .6; } .muted { opacity: .6; }
|
|
39
|
+
.twirl {
|
|
40
|
+
background: none; border: none; cursor: pointer; font: inherit; padding: 0 .3rem;
|
|
41
|
+
color: inherit; display: inline-block; transition: transform .15s ease;
|
|
42
|
+
}
|
|
43
|
+
.twirl.open { transform: rotate(90deg); }
|
|
44
|
+
.detail-cell { padding: .75rem 1.5rem; background: color-mix(in oklab, currentColor 6%, transparent); }
|
|
45
|
+
.detail-cell > div { margin-bottom: .5rem; }
|
|
46
|
+
.detail-cell pre { white-space: pre-wrap; word-break: break-word; margin: .25rem 0 0; font: inherit; }
|
|
47
|
+
.detail-cell ul { margin: .25rem 0 0; padding-left: 1.2rem; }
|
|
48
|
+
.subtable { border-collapse: collapse; margin-top: .25rem; }
|
|
49
|
+
.subtable td { padding: .15rem .5rem .15rem 0; border-bottom: none; }
|
|
27
50
|
</style>
|
|
28
51
|
<h1>ghostrails</h1>
|
|
29
52
|
<p class="sub">The checkbox is what <code>ghostrail start</code> should bring up. The pill beside it is what is running right now.</p>
|
|
@@ -31,58 +54,200 @@ export const DASHBOARD_PAGE_HTML = `<!doctype html>
|
|
|
31
54
|
<script>
|
|
32
55
|
const tok = new URLSearchParams(location.search).get("token");
|
|
33
56
|
const api = (p) => p + (tok ? (p.includes("?") ? "&" : "?") + "token=" + encodeURIComponent(tok) : "");
|
|
57
|
+
const esc = (s) => String(s ?? "").replace(/[&<>"']/g, (c) =>
|
|
58
|
+
({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
59
|
+
const enc = encodeURIComponent;
|
|
60
|
+
|
|
61
|
+
// Expanded rows and fetched PR status outlive a re-render, so the 10s poll does
|
|
62
|
+
// not collapse what the operator opened. Keyed on ghostrail path plus run id,
|
|
63
|
+
// which is stable across renders; DOM ids are positional and are not.
|
|
64
|
+
const expanded = new Set();
|
|
65
|
+
const prStatus = new Map();
|
|
66
|
+
const rowKey = (path, runId) => JSON.stringify([path, runId]);
|
|
67
|
+
let latest = [];
|
|
68
|
+
|
|
69
|
+
function fmtWhen(ts) {
|
|
70
|
+
return ts ? new Date(ts).toLocaleString() : "";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function gateStepsHtml(gate) {
|
|
74
|
+
if (!gate || gate.length === 0) return "<div class=muted>no gate steps</div>";
|
|
75
|
+
const rows = gate.map((s) =>
|
|
76
|
+
"<tr><td>" + esc(s.name) + "</td><td class='" + (s.exitCode === 0 ? "done" : "failed") + "'>" +
|
|
77
|
+
(s.exitCode === 0 ? "passed" : "failed (exit " + esc(s.exitCode) + ")") + "</td><td class=muted>" +
|
|
78
|
+
esc(s.durationMs) + "ms</td></tr>"
|
|
79
|
+
).join("");
|
|
80
|
+
return "<table class=subtable>" + rows + "</table>";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function artifactsHtml(path, artifacts) {
|
|
84
|
+
if (!artifacts || artifacts.length === 0) return "";
|
|
85
|
+
const items = artifacts.map((a) => {
|
|
86
|
+
const href = api("/api/artifact?path=" + enc(path) + "&hash=" + enc(a.hash));
|
|
87
|
+
return "<li><a href='" + esc(href) + "'>" + esc(a.name) + "</a></li>";
|
|
88
|
+
}).join("");
|
|
89
|
+
return "<div><b>Artifacts</b><ul>" + items + "</ul></div>";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function checkLineHtml(c) {
|
|
93
|
+
const label = c.conclusion || c.status;
|
|
94
|
+
const cls = c.conclusion === "success" ? "done" : c.conclusion ? "failed" : "muted";
|
|
95
|
+
const name = c.url ? "<a href='" + esc(c.url) + "'>" + esc(c.name) + "</a>" : esc(c.name);
|
|
96
|
+
return "<li>" + name + ": <span class='" + cls + "'>" + esc(label) + "</span></li>";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function prStatusHtml(status) {
|
|
100
|
+
if (!status) return "<div class=muted>loading CI status…</div>";
|
|
101
|
+
if (!status.available) {
|
|
102
|
+
return "<div class=muted>PR status unknown" + (status.error ? ": " + esc(status.error) : "") + "</div>";
|
|
103
|
+
}
|
|
104
|
+
const merged = status.state === "merged";
|
|
105
|
+
const parts = [
|
|
106
|
+
"<div><b>PR state:</b> " + esc(status.state) +
|
|
107
|
+
(merged && status.mergedAt ? " at " + esc(new Date(status.mergedAt).toLocaleString()) : "") + "</div>",
|
|
108
|
+
];
|
|
109
|
+
if (status.checks && status.checks.length > 0) {
|
|
110
|
+
parts.push("<div><b>CI checks</b><ul>" + status.checks.map(checkLineHtml).join("") + "</ul></div>");
|
|
111
|
+
} else {
|
|
112
|
+
parts.push("<div class=muted>no CI checks reported</div>");
|
|
113
|
+
}
|
|
114
|
+
return parts.join("");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function detailHtml(path, r) {
|
|
118
|
+
const parts = [];
|
|
119
|
+
parts.push("<div><b>Item</b> " + esc(r.itemId || "") + (r.kind ? " (" + esc(r.kind) + ")" : "") + "</div>");
|
|
120
|
+
if (r.branch) parts.push("<div><b>Branch</b> " + esc(r.branch) + "</div>");
|
|
121
|
+
if (r.backend) parts.push("<div><b>Backend</b> " + esc(r.backend) + "</div>");
|
|
122
|
+
if (r.costUsd !== undefined) {
|
|
123
|
+
parts.push("<div><b>Cost</b> $" + esc(Number(r.costUsd).toFixed(4)) + "</div>");
|
|
124
|
+
}
|
|
125
|
+
if (r.tokensIn !== undefined || r.tokensOut !== undefined) {
|
|
126
|
+
parts.push("<div><b>Tokens</b> in " + esc(r.tokensIn ?? 0) + " / out " + esc(r.tokensOut ?? 0) + "</div>");
|
|
127
|
+
}
|
|
128
|
+
parts.push("<div><b>Gate</b>" + gateStepsHtml(r.gate) + "</div>");
|
|
129
|
+
if (r.error) parts.push("<div><b>Error</b><pre class=failed>" + esc(r.error) + "</pre></div>");
|
|
130
|
+
if (r.summary) parts.push("<div><b>Comment</b><pre>" + esc(r.summary) + "</pre></div>");
|
|
131
|
+
if (r.prUrl) {
|
|
132
|
+
parts.push("<div><b>PR</b> <a href='" + esc(r.prUrl) + "'>" + esc(r.prUrl) + "</a></div>");
|
|
133
|
+
}
|
|
134
|
+
parts.push(artifactsHtml(path, r.artifacts));
|
|
135
|
+
if (r.prNumber !== undefined) parts.push(prStatusHtml(prStatus.get(rowKey(path, r.runId))));
|
|
136
|
+
parts.push(
|
|
137
|
+
"<div class=muted>started " + esc(fmtWhen(r.startedAt)) +
|
|
138
|
+
", finished " + esc(fmtWhen(r.finishedAt)) + "</div>",
|
|
139
|
+
);
|
|
140
|
+
return parts.join("");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function runsHtml(g, gi) {
|
|
144
|
+
if (!g.runs.length) {
|
|
145
|
+
return "<p class='none'>" + (g.health === "ok" ? "no runs yet" : esc(g.health)) + "</p>";
|
|
146
|
+
}
|
|
147
|
+
const body = g.runs.map((r, ri) => {
|
|
148
|
+
const open = expanded.has(rowKey(g.path, r.runId));
|
|
149
|
+
const gate = (r.gate || []).map((s) => (s.exitCode === 0 ? "✓" : "✗")).join("");
|
|
150
|
+
const pr = r.prUrl ? "<a href='" + esc(r.prUrl) + "'>#" + esc(r.prNumber) + "</a>" : "";
|
|
151
|
+
const state = esc(r.state || ${JSON.stringify(IN_PROGRESS_STATE)});
|
|
152
|
+
const main = "<tr>" +
|
|
153
|
+
"<td><button class='twirl" + (open ? " open" : "") + "' id='t-" + gi + "-" + ri + "'" +
|
|
154
|
+
" aria-expanded='" + open + "' data-path='" + esc(g.path) + "' data-run='" + esc(r.runId) + "'" +
|
|
155
|
+
" title='show detail'>▶</button></td>" +
|
|
156
|
+
"<td class=muted>" + esc(r.runId.slice(0, 8)) + "</td>" +
|
|
157
|
+
"<td>" + esc(r.title || r.itemId || "") + "</td>" +
|
|
158
|
+
"<td class='state " + state + "'>" + state + "</td>" +
|
|
159
|
+
"<td>" + esc(r.backend || "") + "</td>" +
|
|
160
|
+
"<td>" + gate + "</td>" +
|
|
161
|
+
"<td>" + pr + "</td>" +
|
|
162
|
+
"<td class=muted>" + esc(fmtWhen(r.startedAt)) + "</td></tr>";
|
|
163
|
+
if (!open) return main;
|
|
164
|
+
return main + "<tr><td colspan=8 class=detail-cell>" + detailHtml(g.path, r) + "</td></tr>";
|
|
165
|
+
}).join("");
|
|
166
|
+
return "<table class=runs><thead><tr><th></th><th>Run</th><th>Item</th><th>State</th>" +
|
|
167
|
+
"<th>Backend</th><th>Gate</th><th>PR</th><th>When</th></tr></thead><tbody>" +
|
|
168
|
+
body + "</tbody></table>";
|
|
169
|
+
}
|
|
34
170
|
|
|
35
171
|
function render(ghostrails) {
|
|
172
|
+
latest = ghostrails;
|
|
36
173
|
const list = document.getElementById("list");
|
|
37
174
|
if (!ghostrails.length) {
|
|
38
175
|
list.innerHTML = '<p class="none">No ghostrails registered. <code>ghostrail init <template></code> registers one.</p>';
|
|
39
176
|
return;
|
|
40
177
|
}
|
|
41
|
-
list.innerHTML =
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
for (const box of list.querySelectorAll("input[type=checkbox]")) {
|
|
60
|
-
box.addEventListener("change", async (e) => {
|
|
61
|
-
const t = e.target;
|
|
62
|
-
try {
|
|
63
|
-
const res = await fetch(api("/api/watch"), {
|
|
64
|
-
method: "POST",
|
|
65
|
-
headers: { "content-type": "application/json" },
|
|
66
|
-
body: JSON.stringify({ path: t.dataset.path, watch: t.checked }),
|
|
67
|
-
});
|
|
68
|
-
if (!res.ok) throw new Error((await res.json()).error || res.statusText);
|
|
69
|
-
render((await res.json()).ghostrails);
|
|
70
|
-
} catch (err) {
|
|
71
|
-
t.checked = !t.checked;
|
|
72
|
-
alert("could not update: " + err.message);
|
|
73
|
-
}
|
|
74
|
-
});
|
|
178
|
+
list.innerHTML = ghostrails.map((g, gi) =>
|
|
179
|
+
"<div class='gr'><div class='top'>" +
|
|
180
|
+
"<label><input type='checkbox' " + (g.watch ? "checked" : "") + " data-path='" + esc(g.path) + "'> watch</label>" +
|
|
181
|
+
"<span class='name'>" + esc(g.name) + "</span>" +
|
|
182
|
+
"<span class='pill'>" + esc(g.state) + "</span>" +
|
|
183
|
+
"<span class='path'>" + esc(g.path) + "</span>" +
|
|
184
|
+
"</div>" + runsHtml(g, gi) + "</div>"
|
|
185
|
+
).join("");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Fetch PR status for every expanded run that has a PR, then repaint. */
|
|
189
|
+
async function refreshPrStatus() {
|
|
190
|
+
const wanted = [];
|
|
191
|
+
for (const g of latest) {
|
|
192
|
+
for (const r of g.runs) {
|
|
193
|
+
const k = rowKey(g.path, r.runId);
|
|
194
|
+
if (r.prNumber !== undefined && expanded.has(k)) wanted.push([k, g.path, r.runId]);
|
|
195
|
+
}
|
|
75
196
|
}
|
|
197
|
+
if (!wanted.length) return;
|
|
198
|
+
await Promise.all(wanted.map(async ([k, path, runId]) => {
|
|
199
|
+
try {
|
|
200
|
+
const res = await fetch(api("/api/pr-status?path=" + enc(path) + "&run=" + enc(runId)));
|
|
201
|
+
prStatus.set(k, await res.json());
|
|
202
|
+
} catch {
|
|
203
|
+
// Leave whatever was last known. A dropped poll is not new information.
|
|
204
|
+
}
|
|
205
|
+
}));
|
|
206
|
+
render(latest);
|
|
76
207
|
}
|
|
77
208
|
|
|
78
|
-
|
|
79
|
-
|
|
209
|
+
function toggle(path, runId, buttonId) {
|
|
210
|
+
const k = rowKey(path, runId);
|
|
211
|
+
if (expanded.has(k)) expanded.delete(k);
|
|
212
|
+
else expanded.add(k);
|
|
213
|
+
render(latest);
|
|
214
|
+
// Re-render replaces the button that was clicked, so put focus back on its
|
|
215
|
+
// successor. Positional ids are fine for this: the list has not changed.
|
|
216
|
+
const btn = document.getElementById(buttonId);
|
|
217
|
+
if (btn) btn.focus();
|
|
218
|
+
if (expanded.has(k)) refreshPrStatus();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const list = document.getElementById("list");
|
|
222
|
+
|
|
223
|
+
list.addEventListener("click", (e) => {
|
|
224
|
+
const btn = e.target.closest(".twirl");
|
|
225
|
+
if (btn) toggle(btn.dataset.path, btn.dataset.run, btn.id);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
list.addEventListener("change", async (e) => {
|
|
229
|
+
const t = e.target;
|
|
230
|
+
if (!t.matches("input[type=checkbox]")) return;
|
|
231
|
+
try {
|
|
232
|
+
const res = await fetch(api("/api/watch"), {
|
|
233
|
+
method: "POST",
|
|
234
|
+
headers: { "content-type": "application/json" },
|
|
235
|
+
body: JSON.stringify({ path: t.dataset.path, watch: t.checked }),
|
|
236
|
+
});
|
|
237
|
+
if (!res.ok) throw new Error((await res.json()).error || res.statusText);
|
|
238
|
+
render((await res.json()).ghostrails);
|
|
239
|
+
} catch (err) {
|
|
240
|
+
t.checked = !t.checked;
|
|
241
|
+
alert("could not update: " + err.message);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
80
244
|
|
|
81
245
|
async function load() {
|
|
82
246
|
try {
|
|
83
247
|
const res = await fetch(api("/api/ghostrails"));
|
|
84
248
|
if (!res.ok) throw new Error((await res.json()).error || res.statusText);
|
|
85
249
|
render((await res.json()).ghostrails);
|
|
250
|
+
await refreshPrStatus();
|
|
86
251
|
} catch (err) {
|
|
87
252
|
document.getElementById("list").innerHTML = '<p class="err">' + esc(err.message) + "</p>";
|
|
88
253
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page.js","sourceRoot":"","sources":["../../src/dashboard/page.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"page.js","sourceRoot":"","sources":["../../src/dashboard/page.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mCAuIA,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2GnE,CAAC"}
|
|
@@ -10,11 +10,43 @@ export type DashboardRoute = {
|
|
|
10
10
|
kind: "ghostrails";
|
|
11
11
|
} | {
|
|
12
12
|
kind: "watch";
|
|
13
|
+
} | {
|
|
14
|
+
kind: "prStatus";
|
|
15
|
+
} | {
|
|
16
|
+
kind: "artifact";
|
|
13
17
|
} | {
|
|
14
18
|
kind: "notfound";
|
|
15
19
|
};
|
|
16
20
|
/** Map a request pathname to a route. Total; never throws. */
|
|
17
21
|
export declare function parseDashboardRoute(pathname: string): DashboardRoute;
|
|
22
|
+
/**
|
|
23
|
+
* The ghostrail a detail request is about, plus the run or artifact within it.
|
|
24
|
+
*
|
|
25
|
+
* The selector is `path`, matching the `watch` route and the deps map, and it
|
|
26
|
+
* travels as a query parameter because a ghostrail's identity does not survive
|
|
27
|
+
* a path segment: a repo identity holds slashes (`github.com/owner/name`) and
|
|
28
|
+
* so does the absolute path an entry falls back to. Percent-encoding one into a
|
|
29
|
+
* segment is exactly the shape that produced TJ-1641.
|
|
30
|
+
*
|
|
31
|
+
* Undefined for anything missing or empty, which the routes answer with 400.
|
|
32
|
+
* Note that `URLSearchParams.get` returns the FIRST value for a repeated key,
|
|
33
|
+
* so a duplicated `path` cannot smuggle a second one past a caller that checked
|
|
34
|
+
* the first.
|
|
35
|
+
*/
|
|
36
|
+
export declare function parseDetailQuery(params: URLSearchParams, key: "run" | "hash"): {
|
|
37
|
+
path: string;
|
|
38
|
+
value: string;
|
|
39
|
+
} | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* The artifact hash a request names, or undefined when it is not one.
|
|
42
|
+
*
|
|
43
|
+
* Separate from "was a hash supplied at all" because the two answers differ: a
|
|
44
|
+
* missing parameter is the caller's mistake and earns a 400, while a
|
|
45
|
+
* well-formed request for a hash that could never exist is simply a 404. The
|
|
46
|
+
* check itself is TJ-1641's, applied here so no non-digest ever reaches a store
|
|
47
|
+
* that would join it onto a directory.
|
|
48
|
+
*/
|
|
49
|
+
export declare function parseArtifactHash(params: URLSearchParams): string | undefined;
|
|
18
50
|
/**
|
|
19
51
|
* True when a request looks cross-origin, which the write route refuses.
|
|
20
52
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/dashboard/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/dashboard/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAGrD;;;;GAIG;AAEH,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,GACtB;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AAEzB,8DAA8D;AAC9D,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,CAcpE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,eAAe,EACvB,GAAG,EAAE,KAAK,GAAG,MAAM,GAClB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAM7C;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,GAAG,SAAS,CAI7E;AA2BD;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAYxE;AAOD;;;;;;GAMG;AACH;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CAY3F"}
|
package/dist/dashboard/routes.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isArtifactHash } from "../store/port.js";
|
|
1
2
|
/** Map a request pathname to a route. Total; never throws. */
|
|
2
3
|
export function parseDashboardRoute(pathname) {
|
|
3
4
|
const parts = pathname.split("/").filter((p) => p.length > 0);
|
|
@@ -12,8 +13,52 @@ export function parseDashboardRoute(pathname) {
|
|
|
12
13
|
return { kind: "ghostrails" };
|
|
13
14
|
if (parts[1] === "watch")
|
|
14
15
|
return { kind: "watch" };
|
|
16
|
+
// Which ghostrail and which run are query parameters, not path segments, so
|
|
17
|
+
// these stay two-segment routes like the others. See `parseDetailQuery`.
|
|
18
|
+
if (parts[1] === "pr-status")
|
|
19
|
+
return { kind: "prStatus" };
|
|
20
|
+
if (parts[1] === "artifact")
|
|
21
|
+
return { kind: "artifact" };
|
|
15
22
|
return { kind: "notfound" };
|
|
16
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* The ghostrail a detail request is about, plus the run or artifact within it.
|
|
26
|
+
*
|
|
27
|
+
* The selector is `path`, matching the `watch` route and the deps map, and it
|
|
28
|
+
* travels as a query parameter because a ghostrail's identity does not survive
|
|
29
|
+
* a path segment: a repo identity holds slashes (`github.com/owner/name`) and
|
|
30
|
+
* so does the absolute path an entry falls back to. Percent-encoding one into a
|
|
31
|
+
* segment is exactly the shape that produced TJ-1641.
|
|
32
|
+
*
|
|
33
|
+
* Undefined for anything missing or empty, which the routes answer with 400.
|
|
34
|
+
* Note that `URLSearchParams.get` returns the FIRST value for a repeated key,
|
|
35
|
+
* so a duplicated `path` cannot smuggle a second one past a caller that checked
|
|
36
|
+
* the first.
|
|
37
|
+
*/
|
|
38
|
+
export function parseDetailQuery(params, key) {
|
|
39
|
+
const path = params.get("path");
|
|
40
|
+
const value = params.get(key);
|
|
41
|
+
if (path === null || path.length === 0)
|
|
42
|
+
return undefined;
|
|
43
|
+
if (value === null || value.length === 0)
|
|
44
|
+
return undefined;
|
|
45
|
+
return { path, value };
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The artifact hash a request names, or undefined when it is not one.
|
|
49
|
+
*
|
|
50
|
+
* Separate from "was a hash supplied at all" because the two answers differ: a
|
|
51
|
+
* missing parameter is the caller's mistake and earns a 400, while a
|
|
52
|
+
* well-formed request for a hash that could never exist is simply a 404. The
|
|
53
|
+
* check itself is TJ-1641's, applied here so no non-digest ever reaches a store
|
|
54
|
+
* that would join it onto a directory.
|
|
55
|
+
*/
|
|
56
|
+
export function parseArtifactHash(params) {
|
|
57
|
+
const hash = params.get("hash");
|
|
58
|
+
if (hash === null || !isArtifactHash(hash))
|
|
59
|
+
return undefined;
|
|
60
|
+
return hash;
|
|
61
|
+
}
|
|
17
62
|
/**
|
|
18
63
|
* Every value for a header, case-insensitively.
|
|
19
64
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/dashboard/routes.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/dashboard/routes.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { type Server } from "node:http";
|
|
2
2
|
import type { Ghostrail } from "../global/registry.js";
|
|
3
|
+
import type { PrStatus } from "../publish/githost.js";
|
|
4
|
+
import type { RunRow } from "../store/events.js";
|
|
5
|
+
import type { RunStore } from "../store/port.js";
|
|
3
6
|
/** How a ghostrail stands on disk, mirroring what `status` reports. */
|
|
4
7
|
export type GhostrailHealth = "ok" | "no config" | "missing";
|
|
5
8
|
/** One row of the dashboard: registry entry plus everything observed about it. */
|
|
@@ -7,13 +10,15 @@ export interface GhostrailView extends Ghostrail {
|
|
|
7
10
|
health: GhostrailHealth;
|
|
8
11
|
/** Live unit state, e.g. `active`. `unknown` when no manager could be asked. */
|
|
9
12
|
state: string;
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Most recent runs from this repo's state, newest first.
|
|
15
|
+
*
|
|
16
|
+
* Whole rows. The four fields this used to copy off each row were the only
|
|
17
|
+
* thing standing between the dashboard and a detail panel, and narrowing them
|
|
18
|
+
* saved nothing: `collectViews` has already read and sliced the rows by the
|
|
19
|
+
* time it gets here (TJ-1640).
|
|
20
|
+
*/
|
|
21
|
+
runs: RunRow[];
|
|
17
22
|
}
|
|
18
23
|
/** Everything the server needs, injected so it can be driven without a machine. */
|
|
19
24
|
export interface DashboardDeps {
|
|
@@ -31,6 +36,26 @@ export interface DashboardDeps {
|
|
|
31
36
|
* was each working it out for itself.
|
|
32
37
|
*/
|
|
33
38
|
stateDir(entry: Ghostrail): Promise<string>;
|
|
39
|
+
/**
|
|
40
|
+
* One ghostrail's run store. Defaults to a file store over `stateDir`, which
|
|
41
|
+
* is what every real caller wants; injectable so the detail routes can be
|
|
42
|
+
* driven without a filesystem.
|
|
43
|
+
*
|
|
44
|
+
* It goes through `stateDir` rather than working the path out for itself,
|
|
45
|
+
* because that is the mistake TJ-1639 fixed: two places deriving one
|
|
46
|
+
* directory is how the dashboard ended up reading an empty one.
|
|
47
|
+
*/
|
|
48
|
+
store?(entry: Ghostrail): RunStore;
|
|
49
|
+
/**
|
|
50
|
+
* Live PR and CI status for a PR in one ghostrail's repo.
|
|
51
|
+
*
|
|
52
|
+
* It takes the entry and not just the number because the dashboard spans
|
|
53
|
+
* repos, and a PR number means nothing without one. Optional for the same
|
|
54
|
+
* reason the board's `gitHost` is: a dashboard on a machine with no `gh` and
|
|
55
|
+
* no credentials serves everything else unchanged and reports PR status as
|
|
56
|
+
* unavailable, rather than failing or refusing to start.
|
|
57
|
+
*/
|
|
58
|
+
prStatus?(entry: Ghostrail, prNumber: number): Promise<PrStatus>;
|
|
34
59
|
}
|
|
35
60
|
export interface DashboardOptions {
|
|
36
61
|
bind?: string;
|
|
@@ -47,8 +72,9 @@ export interface DashboardOptions {
|
|
|
47
72
|
/** Assemble the view for every registered ghostrail. */
|
|
48
73
|
export declare function collectViews(deps: DashboardDeps, entries: readonly Ghostrail[]): Promise<GhostrailView[]>;
|
|
49
74
|
/**
|
|
50
|
-
* The dashboard's HTTP server: one page, one listing,
|
|
51
|
-
*
|
|
75
|
+
* The dashboard's HTTP server: one page, one listing, two read routes for a
|
|
76
|
+
* single run's live PR status and its artifacts, and one write route that sets
|
|
77
|
+
* a ghostrail's `watch` flag and does nothing else.
|
|
52
78
|
*
|
|
53
79
|
* It never starts or stops anything. `start` and the service units act on the
|
|
54
80
|
* flag, which keeps process control off an HTTP surface: the worst a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/dashboard/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAqC,MAAM,WAAW,CAAC;AAK3E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/dashboard/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAqC,MAAM,WAAW,CAAC;AAK3E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAEvD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAEjD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAUjD,uEAAuE;AACvE,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,WAAW,GAAG,SAAS,CAAC;AAE7D,kFAAkF;AAClF,MAAM,WAAW,aAAc,SAAQ,SAAS;IAC9C,MAAM,EAAE,eAAe,CAAC;IACxB,gFAAgF;IAChF,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;;OAOG;IACH,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,mFAAmF;AACnF,MAAM,WAAW,aAAa;IAC5B,8DAA8D;IAC9D,YAAY,IAAI,OAAO,CAAC,SAAS,EAAE,GAAG,SAAS,CAAC,CAAC;IACjD,aAAa,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAC/C,0CAA0C;IAC1C,MAAM,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACpE;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C;;;;;;;;OAQG;IACH,KAAK,CAAC,CAAC,KAAK,EAAE,SAAS,GAAG,QAAQ,CAAC;IACnC;;;;;;;;OAQG;IACH,QAAQ,CAAC,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CAClE;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qFAAqF;IACrF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAqCD,wDAAwD;AACxD,wBAAsB,YAAY,CAChC,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,SAAS,SAAS,EAAE,GAC5B,OAAO,CAAC,aAAa,EAAE,CAAC,CAkB1B;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,CA0HjG;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAIjE"}
|