@urateam/dashboard 0.1.4 → 0.1.6
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/dist/__tests__/audit.test.d.ts +2 -0
- package/dist/__tests__/audit.test.d.ts.map +1 -0
- package/dist/__tests__/audit.test.js +219 -0
- package/dist/__tests__/audit.test.js.map +1 -0
- package/dist/__tests__/auth-routes.test.d.ts +2 -0
- package/dist/__tests__/auth-routes.test.d.ts.map +1 -0
- package/dist/__tests__/auth-routes.test.js +116 -0
- package/dist/__tests__/auth-routes.test.js.map +1 -0
- package/dist/__tests__/bootstrap-integration.test.d.ts +2 -0
- package/dist/__tests__/bootstrap-integration.test.d.ts.map +1 -0
- package/dist/__tests__/bootstrap-integration.test.js +75 -0
- package/dist/__tests__/bootstrap-integration.test.js.map +1 -0
- package/dist/__tests__/cost-chart.test.d.ts +2 -0
- package/dist/__tests__/cost-chart.test.d.ts.map +1 -0
- package/dist/__tests__/cost-chart.test.js +51 -0
- package/dist/__tests__/cost-chart.test.js.map +1 -0
- package/dist/__tests__/cost.test.d.ts +2 -0
- package/dist/__tests__/cost.test.d.ts.map +1 -0
- package/dist/__tests__/cost.test.js +197 -0
- package/dist/__tests__/cost.test.js.map +1 -0
- package/dist/__tests__/helpers/license.d.ts +8 -0
- package/dist/__tests__/helpers/license.d.ts.map +1 -0
- package/dist/__tests__/helpers/license.js +71 -0
- package/dist/__tests__/helpers/license.js.map +1 -0
- package/dist/__tests__/rbac-middleware.test.d.ts +2 -0
- package/dist/__tests__/rbac-middleware.test.d.ts.map +1 -0
- package/dist/__tests__/rbac-middleware.test.js +59 -0
- package/dist/__tests__/rbac-middleware.test.js.map +1 -0
- package/dist/__tests__/retry-route.test.d.ts +2 -0
- package/dist/__tests__/retry-route.test.d.ts.map +1 -0
- package/dist/__tests__/retry-route.test.js +115 -0
- package/dist/__tests__/retry-route.test.js.map +1 -0
- package/dist/__tests__/sso-integration.test.d.ts +2 -0
- package/dist/__tests__/sso-integration.test.d.ts.map +1 -0
- package/dist/__tests__/sso-integration.test.js +91 -0
- package/dist/__tests__/sso-integration.test.js.map +1 -0
- package/dist/__tests__/sso-middleware.test.d.ts +2 -0
- package/dist/__tests__/sso-middleware.test.d.ts.map +1 -0
- package/dist/__tests__/sso-middleware.test.js +66 -0
- package/dist/__tests__/sso-middleware.test.js.map +1 -0
- package/dist/__tests__/users-routes.test.d.ts +2 -0
- package/dist/__tests__/users-routes.test.d.ts.map +1 -0
- package/dist/__tests__/users-routes.test.js +127 -0
- package/dist/__tests__/users-routes.test.js.map +1 -0
- package/dist/middleware/rbac.d.ts +4 -0
- package/dist/middleware/rbac.d.ts.map +1 -0
- package/dist/middleware/rbac.js +21 -0
- package/dist/middleware/rbac.js.map +1 -0
- package/dist/middleware/sso.d.ts +9 -0
- package/dist/middleware/sso.d.ts.map +1 -0
- package/dist/middleware/sso.js +47 -0
- package/dist/middleware/sso.js.map +1 -0
- package/dist/routes/audit.d.ts +4 -0
- package/dist/routes/audit.d.ts.map +1 -0
- package/dist/routes/audit.js +123 -0
- package/dist/routes/audit.js.map +1 -0
- package/dist/routes/auth.d.ts +10 -0
- package/dist/routes/auth.d.ts.map +1 -0
- package/dist/routes/auth.js +105 -0
- package/dist/routes/auth.js.map +1 -0
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +4 -2
- package/dist/routes/config.js.map +1 -1
- package/dist/routes/coordination.d.ts.map +1 -1
- package/dist/routes/coordination.js +5 -3
- package/dist/routes/coordination.js.map +1 -1
- package/dist/routes/cost.d.ts +10 -0
- package/dist/routes/cost.d.ts.map +1 -0
- package/dist/routes/cost.js +104 -0
- package/dist/routes/cost.js.map +1 -0
- package/dist/routes/errors.d.ts.map +1 -1
- package/dist/routes/errors.js +4 -2
- package/dist/routes/errors.js.map +1 -1
- package/dist/routes/runs.d.ts +9 -1
- package/dist/routes/runs.d.ts.map +1 -1
- package/dist/routes/runs.js +82 -9
- package/dist/routes/runs.js.map +1 -1
- package/dist/routes/tokens.d.ts.map +1 -1
- package/dist/routes/tokens.js +4 -2
- package/dist/routes/tokens.js.map +1 -1
- package/dist/routes/users.d.ts +8 -0
- package/dist/routes/users.d.ts.map +1 -0
- package/dist/routes/users.js +64 -0
- package/dist/routes/users.js.map +1 -0
- package/dist/server.d.ts +13 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +65 -7
- package/dist/server.js.map +1 -1
- package/dist/static/style.css +648 -0
- package/dist/views/audit.d.ts +29 -0
- package/dist/views/audit.d.ts.map +1 -0
- package/dist/views/audit.js +144 -0
- package/dist/views/audit.js.map +1 -0
- package/dist/views/cost.d.ts +28 -0
- package/dist/views/cost.d.ts.map +1 -0
- package/dist/views/cost.js +173 -0
- package/dist/views/cost.js.map +1 -0
- package/dist/views/forbidden.d.ts +7 -0
- package/dist/views/forbidden.d.ts.map +1 -0
- package/dist/views/forbidden.js +18 -0
- package/dist/views/forbidden.js.map +1 -0
- package/dist/views/layout.d.ts +11 -1
- package/dist/views/layout.d.ts.map +1 -1
- package/dist/views/layout.js +12 -18
- package/dist/views/layout.js.map +1 -1
- package/dist/views/run-detail.d.ts +1 -1
- package/dist/views/run-detail.d.ts.map +1 -1
- package/dist/views/run-detail.js +6 -1
- package/dist/views/run-detail.js.map +1 -1
- package/dist/views/users.d.ts +16 -0
- package/dist/views/users.d.ts.map +1 -0
- package/dist/views/users.js +44 -0
- package/dist/views/users.js.map +1 -0
- package/package.json +7 -5
- package/LICENSE +0 -27
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { escapeHtml, getBasePath } from "./layout.js";
|
|
2
|
+
function truncate(s, max = 200) {
|
|
3
|
+
if (s.length <= max)
|
|
4
|
+
return s;
|
|
5
|
+
return s.slice(0, max) + "...";
|
|
6
|
+
}
|
|
7
|
+
function toRelativeTime(date) {
|
|
8
|
+
const ms = Date.now() - date.getTime();
|
|
9
|
+
if (ms < 0)
|
|
10
|
+
return "just now";
|
|
11
|
+
const secs = Math.floor(ms / 1000);
|
|
12
|
+
if (secs < 60)
|
|
13
|
+
return `${secs}s ago`;
|
|
14
|
+
const mins = Math.floor(secs / 60);
|
|
15
|
+
if (mins < 60)
|
|
16
|
+
return `${mins}m ago`;
|
|
17
|
+
const hours = Math.floor(mins / 60);
|
|
18
|
+
if (hours < 24)
|
|
19
|
+
return `${hours}h ago`;
|
|
20
|
+
const days = Math.floor(hours / 24);
|
|
21
|
+
return `${days}d ago`;
|
|
22
|
+
}
|
|
23
|
+
function renderRow(e) {
|
|
24
|
+
const basePath = getBasePath();
|
|
25
|
+
const payloadJson = (() => {
|
|
26
|
+
try {
|
|
27
|
+
return JSON.stringify(e.payload ?? {});
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return "{}";
|
|
31
|
+
}
|
|
32
|
+
})();
|
|
33
|
+
const idEnc = encodeURIComponent(e.id);
|
|
34
|
+
return `<tr>
|
|
35
|
+
<td><button type="button"
|
|
36
|
+
hx-get="${basePath}/audit/event/${escapeHtml(idEnc)}"
|
|
37
|
+
hx-target="closest tr"
|
|
38
|
+
hx-swap="afterend"
|
|
39
|
+
title="Show full payload"
|
|
40
|
+
style="background:none;border:none;cursor:pointer;font-size:0.9em;">+</button></td>
|
|
41
|
+
<td title="${escapeHtml(e.timestamp.toISOString())}">${escapeHtml(toRelativeTime(e.timestamp))}</td>
|
|
42
|
+
<td><code>${escapeHtml(e.eventType)}</code></td>
|
|
43
|
+
<td>${escapeHtml(e.actor)}</td>
|
|
44
|
+
<td>${escapeHtml(e.actorType)}</td>
|
|
45
|
+
<td>${escapeHtml(e.scope ?? "")}</td>
|
|
46
|
+
<td>${escapeHtml(e.runId ?? "")}</td>
|
|
47
|
+
<td>${escapeHtml(e.issueId ?? "")}</td>
|
|
48
|
+
<td><code>${escapeHtml(truncate(payloadJson))}</code></td>
|
|
49
|
+
</tr>`;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Render a detail row inserted via HTMX after a row. Shows the full formatted
|
|
53
|
+
* payload plus any fields truncated in the compact row (timestamp, tokens).
|
|
54
|
+
*/
|
|
55
|
+
export function renderEventDetailRow(e) {
|
|
56
|
+
const payloadFormatted = (() => {
|
|
57
|
+
try {
|
|
58
|
+
return JSON.stringify(e.payload ?? {}, null, 2);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return "{}";
|
|
62
|
+
}
|
|
63
|
+
})();
|
|
64
|
+
return `<tr class="audit-detail-row">
|
|
65
|
+
<td></td>
|
|
66
|
+
<td colspan="8" style="background:#f8f9fa;padding:1rem;">
|
|
67
|
+
<div style="font-size:0.85em;color:#666;margin-bottom:0.5rem;">
|
|
68
|
+
<strong>Event ID:</strong> <code>${escapeHtml(e.id)}</code>
|
|
69
|
+
|
|
|
70
|
+
<strong>Timestamp:</strong> ${escapeHtml(e.timestamp.toISOString())}
|
|
71
|
+
|
|
|
72
|
+
<strong>Tokens:</strong> ${e.inputTokens} in / ${e.outputTokens} out
|
|
73
|
+
</div>
|
|
74
|
+
<pre style="background:#fff;border:1px solid #e0e0e0;padding:0.75rem;overflow:auto;margin:0;font-size:0.85em;">${escapeHtml(payloadFormatted)}</pre>
|
|
75
|
+
</td>
|
|
76
|
+
</tr>`;
|
|
77
|
+
}
|
|
78
|
+
function buildQueryString(filters, extra = {}) {
|
|
79
|
+
const params = new URLSearchParams();
|
|
80
|
+
for (const [k, v] of Object.entries({ ...filters, ...extra })) {
|
|
81
|
+
if (v !== undefined && v !== null && v !== "")
|
|
82
|
+
params.set(k, String(v));
|
|
83
|
+
}
|
|
84
|
+
const s = params.toString();
|
|
85
|
+
return s ? "?" + s : "";
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Render the audit log page. When `partial` is true, emits only the table
|
|
89
|
+
* body rows + load-more marker so HTMX can swap into the tbody.
|
|
90
|
+
*/
|
|
91
|
+
export function renderAuditPage(args) {
|
|
92
|
+
const basePath = getBasePath();
|
|
93
|
+
const { events, nextCursor, filters, partial } = args;
|
|
94
|
+
const rows = events.map(renderRow).join("\n");
|
|
95
|
+
const loadMore = nextCursor
|
|
96
|
+
? `<tr id="audit-load-more"><td colspan="9" style="text-align:center;padding:1rem;">
|
|
97
|
+
<a href="${basePath}/audit${escapeHtml(buildQueryString(filters, { cursor: nextCursor }))}"
|
|
98
|
+
hx-get="${basePath}/audit/page${escapeHtml(buildQueryString(filters, { cursor: nextCursor }))}"
|
|
99
|
+
hx-target="#audit-load-more"
|
|
100
|
+
hx-swap="outerHTML">Load more</a>
|
|
101
|
+
</td></tr>`
|
|
102
|
+
: "";
|
|
103
|
+
if (partial) {
|
|
104
|
+
return rows + loadMore;
|
|
105
|
+
}
|
|
106
|
+
const exportHref = `${basePath}/audit/export.csv${escapeHtml(buildQueryString(filters))}`;
|
|
107
|
+
const filterBar = `<form method="get" action="${basePath}/audit" class="audit-filters">
|
|
108
|
+
<input type="text" name="eventType" placeholder="event type" value="${escapeHtml(filters.eventType ?? "")}">
|
|
109
|
+
<input type="text" name="scope" placeholder="scope" value="${escapeHtml(filters.scope ?? "")}">
|
|
110
|
+
<input type="text" name="actor" placeholder="actor prefix" value="${escapeHtml(filters.actor ?? "")}">
|
|
111
|
+
<input type="text" name="runId" placeholder="run id" value="${escapeHtml(filters.runId ?? "")}">
|
|
112
|
+
<input type="text" name="issueId" placeholder="issue id" value="${escapeHtml(filters.issueId ?? "")}">
|
|
113
|
+
<input type="text" name="q" placeholder="search payload" value="${escapeHtml(filters.q ?? "")}">
|
|
114
|
+
<input type="datetime-local" name="from" value="${escapeHtml(filters.from ?? "")}">
|
|
115
|
+
<input type="datetime-local" name="to" value="${escapeHtml(filters.to ?? "")}">
|
|
116
|
+
<button type="submit">Filter</button>
|
|
117
|
+
<a class="btn" href="${exportHref}">Export CSV</a>
|
|
118
|
+
</form>`;
|
|
119
|
+
return `<div id="audit">
|
|
120
|
+
${filterBar}
|
|
121
|
+
<div class="table-wrapper">
|
|
122
|
+
<table>
|
|
123
|
+
<thead>
|
|
124
|
+
<tr>
|
|
125
|
+
<th></th>
|
|
126
|
+
<th>Time</th>
|
|
127
|
+
<th>Event</th>
|
|
128
|
+
<th>Actor</th>
|
|
129
|
+
<th>Actor type</th>
|
|
130
|
+
<th>Scope</th>
|
|
131
|
+
<th>Run</th>
|
|
132
|
+
<th>Issue</th>
|
|
133
|
+
<th>Payload</th>
|
|
134
|
+
</tr>
|
|
135
|
+
</thead>
|
|
136
|
+
<tbody id="audit-rows">
|
|
137
|
+
${rows.length > 0 ? rows : '<tr><td colspan="9" style="text-align:center;color:#999;padding:2rem;">No audit events</td></tr>'}
|
|
138
|
+
${loadMore}
|
|
139
|
+
</tbody>
|
|
140
|
+
</table>
|
|
141
|
+
</div>
|
|
142
|
+
</div>`;
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/views/audit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { AggregateResult, CostsConfig, DailyRow } from "@urateam/core";
|
|
2
|
+
export interface CostFilters {
|
|
3
|
+
from: Date;
|
|
4
|
+
to: Date;
|
|
5
|
+
preset: string;
|
|
6
|
+
}
|
|
7
|
+
export interface RenderCostPageArgs {
|
|
8
|
+
result: AggregateResult;
|
|
9
|
+
filters: CostFilters;
|
|
10
|
+
costs: CostsConfig;
|
|
11
|
+
basePath?: string;
|
|
12
|
+
/** When true, render only the body (HTMX partial). */
|
|
13
|
+
partial?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Render a simple SVG trend chart of daily dollars over the window.
|
|
17
|
+
* Inline SVG avoids any external script/dep — CSP-safe.
|
|
18
|
+
*
|
|
19
|
+
* Returns an empty string when there are fewer than 2 data points (a single
|
|
20
|
+
* dot isn't meaningful and tiny polylines collapse to invisible).
|
|
21
|
+
*/
|
|
22
|
+
export declare function renderCostChart(byDay: DailyRow[]): string;
|
|
23
|
+
/**
|
|
24
|
+
* Render the cost & ROI page. When `partial` is true, emits only the body
|
|
25
|
+
* (summary + tables) so HTMX can swap it into #cost-body.
|
|
26
|
+
*/
|
|
27
|
+
export declare function renderCostPage(args: RenderCostPageArgs): string;
|
|
28
|
+
//# sourceMappingURL=cost.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cost.d.ts","sourceRoot":"","sources":["../../src/views/cost.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAgB,WAAW,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAG1F,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,IAAI,CAAC;IACX,EAAE,EAAE,IAAI,CAAC;IACT,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,eAAe,CAAC;IACxB,OAAO,EAAE,WAAW,CAAC;IACrB,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sDAAsD;IACtD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAsBD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,CAqCzD;AAuDD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,kBAAkB,GAAG,MAAM,CAuE/D"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { escapeHtml } from "./layout.js";
|
|
2
|
+
function fmtNumber(n) {
|
|
3
|
+
if (!isFinite(n))
|
|
4
|
+
return "∞";
|
|
5
|
+
return n.toLocaleString("en-US", { maximumFractionDigits: 0 });
|
|
6
|
+
}
|
|
7
|
+
function fmtHours(n) {
|
|
8
|
+
if (!isFinite(n))
|
|
9
|
+
return "∞";
|
|
10
|
+
return n.toLocaleString("en-US", { maximumFractionDigits: 1 });
|
|
11
|
+
}
|
|
12
|
+
function fmtDollars(n) {
|
|
13
|
+
if (!isFinite(n))
|
|
14
|
+
return "∞";
|
|
15
|
+
return "$" + n.toFixed(2);
|
|
16
|
+
}
|
|
17
|
+
function fmtRoi(n) {
|
|
18
|
+
if (!isFinite(n))
|
|
19
|
+
return "∞";
|
|
20
|
+
return n.toFixed(1) + "×";
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Render a simple SVG trend chart of daily dollars over the window.
|
|
24
|
+
* Inline SVG avoids any external script/dep — CSP-safe.
|
|
25
|
+
*
|
|
26
|
+
* Returns an empty string when there are fewer than 2 data points (a single
|
|
27
|
+
* dot isn't meaningful and tiny polylines collapse to invisible).
|
|
28
|
+
*/
|
|
29
|
+
export function renderCostChart(byDay) {
|
|
30
|
+
if (byDay.length < 2)
|
|
31
|
+
return "";
|
|
32
|
+
const width = 600;
|
|
33
|
+
const height = 80;
|
|
34
|
+
const padX = 8;
|
|
35
|
+
const padY = 8;
|
|
36
|
+
const usableW = width - padX * 2;
|
|
37
|
+
const usableH = height - padY * 2;
|
|
38
|
+
const maxDollars = Math.max(...byDay.map((d) => d.dollars));
|
|
39
|
+
// When the max is zero, avoid divide-by-zero and render a flat baseline.
|
|
40
|
+
const effMax = maxDollars > 0 ? maxDollars : 1;
|
|
41
|
+
const n = byDay.length;
|
|
42
|
+
const points = byDay
|
|
43
|
+
.map((d, i) => {
|
|
44
|
+
const x = padX + (i / (n - 1)) * usableW;
|
|
45
|
+
const y = padY + usableH - (d.dollars / effMax) * usableH;
|
|
46
|
+
return `${x.toFixed(1)},${y.toFixed(1)}`;
|
|
47
|
+
})
|
|
48
|
+
.join(" ");
|
|
49
|
+
// Simple axis labels: first and last date, max dollars on Y axis.
|
|
50
|
+
const firstDate = byDay[0].date;
|
|
51
|
+
const lastDate = byDay[byDay.length - 1].date;
|
|
52
|
+
const totalDollars = byDay.reduce((sum, d) => sum + d.dollars, 0);
|
|
53
|
+
return `<div class="cost-chart" style="margin:1rem 0;">
|
|
54
|
+
<div style="display:flex;justify-content:space-between;font-size:0.85em;color:#666;margin-bottom:0.25rem;">
|
|
55
|
+
<span>Daily cost — ${escapeHtml(firstDate)} to ${escapeHtml(lastDate)}</span>
|
|
56
|
+
<span>Total: ${fmtDollars(totalDollars)} · peak: ${fmtDollars(maxDollars)}</span>
|
|
57
|
+
</div>
|
|
58
|
+
<svg viewBox="0 0 ${width} ${height}" width="100%" height="${height}" xmlns="http://www.w3.org/2000/svg" style="border:1px solid #e0e0e0;background:#fafbfc;">
|
|
59
|
+
<polyline fill="none" stroke="#3a7afe" stroke-width="2" points="${points}" />
|
|
60
|
+
</svg>
|
|
61
|
+
</div>`;
|
|
62
|
+
}
|
|
63
|
+
function renderBreakdownTable(title, rows) {
|
|
64
|
+
const body = rows.length === 0
|
|
65
|
+
? '<tr><td colspan="7" style="text-align:center;color:#999;padding:1rem;">No data</td></tr>'
|
|
66
|
+
: rows
|
|
67
|
+
.map((r) => `<tr>
|
|
68
|
+
<td>${escapeHtml(r.label)}</td>
|
|
69
|
+
<td>${fmtNumber(r.runs)}</td>
|
|
70
|
+
<td>${fmtNumber(r.prsMerged)}</td>
|
|
71
|
+
<td>${fmtHours(r.timeSavedHours)}</td>
|
|
72
|
+
<td>${fmtNumber(r.inputTokens + r.outputTokens)}</td>
|
|
73
|
+
<td>${fmtDollars(r.dollars)}</td>
|
|
74
|
+
<td>${fmtRoi(r.roiMultiplier)}</td>
|
|
75
|
+
</tr>`)
|
|
76
|
+
.join("\n");
|
|
77
|
+
return `<h3>${escapeHtml(title)}</h3>
|
|
78
|
+
<div class="table-wrapper">
|
|
79
|
+
<table class="cost-breakdown">
|
|
80
|
+
<thead>
|
|
81
|
+
<tr>
|
|
82
|
+
<th>Name</th>
|
|
83
|
+
<th>Runs</th>
|
|
84
|
+
<th>PRs merged</th>
|
|
85
|
+
<th>Hours saved</th>
|
|
86
|
+
<th>Tokens</th>
|
|
87
|
+
<th>Cost</th>
|
|
88
|
+
<th>ROI</th>
|
|
89
|
+
</tr>
|
|
90
|
+
</thead>
|
|
91
|
+
<tbody>
|
|
92
|
+
${body}
|
|
93
|
+
</tbody>
|
|
94
|
+
</table>
|
|
95
|
+
</div>`;
|
|
96
|
+
}
|
|
97
|
+
function buildExportHref(basePath, filters) {
|
|
98
|
+
const from = filters.from.toISOString().slice(0, 10);
|
|
99
|
+
const to = filters.to.toISOString().slice(0, 10);
|
|
100
|
+
const params = new URLSearchParams({
|
|
101
|
+
window: filters.preset,
|
|
102
|
+
from,
|
|
103
|
+
to,
|
|
104
|
+
});
|
|
105
|
+
return `${basePath}/cost/export.csv?${params.toString()}`;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Render the cost & ROI page. When `partial` is true, emits only the body
|
|
109
|
+
* (summary + tables) so HTMX can swap it into #cost-body.
|
|
110
|
+
*/
|
|
111
|
+
export function renderCostPage(args) {
|
|
112
|
+
const { result, filters, costs, partial } = args;
|
|
113
|
+
const basePath = args.basePath ?? "";
|
|
114
|
+
const { summary, byTeam, byRepo, byPipeline, byDay } = result;
|
|
115
|
+
const hourlyRate = costs.hourlyEngRate ?? 50;
|
|
116
|
+
const value = summary.timeSavedHours * hourlyRate;
|
|
117
|
+
const bodyInner = `${summary.truncated ? `<div class="warning-banner">⚠ Window exceeds 10,000 runs. Results truncated to the 10,000 most recent. Narrow your date range for complete totals.</div>` : ""}
|
|
118
|
+
<div class="summary-card">
|
|
119
|
+
<p><strong>${fmtNumber(summary.prsMerged)} PRs merged</strong>
|
|
120
|
+
· <strong>${fmtHours(summary.timeSavedHours)}h saved</strong>
|
|
121
|
+
· <strong>${fmtNumber(summary.runs)} runs</strong></p>
|
|
122
|
+
<p>${fmtNumber(summary.inputTokens + summary.outputTokens)} tokens
|
|
123
|
+
· ${fmtDollars(summary.dollars)} cost</p>
|
|
124
|
+
<p><strong>ROI:</strong> ${fmtHours(summary.timeSavedHours)}h × ${fmtDollars(hourlyRate)}/h
|
|
125
|
+
= ${fmtDollars(value)} value ÷ ${fmtDollars(summary.dollars)} cost
|
|
126
|
+
= ${fmtRoi(summary.roiMultiplier)}</p>
|
|
127
|
+
</div>
|
|
128
|
+
${summary.truncated ? "" : renderCostChart(byDay ?? [])}
|
|
129
|
+
${renderBreakdownTable("By team", byTeam)}
|
|
130
|
+
${renderBreakdownTable("By repo", byRepo)}
|
|
131
|
+
${renderBreakdownTable("By pipeline", byPipeline)}
|
|
132
|
+
<details class="cost-formula">
|
|
133
|
+
<summary>Formula</summary>
|
|
134
|
+
<p>Time saved per PR is ${fmtNumber(costs.timeSavedPerPrDefault ?? 4)}h by default, overridable per pipeline via <code>timeSavedPerPr</code>.</p>
|
|
135
|
+
<p>Dollar cost is computed per-stage from each stage's model:</p>
|
|
136
|
+
<ul>
|
|
137
|
+
${Object.entries(costs.modelPricing ?? {})
|
|
138
|
+
.map(([model, rate]) => `<li><code>${escapeHtml(model)}</code>: $${escapeHtml(String(rate.inputPerMillion))}/M input, $${escapeHtml(String(rate.outputPerMillion))}/M output</li>`)
|
|
139
|
+
.join("")}
|
|
140
|
+
</ul>
|
|
141
|
+
<p>Hourly engineer rate (for ROI): ${fmtDollars(hourlyRate)}</p>
|
|
142
|
+
<p>ROI = (hours saved × hourly rate) ÷ dollar cost</p>
|
|
143
|
+
</details>`;
|
|
144
|
+
if (partial) {
|
|
145
|
+
return bodyInner;
|
|
146
|
+
}
|
|
147
|
+
const exportHref = buildExportHref(basePath, filters);
|
|
148
|
+
const opt = (val, label) => `<option value="${val}"${filters.preset === val ? " selected" : ""}>${escapeHtml(label)}</option>`;
|
|
149
|
+
return `<div id="cost">
|
|
150
|
+
<form method="get" action="${basePath}/cost" class="cost-filters"
|
|
151
|
+
hx-get="${basePath}/cost/page" hx-trigger="change" hx-target="#cost-body">
|
|
152
|
+
<label>Window
|
|
153
|
+
<select name="window">
|
|
154
|
+
${opt("7d", "Last 7 days")}
|
|
155
|
+
${opt("30d", "Last 30 days")}
|
|
156
|
+
${opt("90d", "Last 90 days")}
|
|
157
|
+
${opt("365d", "Last 365 days")}
|
|
158
|
+
${opt("custom", "Custom")}
|
|
159
|
+
</select>
|
|
160
|
+
</label>
|
|
161
|
+
${filters.preset === "custom"
|
|
162
|
+
? `<input type="date" name="from" value="${escapeHtml(filters.from.toISOString().slice(0, 10))}">
|
|
163
|
+
<input type="date" name="to" value="${escapeHtml(filters.to.toISOString().slice(0, 10))}">`
|
|
164
|
+
: ""}
|
|
165
|
+
<button type="submit">Apply</button>
|
|
166
|
+
<a class="btn" href="${escapeHtml(exportHref)}">Export CSV</a>
|
|
167
|
+
</form>
|
|
168
|
+
<div id="cost-body">
|
|
169
|
+
${bodyInner}
|
|
170
|
+
</div>
|
|
171
|
+
</div>`;
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=cost.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cost.js","sourceRoot":"","sources":["../../src/views/cost.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forbidden.d.ts","sourceRoot":"","sources":["../../src/views/forbidden.ts"],"names":[],"mappings":"AASA,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,MAAM,CAQT"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
function escapeHtml(s) {
|
|
2
|
+
return s
|
|
3
|
+
.replace(/&/g, "&")
|
|
4
|
+
.replace(/</g, "<")
|
|
5
|
+
.replace(/>/g, ">")
|
|
6
|
+
.replace(/"/g, """)
|
|
7
|
+
.replace(/'/g, "'");
|
|
8
|
+
}
|
|
9
|
+
export function renderForbidden(args) {
|
|
10
|
+
return `<!DOCTYPE html>
|
|
11
|
+
<html><head><title>Forbidden</title></head><body>
|
|
12
|
+
<h1>403 — Access denied</h1>
|
|
13
|
+
<p>Your account (<strong>${escapeHtml(args.email)}</strong>, role <strong>${escapeHtml(args.role)}</strong>) does not have permission to access this page (requires <code>${escapeHtml(args.action)}</code>).</p>
|
|
14
|
+
<p>Contact your administrator.</p>
|
|
15
|
+
<p><a href="${escapeHtml(args.basePath)}/auth/logout">Sign out</a></p>
|
|
16
|
+
</body></html>`;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=forbidden.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forbidden.js","sourceRoot":"","sources":["../../src/views/forbidden.ts"],"names":[],"mappings":"AAAA,SAAS,UAAU,CAAC,CAAS;IAC3B,OAAO,CAAC;SACL,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,OAAO,CAAC,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAK/B;IACC,OAAO;;;6BAGoB,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,2BAA2B,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,2EAA2E,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC;;gBAErL,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC;eAC1B,CAAC;AAChB,CAAC"}
|
package/dist/views/layout.d.ts
CHANGED
|
@@ -16,6 +16,16 @@ export declare function getBasePath(): string;
|
|
|
16
16
|
* (sourced from `DashboardConfig.basePath`) to decouple
|
|
17
17
|
* link rendering from the process environment.
|
|
18
18
|
*/
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Optional context propagated from the request handler. When `userEmail`
|
|
21
|
+
* is set the nav renders a Sign Out form (POST /auth/logout). Routes that
|
|
22
|
+
* have access to `c.get("user")` should pass it; others omit and get the
|
|
23
|
+
* existing layout unchanged.
|
|
24
|
+
*/
|
|
25
|
+
export interface LayoutContext {
|
|
26
|
+
userEmail?: string;
|
|
27
|
+
userRole?: string;
|
|
28
|
+
}
|
|
29
|
+
export declare function layout(title: string, content: string, basePath?: string, ctx?: LayoutContext): string;
|
|
20
30
|
export declare function escapeHtml(str: string): string;
|
|
21
31
|
//# sourceMappingURL=layout.d.ts.map
|
|
@@ -1 +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,
|
|
1
|
+
{"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../src/views/layout.ts"],"names":[],"mappings":"AAGA,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAiBD;;;;;;;;;;;;;;;;GAgBG;AACH;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,MAAM,CACpB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,EACjB,GAAG,CAAC,EAAE,aAAa,GAClB,MAAM,CA+CR;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO9C"}
|
package/dist/views/layout.js
CHANGED
|
@@ -18,26 +18,16 @@ function normalizeBasePath(basePath) {
|
|
|
18
18
|
// Strip all trailing slashes; collapse bare "/" to ""
|
|
19
19
|
return basePath.replace(/\/+$/, "");
|
|
20
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) {
|
|
21
|
+
export function layout(title, content, basePath, ctx) {
|
|
39
22
|
// Explicit parameter takes priority; fall back to env var for backward compat.
|
|
40
23
|
const bp = normalizeBasePath(basePath ?? getBasePath());
|
|
24
|
+
// Logout goes through the dashboard CSRF middleware (which requires an
|
|
25
|
+
// HX-Request header on all state-changing routes). Using hx-post with an
|
|
26
|
+
// explicit HX-Request header on the button ensures the request is not
|
|
27
|
+
// forgeable from a third-party origin via a plain <form> submission.
|
|
28
|
+
const signOut = ctx?.userEmail
|
|
29
|
+
? `<button type="button" class="link signout-btn" hx-post="${bp}/auth/logout" hx-headers='{"HX-Request":"true"}' hx-push-url="true" hx-swap="none">Sign out (${escapeHtml(ctx.userEmail)})</button>`
|
|
30
|
+
: "";
|
|
41
31
|
const cspContent = "default-src 'self'; script-src 'self' https://unpkg.com; style-src 'self'";
|
|
42
32
|
return `<!DOCTYPE html>
|
|
43
33
|
<html lang="en">
|
|
@@ -61,8 +51,12 @@ export function layout(title, content, basePath) {
|
|
|
61
51
|
<a href="${bp}/">Runs</a>
|
|
62
52
|
<a href="${bp}/tokens">Tokens</a>
|
|
63
53
|
<a href="${bp}/errors">Errors</a>
|
|
54
|
+
<a href="${bp}/audit">Audit</a>
|
|
55
|
+
<a href="${bp}/cost">Cost</a>
|
|
64
56
|
<a href="${bp}/config">Config</a>
|
|
65
57
|
<a href="${bp}/coordination">Coordination</a>
|
|
58
|
+
${ctx?.userRole === "admin" ? `<a href="${bp}/users">Users</a>` : ""}
|
|
59
|
+
${signOut}
|
|
66
60
|
</nav>
|
|
67
61
|
<main>
|
|
68
62
|
<h1>${escapeHtml(title)}</h1>
|
package/dist/views/layout.js.map
CHANGED
|
@@ -1 +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;
|
|
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;AA8BD,MAAM,UAAU,MAAM,CACpB,KAAa,EACb,OAAe,EACf,QAAiB,EACjB,GAAmB;IAEnB,+EAA+E;IAC/E,MAAM,EAAE,GAAG,iBAAiB,CAAC,QAAQ,IAAI,WAAW,EAAE,CAAC,CAAC;IACxD,uEAAuE;IACvE,yEAAyE;IACzE,sEAAsE;IACtE,qEAAqE;IACrE,MAAM,OAAO,GAAG,GAAG,EAAE,SAAS;QAC5B,CAAC,CAAC,2DAA2D,EAAE,gGAAgG,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,YAAY;QACpM,CAAC,CAAC,EAAE,CAAC;IACP,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;eACF,EAAE;eACF,EAAE;MACX,GAAG,EAAE,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,YAAY,EAAE,mBAAmB,CAAC,CAAC,CAAC,EAAE;MAClE,OAAO;;;UAGH,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"}
|
|
@@ -31,5 +31,5 @@ export interface LogEntry {
|
|
|
31
31
|
type: string;
|
|
32
32
|
content: string;
|
|
33
33
|
}
|
|
34
|
-
export declare function runDetailView(run: RunInfo, stages: StageInfo[], logs: LogEntry[], page: number, totalLogs: number): string;
|
|
34
|
+
export declare function runDetailView(run: RunInfo, stages: StageInfo[], logs: LogEntry[], page: number, totalLogs: number, canRetry?: boolean): string;
|
|
35
35
|
//# sourceMappingURL=run-detail.d.ts.map
|
|
@@ -1 +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,
|
|
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,EACjB,QAAQ,GAAE,OAAe,GACxB,MAAM,CAmGR"}
|
package/dist/views/run-detail.js
CHANGED
|
@@ -48,7 +48,7 @@ function formatJson(raw) {
|
|
|
48
48
|
return escapeHtml(raw);
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
-
export function runDetailView(run, stages, logs, page, totalLogs) {
|
|
51
|
+
export function runDetailView(run, stages, logs, page, totalLogs, canRetry = false) {
|
|
52
52
|
const basePath = getBasePath();
|
|
53
53
|
const logsPerPage = 50;
|
|
54
54
|
const totalPages = Math.max(1, Math.ceil(totalLogs / logsPerPage));
|
|
@@ -60,6 +60,11 @@ export function runDetailView(run, stages, logs, page, totalLogs) {
|
|
|
60
60
|
${statusBadge(run.status)}
|
|
61
61
|
<span class="duration-badge" title="Total run duration">⏱ ${duration}${isInFlight ? " (running)" : ""}</span>
|
|
62
62
|
${run.prUrl ? `<a href="${escapeHtml(run.prUrl)}" target="_blank" rel="noopener" style="font-size:0.875rem;">↗ Pull Request</a>` : ""}
|
|
63
|
+
${canRetry && (run.status === "failed" || run.status === "retriable")
|
|
64
|
+
? `<form method="post" action="${basePath}/runs/${encodeURIComponent(run.id)}/retry" hx-post="${basePath}/runs/${encodeURIComponent(run.id)}/retry" hx-headers='{"HX-Request":"true"}' style="display:inline;margin:0;">
|
|
65
|
+
<button type="submit" class="button-primary">Retry</button>
|
|
66
|
+
</form>`
|
|
67
|
+
: ""}
|
|
63
68
|
</div>
|
|
64
69
|
<dl class="meta">
|
|
65
70
|
<div><dt>Issue</dt><dd>${escapeHtml(run.issueId)}: ${escapeHtml(run.issueTitle)}</dd></div>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-detail.js","sourceRoot":"","sources":["../../src/views/run-detail.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAsCtD,SAAS,WAAW,CAAC,MAAc;IACjC,OAAO,4BAA4B,MAAM,KAAK,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC;AAC5E,CAAC;AAED,SAAS,UAAU,CAAC,CAAc;IAChC,IAAI,CAAC,CAAC;QAAE,OAAO,GAAG,CAAC;IACnB,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,MAAM,CAAC;AACjE,CAAC;AAED,SAAS,cAAc,CAAC,CAAc;IACpC,IAAI,CAAC,CAAC;QAAE,OAAO,GAAG,CAAC;IACnB,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;IAC9C,IAAI,EAAE,GAAG,CAAC;QAAE,OAAO,UAAU,CAAC;IAC9B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;IACnC,IAAI,IAAI,GAAG,EAAE;QAAE,OAAO,GAAG,IAAI,OAAO,CAAC;IACrC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;IACnC,IAAI,IAAI,GAAG,EAAE;QAAE,OAAO,GAAG,IAAI,OAAO,CAAC;IACrC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;IACpC,IAAI,KAAK,GAAG,EAAE;QAAE,OAAO,GAAG,KAAK,OAAO,CAAC;IACvC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC;IACpC,OAAO,GAAG,IAAI,OAAO,CAAC;AACxB,CAAC;AAED,SAAS,cAAc,CAAC,KAAW,EAAE,GAAgB;IACnD,MAAM,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;IAC/C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;IAC7D,IAAI,OAAO,GAAG,EAAE;QAAE,OAAO,GAAG,OAAO,GAAG,CAAC;IACvC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;IACrC,MAAM,GAAG,GAAG,OAAO,GAAG,EAAE,CAAC;IACzB,IAAI,GAAG,GAAG,EAAE;QAAE,OAAO,GAAG,GAAG,KAAK,GAAG,GAAG,CAAC;IACvC,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC;IAChC,OAAO,GAAG,EAAE,KAAK,GAAG,GAAG,EAAE,GAAG,CAAC;AAC/B,CAAC;AAED,SAAS,UAAU,CAAC,GAAkB;IACpC,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAY,CAAC;QACvC,OAAO,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAC3B,GAAY,EACZ,MAAmB,EACnB,IAAgB,EAChB,IAAY,EACZ,SAAiB;
|
|
1
|
+
{"version":3,"file":"run-detail.js","sourceRoot":"","sources":["../../src/views/run-detail.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Role } from "@urateam/core";
|
|
2
|
+
export interface UserRow {
|
|
3
|
+
id: string;
|
|
4
|
+
email: string;
|
|
5
|
+
name: string | null;
|
|
6
|
+
role: Role;
|
|
7
|
+
lastLoginAt: Date | null;
|
|
8
|
+
}
|
|
9
|
+
export declare function renderUsersPage(args: {
|
|
10
|
+
users: UserRow[];
|
|
11
|
+
currentUserId: string;
|
|
12
|
+
basePath: string;
|
|
13
|
+
userEmail?: string;
|
|
14
|
+
userRole?: string;
|
|
15
|
+
}): string;
|
|
16
|
+
//# sourceMappingURL=users.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../src/views/users.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAG1C,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,IAAI,CAAC;IACX,WAAW,EAAE,IAAI,GAAG,IAAI,CAAC;CAC1B;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,MAAM,CA2CT"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { escapeHtml, layout } from "./layout.js";
|
|
2
|
+
export function renderUsersPage(args) {
|
|
3
|
+
const bp = args.basePath;
|
|
4
|
+
const rows = args.users
|
|
5
|
+
.map((u) => {
|
|
6
|
+
const isSelf = u.id === args.currentUserId;
|
|
7
|
+
const lastLogin = u.lastLoginAt
|
|
8
|
+
? u.lastLoginAt.toISOString()
|
|
9
|
+
: "(never)";
|
|
10
|
+
const disabled = isSelf ? "disabled" : "";
|
|
11
|
+
return `
|
|
12
|
+
<tr>
|
|
13
|
+
<td>${escapeHtml(u.email)}${isSelf ? " <em>(you)</em>" : ""}</td>
|
|
14
|
+
<td>${escapeHtml(u.name ?? "")}</td>
|
|
15
|
+
<td>${escapeHtml(u.role)}</td>
|
|
16
|
+
<td>${escapeHtml(lastLogin)}</td>
|
|
17
|
+
<td>
|
|
18
|
+
<form method="post" action="${bp}/users/${escapeHtml(u.id)}/role" hx-post="${bp}/users/${escapeHtml(u.id)}/role" hx-headers='{"HX-Request":"true"}'>
|
|
19
|
+
<select name="role" ${disabled}>
|
|
20
|
+
<option value="admin" ${u.role === "admin" ? "selected" : ""}>admin</option>
|
|
21
|
+
<option value="operator" ${u.role === "operator" ? "selected" : ""}>operator</option>
|
|
22
|
+
<option value="viewer" ${u.role === "viewer" ? "selected" : ""}>viewer</option>
|
|
23
|
+
</select>
|
|
24
|
+
<button type="submit" ${disabled}>Update</button>
|
|
25
|
+
</form>
|
|
26
|
+
</td>
|
|
27
|
+
</tr>`;
|
|
28
|
+
})
|
|
29
|
+
.join("");
|
|
30
|
+
const content = `
|
|
31
|
+
<table class="users-table">
|
|
32
|
+
<thead>
|
|
33
|
+
<tr>
|
|
34
|
+
<th>Email</th><th>Name</th><th>Role</th><th>Last login</th><th></th>
|
|
35
|
+
</tr>
|
|
36
|
+
</thead>
|
|
37
|
+
<tbody>${rows}</tbody>
|
|
38
|
+
</table>`;
|
|
39
|
+
return layout("Users", content, bp, {
|
|
40
|
+
userEmail: args.userEmail,
|
|
41
|
+
userRole: args.userRole,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=users.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"users.js","sourceRoot":"","sources":["../../src/views/users.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAUjD,MAAM,UAAU,eAAe,CAAC,IAM/B;IACC,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;IACzB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK;SACpB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,MAAM,MAAM,GAAG,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,aAAa,CAAC;QAC3C,MAAM,SAAS,GAAG,CAAC,CAAC,WAAW;YAC7B,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,WAAW,EAAE;YAC7B,CAAC,CAAC,SAAS,CAAC;QACd,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1C,OAAO;;cAEC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE;cACrD,UAAU,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;cACxB,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC;cAClB,UAAU,CAAC,SAAS,CAAC;;wCAEK,EAAE,UAAU,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,mBAAmB,EAAE,UAAU,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;kCACjF,QAAQ;sCACJ,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE;yCACjC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE;uCACzC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE;;oCAExC,QAAQ;;;YAGhC,CAAC;IACT,CAAC,CAAC;SACD,IAAI,CAAC,EAAE,CAAC,CAAC;IAEZ,MAAM,OAAO,GAAG;;;;;;;aAOL,IAAI;WACN,CAAC;IAEV,OAAO,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE;QAClC,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;KACxB,CAAC,CAAC;AACL,CAAC"}
|