@urateam/dashboard 0.1.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.
Files changed (69) hide show
  1. package/dist/__tests__/layout.test.d.ts +2 -0
  2. package/dist/__tests__/layout.test.d.ts.map +1 -0
  3. package/dist/__tests__/layout.test.js +138 -0
  4. package/dist/__tests__/layout.test.js.map +1 -0
  5. package/dist/__tests__/security.repro.test.d.ts +16 -0
  6. package/dist/__tests__/security.repro.test.d.ts.map +1 -0
  7. package/dist/__tests__/security.repro.test.js +249 -0
  8. package/dist/__tests__/security.repro.test.js.map +1 -0
  9. package/dist/__tests__/server.test.d.ts +2 -0
  10. package/dist/__tests__/server.test.d.ts.map +1 -0
  11. package/dist/__tests__/server.test.js +268 -0
  12. package/dist/__tests__/server.test.js.map +1 -0
  13. package/dist/__tests__/ui-refresh.test.d.ts +2 -0
  14. package/dist/__tests__/ui-refresh.test.d.ts.map +1 -0
  15. package/dist/__tests__/ui-refresh.test.js +860 -0
  16. package/dist/__tests__/ui-refresh.test.js.map +1 -0
  17. package/dist/routes/config.d.ts +4 -0
  18. package/dist/routes/config.d.ts.map +1 -0
  19. package/dist/routes/config.js +12 -0
  20. package/dist/routes/config.js.map +1 -0
  21. package/dist/routes/coordination.d.ts +4 -0
  22. package/dist/routes/coordination.d.ts.map +1 -0
  23. package/dist/routes/coordination.js +22 -0
  24. package/dist/routes/coordination.js.map +1 -0
  25. package/dist/routes/errors.d.ts +4 -0
  26. package/dist/routes/errors.d.ts.map +1 -0
  27. package/dist/routes/errors.js +36 -0
  28. package/dist/routes/errors.js.map +1 -0
  29. package/dist/routes/runs.d.ts +4 -0
  30. package/dist/routes/runs.d.ts.map +1 -0
  31. package/dist/routes/runs.js +72 -0
  32. package/dist/routes/runs.js.map +1 -0
  33. package/dist/routes/tokens.d.ts +4 -0
  34. package/dist/routes/tokens.d.ts.map +1 -0
  35. package/dist/routes/tokens.js +47 -0
  36. package/dist/routes/tokens.js.map +1 -0
  37. package/dist/server.d.ts +19 -0
  38. package/dist/server.d.ts.map +1 -0
  39. package/dist/server.js +107 -0
  40. package/dist/server.js.map +1 -0
  41. package/dist/views/config.d.ts +3 -0
  42. package/dist/views/config.d.ts.map +1 -0
  43. package/dist/views/config.js +47 -0
  44. package/dist/views/config.js.map +1 -0
  45. package/dist/views/coordination.d.ts +3 -0
  46. package/dist/views/coordination.d.ts.map +1 -0
  47. package/dist/views/coordination.js +63 -0
  48. package/dist/views/coordination.js.map +1 -0
  49. package/dist/views/errors.d.ts +14 -0
  50. package/dist/views/errors.d.ts.map +1 -0
  51. package/dist/views/errors.js +52 -0
  52. package/dist/views/errors.js.map +1 -0
  53. package/dist/views/layout.d.ts +21 -0
  54. package/dist/views/layout.d.ts.map +1 -0
  55. package/dist/views/layout.js +82 -0
  56. package/dist/views/layout.js.map +1 -0
  57. package/dist/views/run-detail.d.ts +35 -0
  58. package/dist/views/run-detail.d.ts.map +1 -0
  59. package/dist/views/run-detail.js +129 -0
  60. package/dist/views/run-detail.js.map +1 -0
  61. package/dist/views/run-feed.d.ts +14 -0
  62. package/dist/views/run-feed.d.ts.map +1 -0
  63. package/dist/views/run-feed.js +80 -0
  64. package/dist/views/run-feed.js.map +1 -0
  65. package/dist/views/tokens.d.ts +13 -0
  66. package/dist/views/tokens.d.ts.map +1 -0
  67. package/dist/views/tokens.js +66 -0
  68. package/dist/views/tokens.js.map +1 -0
  69. package/package.json +28 -0
