@urateam/dashboard 0.1.4 → 0.1.5

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 (115) hide show
  1. package/dist/__tests__/audit.test.d.ts +2 -0
  2. package/dist/__tests__/audit.test.d.ts.map +1 -0
  3. package/dist/__tests__/audit.test.js +219 -0
  4. package/dist/__tests__/audit.test.js.map +1 -0
  5. package/dist/__tests__/auth-routes.test.d.ts +2 -0
  6. package/dist/__tests__/auth-routes.test.d.ts.map +1 -0
  7. package/dist/__tests__/auth-routes.test.js +116 -0
  8. package/dist/__tests__/auth-routes.test.js.map +1 -0
  9. package/dist/__tests__/bootstrap-integration.test.d.ts +2 -0
  10. package/dist/__tests__/bootstrap-integration.test.d.ts.map +1 -0
  11. package/dist/__tests__/bootstrap-integration.test.js +75 -0
  12. package/dist/__tests__/bootstrap-integration.test.js.map +1 -0
  13. package/dist/__tests__/cost-chart.test.d.ts +2 -0
  14. package/dist/__tests__/cost-chart.test.d.ts.map +1 -0
  15. package/dist/__tests__/cost-chart.test.js +51 -0
  16. package/dist/__tests__/cost-chart.test.js.map +1 -0
  17. package/dist/__tests__/cost.test.d.ts +2 -0
  18. package/dist/__tests__/cost.test.d.ts.map +1 -0
  19. package/dist/__tests__/cost.test.js +197 -0
  20. package/dist/__tests__/cost.test.js.map +1 -0
  21. package/dist/__tests__/helpers/license.d.ts +8 -0
  22. package/dist/__tests__/helpers/license.d.ts.map +1 -0
  23. package/dist/__tests__/helpers/license.js +71 -0
  24. package/dist/__tests__/helpers/license.js.map +1 -0
  25. package/dist/__tests__/rbac-middleware.test.d.ts +2 -0
  26. package/dist/__tests__/rbac-middleware.test.d.ts.map +1 -0
  27. package/dist/__tests__/rbac-middleware.test.js +59 -0
  28. package/dist/__tests__/rbac-middleware.test.js.map +1 -0
  29. package/dist/__tests__/retry-route.test.d.ts +2 -0
  30. package/dist/__tests__/retry-route.test.d.ts.map +1 -0
  31. package/dist/__tests__/retry-route.test.js +115 -0
  32. package/dist/__tests__/retry-route.test.js.map +1 -0
  33. package/dist/__tests__/sso-integration.test.d.ts +2 -0
  34. package/dist/__tests__/sso-integration.test.d.ts.map +1 -0
  35. package/dist/__tests__/sso-integration.test.js +91 -0
  36. package/dist/__tests__/sso-integration.test.js.map +1 -0
  37. package/dist/__tests__/sso-middleware.test.d.ts +2 -0
  38. package/dist/__tests__/sso-middleware.test.d.ts.map +1 -0
  39. package/dist/__tests__/sso-middleware.test.js +66 -0
  40. package/dist/__tests__/sso-middleware.test.js.map +1 -0
  41. package/dist/__tests__/users-routes.test.d.ts +2 -0
  42. package/dist/__tests__/users-routes.test.d.ts.map +1 -0
  43. package/dist/__tests__/users-routes.test.js +127 -0
  44. package/dist/__tests__/users-routes.test.js.map +1 -0
  45. package/dist/middleware/rbac.d.ts +4 -0
  46. package/dist/middleware/rbac.d.ts.map +1 -0
  47. package/dist/middleware/rbac.js +21 -0
  48. package/dist/middleware/rbac.js.map +1 -0
  49. package/dist/middleware/sso.d.ts +9 -0
  50. package/dist/middleware/sso.d.ts.map +1 -0
  51. package/dist/middleware/sso.js +47 -0
  52. package/dist/middleware/sso.js.map +1 -0
  53. package/dist/routes/audit.d.ts +4 -0
  54. package/dist/routes/audit.d.ts.map +1 -0
  55. package/dist/routes/audit.js +123 -0
  56. package/dist/routes/audit.js.map +1 -0
  57. package/dist/routes/auth.d.ts +10 -0
  58. package/dist/routes/auth.d.ts.map +1 -0
  59. package/dist/routes/auth.js +105 -0
  60. package/dist/routes/auth.js.map +1 -0
  61. package/dist/routes/config.d.ts.map +1 -1
  62. package/dist/routes/config.js +4 -2
  63. package/dist/routes/config.js.map +1 -1
  64. package/dist/routes/coordination.d.ts.map +1 -1
  65. package/dist/routes/coordination.js +5 -3
  66. package/dist/routes/coordination.js.map +1 -1
  67. package/dist/routes/cost.d.ts +10 -0
  68. package/dist/routes/cost.d.ts.map +1 -0
  69. package/dist/routes/cost.js +104 -0
  70. package/dist/routes/cost.js.map +1 -0
  71. package/dist/routes/errors.d.ts.map +1 -1
  72. package/dist/routes/errors.js +4 -2
  73. package/dist/routes/errors.js.map +1 -1
  74. package/dist/routes/runs.d.ts +9 -1
  75. package/dist/routes/runs.d.ts.map +1 -1
  76. package/dist/routes/runs.js +82 -9
  77. package/dist/routes/runs.js.map +1 -1
  78. package/dist/routes/tokens.d.ts.map +1 -1
  79. package/dist/routes/tokens.js +4 -2
  80. package/dist/routes/tokens.js.map +1 -1
  81. package/dist/routes/users.d.ts +8 -0
  82. package/dist/routes/users.d.ts.map +1 -0
  83. package/dist/routes/users.js +64 -0
  84. package/dist/routes/users.js.map +1 -0
  85. package/dist/server.d.ts +13 -1
  86. package/dist/server.d.ts.map +1 -1
  87. package/dist/server.js +65 -7
  88. package/dist/server.js.map +1 -1
  89. package/dist/static/style.css +648 -0
  90. package/dist/views/audit.d.ts +29 -0
  91. package/dist/views/audit.d.ts.map +1 -0
  92. package/dist/views/audit.js +144 -0
  93. package/dist/views/audit.js.map +1 -0
  94. package/dist/views/cost.d.ts +28 -0
  95. package/dist/views/cost.d.ts.map +1 -0
  96. package/dist/views/cost.js +173 -0
  97. package/dist/views/cost.js.map +1 -0
  98. package/dist/views/forbidden.d.ts +7 -0
  99. package/dist/views/forbidden.d.ts.map +1 -0
  100. package/dist/views/forbidden.js +18 -0
  101. package/dist/views/forbidden.js.map +1 -0
  102. package/dist/views/layout.d.ts +11 -1
  103. package/dist/views/layout.d.ts.map +1 -1
  104. package/dist/views/layout.js +12 -18
  105. package/dist/views/layout.js.map +1 -1
  106. package/dist/views/run-detail.d.ts +1 -1
  107. package/dist/views/run-detail.d.ts.map +1 -1
  108. package/dist/views/run-detail.js +6 -1
  109. package/dist/views/run-detail.js.map +1 -1
  110. package/dist/views/users.d.ts +16 -0
  111. package/dist/views/users.d.ts.map +1 -0
  112. package/dist/views/users.js +44 -0
  113. package/dist/views/users.js.map +1 -0
  114. package/package.json +7 -5
  115. 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