@@ -0,0 +1,14 @@
1
+ interface StageFailure {
2
+ stage: string;
3
+ totalRuns: number;
4
+ failedRuns: number;
5
+ failureRate: number;
6
+ }
7
+ interface ErrorPattern {
8
+ stage: string;
9
+ errorMessage: string;
10
+ count: number;
11
+ }
12
+ export declare function errorsView(stageFailures: StageFailure[], errorPatterns: ErrorPattern[]): string;
13
+ export {};
14
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/views/errors.ts"],"names":[],"mappings":"AAEA,UAAU,YAAY;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,YAAY;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,UAAU,CACxB,aAAa,EAAE,YAAY,EAAE,EAC7B,aAAa,EAAE,YAAY,EAAE,GAC5B,MAAM,CAyDR"}
@@ -0,0 +1,52 @@
1
+ import { escapeHtml } from "./layout.js";
2
+ export function errorsView(stageFailures, errorPatterns) {
3
+ const failureTable = stageFailures.length > 0
4
+ ? `<table>
5
+ <thead>
6
+ <tr><th>Stage</th><th>Total Runs</th><th>Failed</th><th>Failure Rate</th></tr>
7
+ </thead>
8
+ <tbody>
9
+ ${stageFailures
10
+ .map((f) => `<tr>
11
+ <td>${escapeHtml(f.stage)}</td>
12
+ <td>${f.totalRuns}</td>
13
+ <td>${f.failedRuns}</td>
14
+ <td>
15
+ <div class="bar-track" style="width:6rem;display:inline-block;vertical-align:middle;">
16
+ <div class="bar-fill" style="width:${Number(f.failureRate).toFixed(1)}%;background:var(--color-red);"></div>
17
+ </div>
18
+ ${Number(f.failureRate).toFixed(1)}%
19
+ </td>
20
+ </tr>`)
21
+ .join("\n")}
22
+ </tbody>
23
+ </table>`
24
+ : '<p style="color:var(--color-text-muted)">No stage data</p>';
25
+ const patternTable = errorPatterns.length > 0
26
+ ? `<table>
27
+ <thead>
28
+ <tr><th>Stage</th><th>Error Message</th><th>Count</th></tr>
29
+ </thead>
30
+ <tbody>
31
+ ${errorPatterns
32
+ .map((e) => `<tr>
33
+ <td>${escapeHtml(e.stage)}</td>
34
+ <td><code>${escapeHtml(e.errorMessage)}</code></td>
35
+ <td>${e.count}</td>
36
+ </tr>`)
37
+ .join("\n")}
38
+ </tbody>
39
+ </table>`
40
+ : '<p style="color:var(--color-text-muted)">No errors recorded</p>';
41
+ return `
42
+ <div class="card">
43
+ <h2>Failure Rates by Stage</h2>
44
+ ${failureTable}
45
+ </div>
46
+
47
+ <div class="card">
48
+ <h2>Common Error Patterns</h2>
49
+ ${patternTable}
50
+ </div>`;
51
+ }
52
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/views/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAezC,MAAM,UAAU,UAAU,CACxB,aAA6B,EAC7B,aAA6B;IAE7B,MAAM,YAAY,GAChB,aAAa,CAAC,MAAM,GAAG,CAAC;QACtB,CAAC,CAAC;;;;;UAKE,aAAa;aACZ,GAAG,CACF,CAAC,CAAC,EAAE,EAAE,CAAC;kBACD,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC;kBACnB,CAAC,CAAC,SAAS;kBACX,CAAC,CAAC,UAAU;;;qDAGuB,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;;gBAErE,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;;gBAEhC,CACL;aACA,IAAI,CAAC,IAAI,CAAC;;aAER;QACP,CAAC,CAAC,4DAA4D,CAAC;IAEnE,MAAM,YAAY,GAChB,aAAa,CAAC,MAAM,GAAG,CAAC;QACtB,CAAC,CAAC;;;;;UAKE,aAAa;aACZ,GAAG,CACF,CAAC,CAAC,EAAE,EAAE,CAAC;kBACD,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC;wBACb,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC;kBAChC,CAAC,CAAC,KAAK;gBACT,CACL;aACA,IAAI,CAAC,IAAI,CAAC;;aAER;QACP,CAAC,CAAC,iEAAiE,CAAC;IAExE,OAAO;;;MAGH,YAAY;;;;;MAKZ,YAAY;SACT,CAAC;AACV,CAAC"}
@@ -0,0 +1,21 @@
1
+ export declare function getBasePath(): string;
2
+ /**
3
+ * Render the full HTML shell (head + nav + main) around the given content.
4
+ *
5
+ * @param title - Page title shown in <h1> and <title>.
6
+ * @param content - Inner HTML for the <main> element.
7
+ * @param basePath - Root path prefix for all navigation links and static
8
+ * asset references. Must be provided without a trailing
9
+ * slash (e.g. `"/ateam"` or `""`). Every relative URL
10
+ * attribute (`href`, `src`, etc.) inside this template
11
+ * must use `${bp}/…` so links work correctly when the
12
+ * dashboard is mounted under a reverse-proxy path prefix.
13
+ * When omitted, falls back to the `DASHBOARD_BASE_PATH`
14
+ * environment variable (via `getBasePath()`), then to
15
+ * `""` (serve at root `/`). Pass an explicit value
16
+ * (sourced from `DashboardConfig.basePath`) to decouple
17
+ * link rendering from the process environment.
18
+ */
19
+ export declare function layout(title: string, content: string, basePath?: string): string;
20
+ export declare function escapeHtml(str: string): string;
21
+ //# sourceMappingURL=layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../src/views/layout.ts"],"names":[],"mappings":"AAGA,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAiBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAoChF;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO9C"}
@@ -0,0 +1,82 @@
1
+ // Base path for all dashboard links and asset references.
2
+ // Set DASHBOARD_BASE_PATH=/ateam (no trailing slash) when the dashboard is
3
+ // served under a path prefix (e.g. via a Caddy strip_prefix proxy).
4
+ export function getBasePath() {
5
+ return (process.env.DASHBOARD_BASE_PATH ?? "").replace(/\/+$/, "");
6
+ }
7
+ /**
8
+ * Normalize a basePath value so it has no trailing slash and starts with
9
+ * a leading slash (or is empty string for root).
10
+ *
11
+ * Examples:
12
+ * "" → ""
13
+ * "/" → ""
14
+ * "/ateam" → "/ateam"
15
+ * "/ateam/" → "/ateam"
16
+ */
17
+ function normalizeBasePath(basePath) {
18
+ // Strip all trailing slashes; collapse bare "/" to ""
19
+ return basePath.replace(/\/+$/, "");
20
+ }
21
+ /**
22
+ * Render the full HTML shell (head + nav + main) around the given content.
23
+ *
24
+ * @param title - Page title shown in <h1> and <title>.
25
+ * @param content - Inner HTML for the <main> element.
26
+ * @param basePath - Root path prefix for all navigation links and static
27
+ * asset references. Must be provided without a trailing
28
+ * slash (e.g. `"/ateam"` or `""`). Every relative URL
29
+ * attribute (`href`, `src`, etc.) inside this template
30
+ * must use `${bp}/…` so links work correctly when the
31
+ * dashboard is mounted under a reverse-proxy path prefix.
32
+ * When omitted, falls back to the `DASHBOARD_BASE_PATH`
33
+ * environment variable (via `getBasePath()`), then to
34
+ * `""` (serve at root `/`). Pass an explicit value
35
+ * (sourced from `DashboardConfig.basePath`) to decouple
36
+ * link rendering from the process environment.
37
+ */
38
+ export function layout(title, content, basePath) {
39
+ // Explicit parameter takes priority; fall back to env var for backward compat.
40
+ const bp = normalizeBasePath(basePath ?? getBasePath());
41
+ const cspContent = "default-src 'self'; script-src 'self' https://unpkg.com; style-src 'self'";
42
+ return `<!DOCTYPE html>
43
+ <html lang="en">
44
+ <head>
45
+ <meta charset="utf-8">
46
+ <meta name="viewport" content="width=device-width, initial-scale=1">
47
+ <meta http-equiv="Content-Security-Policy" content="${cspContent}">
48
+ <title>${escapeHtml(title)} - urateam</title>
49
+ <!-- Favicon -->
50
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>">
51
+ <!-- Inter font (falls back to system-ui in CSS) -->
52
+ <link rel="preconnect" href="https://fonts.googleapis.com">
53
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
54
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap">
55
+ <link rel="stylesheet" href="${bp}/static/style.css">
56
+ <script src="https://unpkg.com/htmx.org@2.0.0"></script>
57
+ </head>
58
+ <body>
59
+ <nav>
60
+ <a class="brand" href="${bp}/">⚡ urateam</a>
61
+ <a href="${bp}/">Runs</a>
62
+ <a href="${bp}/tokens">Tokens</a>
63
+ <a href="${bp}/errors">Errors</a>
64
+ <a href="${bp}/config">Config</a>
65
+ <a href="${bp}/coordination">Coordination</a>
66
+ </nav>
67
+ <main>
68
+ <h1>${escapeHtml(title)}</h1>
69
+ ${content}
70
+ </main>
71
+ </body>
72
+ </html>`;
73
+ }
74
+ export function escapeHtml(str) {
75
+ return str
76
+ .replace(/&/g, "&amp;")
77
+ .replace(/</g, "&lt;")
78
+ .replace(/>/g, "&gt;")
79
+ .replace(/"/g, "&quot;")
80
+ .replace(/'/g, "&#039;");
81
+ }
82
+ //# sourceMappingURL=layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.js","sourceRoot":"","sources":["../../src/views/layout.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAC1D,2EAA2E;AAC3E,oEAAoE;AACpE,MAAM,UAAU,WAAW;IACzB,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACrE,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,iBAAiB,CAAC,QAAgB;IACzC,sDAAsD;IACtD,OAAO,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACtC,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,MAAM,CAAC,KAAa,EAAE,OAAe,EAAE,QAAiB;IACtE,+EAA+E;IAC/E,MAAM,EAAE,GAAG,iBAAiB,CAAC,QAAQ,IAAI,WAAW,EAAE,CAAC,CAAC;IACxD,MAAM,UAAU,GACd,2EAA2E,CAAC;IAC9E,OAAO;;;;;wDAK+C,UAAU;WACvD,UAAU,CAAC,KAAK,CAAC;;;;;;;iCAOK,EAAE;;;;;6BAKN,EAAE;eAChB,EAAE;eACF,EAAE;eACF,EAAE;eACF,EAAE;eACF,EAAE;;;UAGP,UAAU,CAAC,KAAK,CAAC;MACrB,OAAO;;;QAGL,CAAC;AACT,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,OAAO,GAAG;SACP,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,35 @@
1
+ export interface RunInfo {
2
+ id: string;
3
+ issueId: string;
4
+ issueTitle: string;
5
+ pipelineKey: string;
6
+ repoUrl: string;
7
+ branch: string | null;
8
+ status: string;
9
+ startedAt: Date;
10
+ completedAt: Date | null;
11
+ prUrl: string | null;
12
+ totalInputTokens: number;
13
+ totalOutputTokens: number;
14
+ errorMessage: string | null;
15
+ }
16
+ export interface StageInfo {
17
+ id: string;
18
+ stage: string;
19
+ status: string;
20
+ startedAt: Date;
21
+ completedAt: Date | null;
22
+ inputTokens: number;
23
+ outputTokens: number;
24
+ turns: number;
25
+ handoffArtifact: string | null;
26
+ errorMessage: string | null;
27
+ }
28
+ export interface LogEntry {
29
+ id: string;
30
+ timestamp: Date;
31
+ type: string;
32
+ content: string;
33
+ }
34
+ export declare function runDetailView(run: RunInfo, stages: StageInfo[], logs: LogEntry[], page: number, totalLogs: number): string;
35
+ //# sourceMappingURL=run-detail.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-detail.d.ts","sourceRoot":"","sources":["../../src/views/run-detail.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,IAAI,CAAC;IAChB,WAAW,EAAE,IAAI,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,IAAI,CAAC;IAChB,WAAW,EAAE,IAAI,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,IAAI,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AA8CD,wBAAgB,aAAa,CAC3B,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,SAAS,EAAE,EACnB,IAAI,EAAE,QAAQ,EAAE,EAChB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAChB,MAAM,CA4FR"}
@@ -0,0 +1,129 @@
1
+ import { escapeHtml, getBasePath } from "./layout.js";
2
+ function statusBadge(status) {
3
+ return `<span class="badge badge-${status}">${escapeHtml(status)}</span>`;
4
+ }
5
+ function formatTime(d) {
6
+ if (!d)
7
+ return "-";
8
+ return d.toISOString().replace("T", " ").slice(0, 19) + " UTC";
9
+ }
10
+ function toRelativeTime(d) {
11
+ if (!d)
12
+ return "-";
13
+ const ms = Date.now() - new Date(d).getTime();
14
+ if (ms < 0)
15
+ return "just now";
16
+ const secs = Math.floor(ms / 1000);
17
+ if (secs < 60)
18
+ return `${secs}s ago`;
19
+ const mins = Math.floor(secs / 60);
20
+ if (mins < 60)
21
+ return `${mins}m ago`;
22
+ const hours = Math.floor(mins / 60);
23
+ if (hours < 24)
24
+ return `${hours}h ago`;
25
+ const days = Math.floor(hours / 24);
26
+ return `${days}d ago`;
27
+ }
28
+ function formatDuration(start, end) {
29
+ const endMs = end ? end.getTime() : Date.now();
30
+ const diffSec = Math.floor((endMs - start.getTime()) / 1000);
31
+ if (diffSec < 60)
32
+ return `${diffSec}s`;
33
+ const min = Math.floor(diffSec / 60);
34
+ const sec = diffSec % 60;
35
+ if (min < 60)
36
+ return `${min}m ${sec}s`;
37
+ const hr = Math.floor(min / 60);
38
+ return `${hr}h ${min % 60}m`;
39
+ }
40
+ function formatJson(raw) {
41
+ if (!raw)
42
+ return "";
43
+ try {
44
+ const obj = JSON.parse(raw);
45
+ return escapeHtml(JSON.stringify(obj, null, 2));
46
+ }
47
+ catch {
48
+ return escapeHtml(raw);
49
+ }
50
+ }
51
+ export function runDetailView(run, stages, logs, page, totalLogs) {
52
+ const basePath = getBasePath();
53
+ const logsPerPage = 50;
54
+ const totalPages = Math.max(1, Math.ceil(totalLogs / logsPerPage));
55
+ const duration = formatDuration(run.startedAt, run.completedAt);
56
+ const isInFlight = run.status === "running" || run.status === "queued";
57
+ const metaHtml = `<div class="card">
58
+ <h2>Run Details</h2>
59
+ <div style="display:flex;align-items:center;gap:0.75rem;margin-bottom:1.25rem;flex-wrap:wrap;">
60
+ ${statusBadge(run.status)}
61
+ <span class="duration-badge" title="Total run duration">⏱ ${duration}${isInFlight ? " (running)" : ""}</span>
62
+ ${run.prUrl ? `<a href="${escapeHtml(run.prUrl)}" target="_blank" rel="noopener" style="font-size:0.875rem;">↗ Pull Request</a>` : ""}
63
+ </div>
64
+ <dl class="meta">
65
+ <div><dt>Issue</dt><dd>${escapeHtml(run.issueId)}: ${escapeHtml(run.issueTitle)}</dd></div>
66
+ <div><dt>Pipeline</dt><dd>${escapeHtml(run.pipelineKey)}</dd></div>
67
+ <div><dt>Repo</dt><dd>${escapeHtml(run.repoUrl)}</dd></div>
68
+ <div><dt>Branch</dt><dd>${escapeHtml(run.branch || "-")}</dd></div>
69
+ <div><dt>Started</dt><dd title="${escapeHtml(formatTime(run.startedAt))}">${toRelativeTime(run.startedAt)}</dd></div>
70
+ <div><dt>Completed</dt><dd title="${run.completedAt ? escapeHtml(formatTime(run.completedAt)) : ""}">${toRelativeTime(run.completedAt)}</dd></div>
71
+ <div><dt>Tokens (total)</dt><dd>${(run.totalInputTokens + run.totalOutputTokens).toLocaleString()}</dd></div>
72
+ <div><dt>Tokens in / out</dt><dd>${run.totalInputTokens.toLocaleString()} / ${run.totalOutputTokens.toLocaleString()}</dd></div>
73
+ ${run.errorMessage ? `<div style="grid-column:1/-1"><dt>Error</dt><dd style="color:var(--color-red)">${escapeHtml(run.errorMessage)}</dd></div>` : ""}
74
+ </dl>
75
+ </div>`;
76
+ const stageHtml = `<div class="card">
77
+ <h2>Stage Timeline</h2>
78
+ <div class="timeline">
79
+ ${stages
80
+ .map((s) => `<div class="timeline-item ${s.status}">
81
+ <div style="display:flex;align-items:center;gap:0.5rem;flex-wrap:wrap;">
82
+ <strong>${escapeHtml(s.stage)}</strong>
83
+ ${statusBadge(s.status)}
84
+ </div>
85
+ <div class="timeline-meta">
86
+ <span>⏱ ${formatDuration(s.startedAt, s.completedAt)}</span>
87
+ <span>🔤 ${(s.inputTokens + s.outputTokens).toLocaleString()} tokens</span>
88
+ <span>↩ ${s.turns} turn${s.turns !== 1 ? "s" : ""}</span>
89
+ </div>
90
+ ${s.errorMessage ? `<div style="color:var(--color-red);font-size:0.8125rem;margin-top:0.5rem;padding:0.375rem 0.5rem;background:color-mix(in srgb,var(--color-red) 8%,transparent);border-radius:4px;">${escapeHtml(s.errorMessage)}</div>` : ""}
91
+ ${s.handoffArtifact
92
+ ? `<details style="margin-top:0.5rem;">
93
+ <summary>Handoff Artifact</summary>
94
+ <pre><code>${formatJson(s.handoffArtifact)}</code></pre>
95
+ </details>`
96
+ : ""}
97
+ </div>`)
98
+ .join("\n")}
99
+ </div>
100
+ </div>`;
101
+ const logEntries = logs.length > 0
102
+ ? logs
103
+ .map((l) => `<div class="log-entry"><span class="log-time" title="${escapeHtml(formatTime(l.timestamp))}">${toRelativeTime(l.timestamp)}</span><span class="log-type log-type-${l.type}">[${escapeHtml(l.type)}]</span>${escapeHtml(l.content)}</div>`)
104
+ .join("\n")
105
+ : '<p style="color:var(--color-text-muted);padding:1rem;">No logs recorded</p>';
106
+ const pagination = totalPages > 1
107
+ ? `<div class="pagination">
108
+ ${page > 1 ? `<a href="${basePath}/runs/${encodeURIComponent(run.id)}?page=${page - 1}">← Prev</a>` : ""}
109
+ ${Array.from({ length: totalPages }, (_, i) => i + 1)
110
+ .slice(Math.max(0, page - 3), page + 2)
111
+ .map((p) => p === page
112
+ ? `<span>${p}</span>`
113
+ : `<a href="${basePath}/runs/${encodeURIComponent(run.id)}?page=${p}">${p}</a>`)
114
+ .join("")}
115
+ ${page < totalPages ? `<a href="${basePath}/runs/${encodeURIComponent(run.id)}?page=${page + 1}">Next →</a>` : ""}
116
+ </div>`
117
+ : "";
118
+ const logHtml = `<div class="card">
119
+ <details class="log-section" open>
120
+ <summary>Agent Logs <span style="font-weight:400;font-size:0.8125rem;color:var(--color-text-muted);">(${totalLogs} total)</span></summary>
121
+ <div class="log-entries">
122
+ ${logEntries}
123
+ </div>
124
+ ${pagination}
125
+ </details>
126
+ </div>`;
127
+ return `<p style="margin-bottom:1rem;"><a href="${basePath}/">← Back to runs</a></p>${metaHtml}${stageHtml}${logHtml}`;
128
+ }
129
+ //# sourceMappingURL=run-detail.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-detail.js","sourceRoot":"","sources":["../../src/views/run-detail.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export interface RunRow {
2
+ id: string;
3
+ issueId: string;
4
+ issueTitle: string;
5
+ pipelineKey: string;
6
+ repoUrl: string;
7
+ status: string;
8
+ startedAt: Date;
9
+ completedAt: Date | null;
10
+ totalInputTokens: number;
11
+ totalOutputTokens: number;
12
+ }
13
+ export declare function runFeedView(runs: RunRow[]): string;
14
+ //# sourceMappingURL=run-feed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-feed.d.ts","sourceRoot":"","sources":["../../src/views/run-feed.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,IAAI,CAAC;IAChB,WAAW,EAAE,IAAI,GAAG,IAAI,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AA2CD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAoClD"}
@@ -0,0 +1,80 @@
1
+ import { escapeHtml, getBasePath } from "./layout.js";
2
+ function statusBadge(status) {
3
+ const cls = `badge badge-${status}`;
4
+ return `<span class="${cls}">${escapeHtml(status)}</span>`;
5
+ }
6
+ function formatDuration(start, end) {
7
+ const endMs = end ? end.getTime() : Date.now();
8
+ const diffSec = Math.floor((endMs - start.getTime()) / 1000);
9
+ if (diffSec < 60)
10
+ return `${diffSec}s`;
11
+ const min = Math.floor(diffSec / 60);
12
+ const sec = diffSec % 60;
13
+ if (min < 60)
14
+ return `${min}m ${sec}s`;
15
+ const hr = Math.floor(min / 60);
16
+ return `${hr}h ${min % 60}m`;
17
+ }
18
+ function formatTokens(input, output) {
19
+ const total = input + output;
20
+ if (total === 0)
21
+ return "-";
22
+ if (total < 1000)
23
+ return String(total);
24
+ return `${(total / 1000).toFixed(1)}k`;
25
+ }
26
+ function repoName(url) {
27
+ const parts = url.split("/");
28
+ return parts[parts.length - 1] || url;
29
+ }
30
+ function toRelativeTime(date) {
31
+ const ms = Date.now() - date.getTime();
32
+ if (ms < 0)
33
+ return "just now";
34
+ const secs = Math.floor(ms / 1000);
35
+ if (secs < 60)
36
+ return `${secs}s ago`;
37
+ const mins = Math.floor(secs / 60);
38
+ if (mins < 60)
39
+ return `${mins}m ago`;
40
+ const hours = Math.floor(mins / 60);
41
+ if (hours < 24)
42
+ return `${hours}h ago`;
43
+ const days = Math.floor(hours / 24);
44
+ return `${days}d ago`;
45
+ }
46
+ export function runFeedView(runs) {
47
+ const basePath = getBasePath();
48
+ const rows = runs
49
+ .map((r) => `<tr>
50
+ <td>${statusBadge(r.status)}</td>
51
+ <td><a href="${basePath}/runs/${encodeURIComponent(r.id)}">${escapeHtml(r.issueId)}: ${escapeHtml(r.issueTitle)}</a></td>
52
+ <td>${escapeHtml(r.pipelineKey)}</td>
53
+ <td>${escapeHtml(repoName(r.repoUrl))}</td>
54
+ <td>${formatDuration(r.startedAt, r.completedAt)}</td>
55
+ <td title="${escapeHtml(r.startedAt.toISOString())}">${toRelativeTime(r.startedAt)}</td>
56
+ <td>${formatTokens(r.totalInputTokens, r.totalOutputTokens)}</td>
57
+ </tr>`)
58
+ .join("\n");
59
+ return `<div id="run-feed" hx-get="${basePath}/" hx-trigger="every 5s" hx-swap="innerHTML" hx-target="#run-feed">
60
+ <div class="table-wrapper">
61
+ <table>
62
+ <thead>
63
+ <tr>
64
+ <th>Status</th>
65
+ <th>Issue</th>
66
+ <th>Pipeline</th>
67
+ <th>Repo</th>
68
+ <th>Duration</th>
69
+ <th>Started</th>
70
+ <th>Tokens</th>
71
+ </tr>
72
+ </thead>
73
+ <tbody>
74
+ ${rows.length > 0 ? rows : '<tr><td colspan="7" style="text-align:center;color:#999;padding:2rem;">No runs yet</td></tr>'}
75
+ </tbody>
76
+ </table>
77
+ </div>
78
+ </div>`;
79
+ }
80
+ //# sourceMappingURL=run-feed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-feed.js","sourceRoot":"","sources":["../../src/views/run-feed.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ interface DailyUsage {
2
+ date: string;
3
+ inputTokens: number;
4
+ outputTokens: number;
5
+ }
6
+ interface GroupedUsage {
7
+ key: string;
8
+ inputTokens: number;
9
+ outputTokens: number;
10
+ }
11
+ export declare function tokensView(daily: DailyUsage[], byPipeline: GroupedUsage[], byStage: GroupedUsage[]): string;
12
+ export {};
13
+ //# sourceMappingURL=tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/views/tokens.ts"],"names":[],"mappings":"AAEA,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,UAAU,YAAY;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAgCD,wBAAgB,UAAU,CACxB,KAAK,EAAE,UAAU,EAAE,EACnB,UAAU,EAAE,YAAY,EAAE,EAC1B,OAAO,EAAE,YAAY,EAAE,GACtB,MAAM,CAmDR"}
@@ -0,0 +1,66 @@
1
+ import { escapeHtml } from "./layout.js";
2
+ function formatTokenCount(n) {
3
+ if (n < 1000)
4
+ return String(n);
5
+ if (n < 1_000_000)
6
+ return `${(n / 1000).toFixed(1)}k`;
7
+ return `${(n / 1_000_000).toFixed(2)}M`;
8
+ }
9
+ function barChart(items, maxVal) {
10
+ if (maxVal === 0)
11
+ maxVal = 1;
12
+ return `<div class="bar-chart">
13
+ ${items
14
+ .map((item) => `<div class="bar-row">
15
+ <span class="bar-label">${escapeHtml(item.label)}</span>
16
+ <div class="bar-track">
17
+ <div class="bar-fill bar-fill-input" style="width:${((item.input / maxVal) * 100).toFixed(1)}%"></div>
18
+ </div>
19
+ <div class="bar-track">
20
+ <div class="bar-fill bar-fill-output" style="width:${((item.output / maxVal) * 100).toFixed(1)}%"></div>
21
+ </div>
22
+ <span class="bar-value">in: ${formatTokenCount(item.input)} / out: ${formatTokenCount(item.output)}</span>
23
+ </div>`)
24
+ .join("\n")}
25
+ </div>
26
+ <div style="font-size:0.75rem;color:var(--color-text-muted);margin-bottom:1rem;">
27
+ <span style="display:inline-block;width:0.75rem;height:0.75rem;background:var(--color-blue);border-radius:2px;vertical-align:middle;margin-right:0.25rem;"></span> Input
28
+ <span style="display:inline-block;width:0.75rem;height:0.75rem;background:#8b5cf6;border-radius:2px;vertical-align:middle;margin-left:0.75rem;margin-right:0.25rem;"></span> Output
29
+ </div>`;
30
+ }
31
+ export function tokensView(daily, byPipeline, byStage) {
32
+ const totalInput = daily.reduce((s, d) => s + d.inputTokens, 0);
33
+ const totalOutput = daily.reduce((s, d) => s + d.outputTokens, 0);
34
+ const dailyMax = Math.max(...daily.map((d) => Math.max(d.inputTokens, d.outputTokens)), 1);
35
+ const pipelineMax = Math.max(...byPipeline.map((p) => Math.max(p.inputTokens, p.outputTokens)), 1);
36
+ const stageMax = Math.max(...byStage.map((s) => Math.max(s.inputTokens, s.outputTokens)), 1);
37
+ return `
38
+ <div class="card">
39
+ <h2>Summary</h2>
40
+ <p>Total input tokens: <strong>${formatTokenCount(totalInput)}</strong> &middot;
41
+ Total output tokens: <strong>${formatTokenCount(totalOutput)}</strong> &middot;
42
+ Combined: <strong>${formatTokenCount(totalInput + totalOutput)}</strong></p>
43
+ </div>
44
+
45
+ <div class="card">
46
+ <h2>Daily Usage (last 30 days)</h2>
47
+ ${daily.length > 0
48
+ ? barChart(daily.map((d) => ({ label: d.date, input: d.inputTokens, output: d.outputTokens })), dailyMax)
49
+ : '<p style="color:var(--color-text-muted)">No data</p>'}
50
+ </div>
51
+
52
+ <div class="card">
53
+ <h2>By Pipeline</h2>
54
+ ${byPipeline.length > 0
55
+ ? barChart(byPipeline.map((p) => ({ label: p.key, input: p.inputTokens, output: p.outputTokens })), pipelineMax)
56
+ : '<p style="color:var(--color-text-muted)">No data</p>'}
57
+ </div>
58
+
59
+ <div class="card">
60
+ <h2>By Stage</h2>
61
+ ${byStage.length > 0
62
+ ? barChart(byStage.map((s) => ({ label: s.key, input: s.inputTokens, output: s.outputTokens })), stageMax)
63
+ : '<p style="color:var(--color-text-muted)">No data</p>'}
64
+ </div>`;
65
+ }
66
+ //# sourceMappingURL=tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../src/views/tokens.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@urateam/dashboard",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "dist/server.js",
6
+ "types": "dist/server.d.ts",
7
+ "files": ["dist"],
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/JonB32/urateam.git",
11
+ "directory": "packages/dashboard"
12
+ },
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "test": "vitest run",
16
+ "dev": "tsx watch src/server.ts"
17
+ },
18
+ "dependencies": {
19
+ "@hono/node-server": "^1.14.0",
20
+ "@urateam/core": "workspace:*",
21
+ "drizzle-orm": "^0.38.0",
22
+ "hono": "^4.7.0"
23
+ },
24
+ "devDependencies": {
25
+ "vitest": "^3.0.0",
26
+ "typescript": "^5.7.0"
27
+ }
28
+ }