+ &nbsp;|&nbsp;
70
+ <strong>Timestamp:</strong> ${escapeHtml(e.timestamp.toISOString())}
71
+ &nbsp;|&nbsp;
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
+ &middot; <strong>${fmtHours(summary.timeSavedHours)}h saved</strong>
121
+ &middot; <strong>${fmtNumber(summary.runs)} runs</strong></p>
122
+ <p>${fmtNumber(summary.inputTokens + summary.outputTokens)} tokens
123
+ &middot; ${fmtDollars(summary.dollars)} cost</p>
124
+ <p><strong>ROI:</strong> ${fmtHours(summary.timeSavedHours)}h &times; ${fmtDollars(hourlyRate)}/h
125
+ = ${fmtDollars(value)} value &divide; ${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 &times; hourly rate) &divide; 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,7 @@
1
+ export declare function renderForbidden(args: {
2
+ email: string;
3
+ role: string;
4
+ action: string;
5
+ basePath: string;
6
+ }): string;
7
+ //# sourceMappingURL=forbidden.d.ts.map
@@ -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, "&amp;")
4
+ .replace(/</g, "&lt;")
5
+ .replace(/>/g, "&gt;")
6
+ .replace(/"/g, "&quot;")
7
+ .replace(/'/g, "&#39;");
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"}
@@ -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
- export declare function layout(title: string, content: string, basePath?: string): string;
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,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"}
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"}
@@ -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>
@@ -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;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"}
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,GAChB,MAAM,CA4FR"}
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"}
@@ -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":"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"}
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"}