@merittdev/horus-lens 0.0.3 → 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.
package/dist/dashboard.js DELETED
@@ -1,2584 +0,0 @@
1
- import "./chunk-PZ5AY32C.js";
2
-
3
- // ../dashboard/dist/index.js
4
- import { createContext, useContext } from "react";
5
- import { jsx } from "react/jsx-runtime";
6
- import { createContext as createContext2, useContext as useContext2, useEffect as useEffect7, useMemo, useRef as useRef4, useState as useState8 } from "react";
7
-
8
- // ../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
9
- function r(e) {
10
- var t, f, n = "";
11
- if ("string" == typeof e || "number" == typeof e) n += e;
12
- else if ("object" == typeof e) if (Array.isArray(e)) {
13
- var o = e.length;
14
- for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
15
- } else for (f in e) e[f] && (n && (n += " "), n += f);
16
- return n;
17
- }
18
- function clsx() {
19
- for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
20
- return n;
21
- }
22
- var clsx_default = clsx;
23
-
24
- // ../dashboard/dist/index.js
25
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
26
- import { jsx as jsx3 } from "react/jsx-runtime";
27
- import { useCallback, useEffect, useState } from "react";
28
- import { jsx as jsx4 } from "react/jsx-runtime";
29
- import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
30
- import { useId, useRef as useRef3, useState as useState4 } from "react";
31
- import { useCallback as useCallback2, useEffect as useEffect2, useRef, useState as useState2 } from "react";
32
- import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
33
- import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
34
- import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
35
- import { useEffect as useEffect5, useState as useState6 } from "react";
36
- import { useEffect as useEffect4, useState as useState5 } from "react";
37
- import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
38
- import { jsx as jsx9 } from "react/jsx-runtime";
39
- import { useEffect as useEffect6, useState as useState7 } from "react";
40
- import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
41
- import { Fragment as Fragment5, jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
42
- import { useEffect as useEffect8, useState as useState9 } from "react";
43
- import { Fragment as Fragment6, jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
44
- var LensApiError = class extends Error {
45
- constructor(status, code, message) {
46
- super(message);
47
- this.status = status;
48
- this.code = code;
49
- this.name = "LensApiError";
50
- }
51
- status;
52
- code;
53
- };
54
- function joinUrl(baseUrl, path) {
55
- return `${baseUrl.replace(/\/+$/, "")}${path}`;
56
- }
57
- function createLensApi(config) {
58
- async function request(path, init) {
59
- const token = await config.getToken();
60
- const project = config.project?.();
61
- const res = await fetch(joinUrl(config.baseUrl, path), {
62
- ...init,
63
- headers: {
64
- Accept: "application/json",
65
- ...token ? config.tokenHeader && config.tokenHeader.toLowerCase() !== "authorization" ? { [config.tokenHeader]: token } : { Authorization: `Bearer ${token}` } : {},
66
- ...project ? { "x-lens-project": project } : {},
67
- ...init?.headers ?? {}
68
- }
69
- });
70
- if (!res.ok) {
71
- let code = String(res.status);
72
- let message = res.statusText || "Request failed";
73
- try {
74
- const body = await res.json();
75
- if (body?.error) code = body.error;
76
- if (body?.message) message = body.message;
77
- } catch {
78
- }
79
- throw new LensApiError(res.status, code, message);
80
- }
81
- if (res.status === 204 || res.headers.get("content-length") === "0") {
82
- return void 0;
83
- }
84
- const text = await res.text();
85
- return text ? JSON.parse(text) : void 0;
86
- }
87
- return {
88
- async listReports(query) {
89
- const params = new URLSearchParams();
90
- if (query?.status) params.set("status", query.status);
91
- if (query?.q) params.set("q", query.q);
92
- if (query?.limit != null) params.set("limit", String(query.limit));
93
- if (query?.cursor) params.set("cursor", query.cursor);
94
- const qs = params.toString();
95
- return request(`/v1/reports${qs ? `?${qs}` : ""}`);
96
- },
97
- async getReport(id) {
98
- return request(`/v1/reports/${encodeURIComponent(id)}`);
99
- },
100
- async getAssetUrl(id, kind) {
101
- const { url } = await request(
102
- `/v1/reports/${encodeURIComponent(id)}/assets/${encodeURIComponent(kind)}`
103
- );
104
- return url;
105
- },
106
- async getAssetJson(id, kind) {
107
- return request(`/v1/reports/${encodeURIComponent(id)}/assets/${kind}/raw`);
108
- },
109
- async listIntegrations() {
110
- const { integrations } = await request("/v1/integrations");
111
- return integrations;
112
- },
113
- async connectIntegration(provider, opts) {
114
- return request(
115
- `/v1/integrations/${encodeURIComponent(provider)}/connect`,
116
- {
117
- method: "POST",
118
- headers: { "Content-Type": "application/json" },
119
- body: JSON.stringify(opts?.returnUrl != null ? { returnUrl: opts.returnUrl } : {})
120
- }
121
- );
122
- },
123
- async completeIntegrationDev(provider, account) {
124
- return request(
125
- `/v1/integrations/${encodeURIComponent(provider)}/callback`,
126
- {
127
- method: "POST",
128
- headers: { "Content-Type": "application/json" },
129
- body: JSON.stringify(account != null ? { account } : {})
130
- }
131
- );
132
- },
133
- async activateIntegration(provider) {
134
- await request(`/v1/integrations/${encodeURIComponent(provider)}/activate`, {
135
- method: "POST"
136
- });
137
- return this.listIntegrations();
138
- },
139
- async disconnectIntegration(provider) {
140
- await request(`/v1/integrations/${encodeURIComponent(provider)}`, {
141
- method: "DELETE"
142
- });
143
- return this.listIntegrations();
144
- },
145
- async updateIntegrationConfig(provider, config2) {
146
- const res = await request(
147
- `/v1/integrations/${encodeURIComponent(provider)}/config`,
148
- {
149
- method: "PATCH",
150
- headers: { "Content-Type": "application/json" },
151
- body: JSON.stringify(config2)
152
- }
153
- );
154
- if (Array.isArray(res)) return res;
155
- if (res && Array.isArray(res.integrations)) return res.integrations;
156
- return this.listIntegrations();
157
- },
158
- async getIntegrationOptions(provider) {
159
- return request(
160
- `/v1/integrations/${encodeURIComponent(provider)}/options`
161
- );
162
- },
163
- async createProject(body) {
164
- return request("/v1/projects", {
165
- method: "POST",
166
- headers: { "Content-Type": "application/json" },
167
- body: JSON.stringify(body)
168
- });
169
- },
170
- async getProject() {
171
- return request("/v1/project");
172
- },
173
- async updateProject(body) {
174
- return request("/v1/project", {
175
- method: "PATCH",
176
- headers: { "Content-Type": "application/json" },
177
- body: JSON.stringify(body)
178
- });
179
- },
180
- async setS3Config(body) {
181
- return request("/v1/project/s3", {
182
- method: "PUT",
183
- headers: { "Content-Type": "application/json" },
184
- body: JSON.stringify(body)
185
- });
186
- },
187
- async clearS3Config() {
188
- return request("/v1/project/s3", { method: "DELETE" });
189
- },
190
- async getStorageStatus() {
191
- return request("/v1/storage");
192
- }
193
- };
194
- }
195
- var LensApiContext = createContext(null);
196
- function LensDashboardProvider(props) {
197
- const provided = /* @__PURE__ */ jsx(LensApiContext.Provider, { value: props.api, children: props.children });
198
- if (!props.theme) return provided;
199
- return /* @__PURE__ */ jsx("div", { className: `lens-db-theme-scope lens-db--${props.theme}`, children: provided });
200
- }
201
- function useLensApi() {
202
- const api = useContext(LensApiContext);
203
- if (!api) {
204
- throw new Error(
205
- "[lens] No LensApi found in context. Wrap your dashboard in <LensDashboardProvider api={createLensApi(...)}>."
206
- );
207
- }
208
- return api;
209
- }
210
- function base({ size = 16, ...rest }) {
211
- return {
212
- width: size,
213
- height: size,
214
- viewBox: "0 0 24 24",
215
- fill: "none",
216
- stroke: "currentColor",
217
- strokeWidth: 1.5,
218
- strokeLinecap: "round",
219
- strokeLinejoin: "round",
220
- "aria-hidden": true,
221
- focusable: false,
222
- ...rest
223
- };
224
- }
225
- function IconPlay(props) {
226
- return /* @__PURE__ */ jsx2("svg", { ...base(props), children: /* @__PURE__ */ jsx2("path", { d: "M8 5.5v13l11-6.5-11-6.5Z", fill: "currentColor", stroke: "none" }) });
227
- }
228
- function IconPause(props) {
229
- return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
230
- /* @__PURE__ */ jsx2("rect", { x: "7", y: "5", width: "3.5", height: "14", rx: "1", fill: "currentColor", stroke: "none" }),
231
- /* @__PURE__ */ jsx2("rect", { x: "13.5", y: "5", width: "3.5", height: "14", rx: "1", fill: "currentColor", stroke: "none" })
232
- ] });
233
- }
234
- function IconClose(props) {
235
- return /* @__PURE__ */ jsx2("svg", { ...base(props), children: /* @__PURE__ */ jsx2("path", { d: "M18 6 6 18M6 6l12 12" }) });
236
- }
237
- function IconChevronLeft(props) {
238
- return /* @__PURE__ */ jsx2("svg", { ...base(props), children: /* @__PURE__ */ jsx2("path", { d: "M15 6l-6 6 6 6" }) });
239
- }
240
- function IconChevronRight(props) {
241
- return /* @__PURE__ */ jsx2("svg", { ...base(props), children: /* @__PURE__ */ jsx2("path", { d: "M9 6l6 6-6 6" }) });
242
- }
243
- function IconExternal(props) {
244
- return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
245
- /* @__PURE__ */ jsx2("path", { d: "M14 5h5v5M19 5l-8 8" }),
246
- /* @__PURE__ */ jsx2("path", { d: "M18 13.5V18a1.5 1.5 0 0 1-1.5 1.5H6A1.5 1.5 0 0 1 4.5 18V7.5A1.5 1.5 0 0 1 6 6h4.5" })
247
- ] });
248
- }
249
- function IconCopy(props) {
250
- return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
251
- /* @__PURE__ */ jsx2("rect", { x: "9", y: "9", width: "11", height: "11", rx: "2" }),
252
- /* @__PURE__ */ jsx2("path", { d: "M5 15V6a1 1 0 0 1 1-1h9" })
253
- ] });
254
- }
255
- function IconCheck(props) {
256
- return /* @__PURE__ */ jsx2("svg", { ...base(props), children: /* @__PURE__ */ jsx2("path", { d: "M20 6 9 17l-5-5" }) });
257
- }
258
- function IconCheckCircle(props) {
259
- return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
260
- /* @__PURE__ */ jsx2("circle", { cx: "12", cy: "12", r: "9" }),
261
- /* @__PURE__ */ jsx2("path", { d: "m8.5 12 2.5 2.5 4.5-5" })
262
- ] });
263
- }
264
- function IconAlertTriangle(props) {
265
- return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
266
- /* @__PURE__ */ jsx2("path", { d: "M12 4.5 21 19.5H3L12 4.5Z" }),
267
- /* @__PURE__ */ jsx2("path", { d: "M12 10v4.5" }),
268
- /* @__PURE__ */ jsx2("path", { d: "M12 17.25h.01" })
269
- ] });
270
- }
271
- function IconInbox(props) {
272
- return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
273
- /* @__PURE__ */ jsx2("path", { d: "M4 13h4l1.5 2.5h5L16 13h4" }),
274
- /* @__PURE__ */ jsx2("path", { d: "M6.5 5h11l3 8v4.5A1.5 1.5 0 0 1 19 19H5a1.5 1.5 0 0 1-1.5-1.5V13l3-8Z" })
275
- ] });
276
- }
277
- function IconBug(props) {
278
- return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
279
- /* @__PURE__ */ jsx2("rect", { x: "8", y: "7", width: "8", height: "12", rx: "4" }),
280
- /* @__PURE__ */ jsx2("path", { d: "M9 5a3 3 0 0 1 6 0" }),
281
- /* @__PURE__ */ jsx2("path", { d: "M12 8.5v10" }),
282
- /* @__PURE__ */ jsx2("path", { d: "M8 11H4.5M8 15H4M8 8 5.5 6M16 11h3.5M16 15h4M16 8l2.5-2" })
283
- ] });
284
- }
285
- function IconPlug(props) {
286
- return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
287
- /* @__PURE__ */ jsx2("path", { d: "M9 3v5M15 3v5" }),
288
- /* @__PURE__ */ jsx2("path", { d: "M6.5 8h11v1.5a5.5 5.5 0 0 1-11 0V8Z" }),
289
- /* @__PURE__ */ jsx2("path", { d: "M12 15v6" })
290
- ] });
291
- }
292
- function IconRocket(props) {
293
- return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
294
- /* @__PURE__ */ jsx2("path", { d: "M12 3c2.6 1.7 4 4.6 4 7.5l-1.8 2.5H9.8L8 10.5C8 7.6 9.4 4.7 12 3Z" }),
295
- /* @__PURE__ */ jsx2("circle", { cx: "12", cy: "9", r: "1.4" }),
296
- /* @__PURE__ */ jsx2("path", { d: "M9.8 13l-2 2.2.2 3 2.4-1.4M14.2 13l2 2.2-.2 3-2.4-1.4" })
297
- ] });
298
- }
299
- function brand({ size = 16, ...rest }) {
300
- return {
301
- width: size,
302
- height: size,
303
- viewBox: "0 0 24 24",
304
- fill: "currentColor",
305
- "aria-hidden": true,
306
- focusable: false,
307
- ...rest
308
- };
309
- }
310
- function IconGitHub(props) {
311
- return /* @__PURE__ */ jsx2("svg", { ...brand(props), children: /* @__PURE__ */ jsx2("path", { d: "M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" }) });
312
- }
313
- function IconLinear(props) {
314
- return /* @__PURE__ */ jsx2("svg", { ...brand(props), children: /* @__PURE__ */ jsx2("path", { d: "M2.886 4.18A11.982 11.982 0 0 1 11.99 0C18.624 0 24 5.376 24 12.009c0 3.64-1.62 6.903-4.18 9.105L2.887 4.18ZM1.817 5.626l16.556 16.556c-.524.33-1.075.62-1.65.866L.951 7.277c.247-.575.537-1.126.866-1.65ZM.322 9.163l14.515 14.515c-.71.172-1.443.282-2.195.322L0 11.358a12 12 0 0 1 .322-2.195Zm-.17 4.862 9.823 9.824a12.02 12.02 0 0 1-9.824-9.824Z" }) });
315
- }
316
- function IconJira(props) {
317
- return /* @__PURE__ */ jsx2("svg", { ...brand(props), children: /* @__PURE__ */ jsx2("path", { d: "M11.571 11.513H0a5.218 5.218 0 0 0 5.232 5.215h2.13v2.057A5.215 5.215 0 0 0 12.575 24V12.518a1.005 1.005 0 0 0-1.005-1.005zm5.723-5.756H5.736a5.215 5.215 0 0 0 5.215 5.214h2.129v2.058a5.218 5.218 0 0 0 5.215 5.214V6.758a1.001 1.001 0 0 0-1.001-1.001zM23.013 0H11.455a5.215 5.215 0 0 0 5.215 5.215h2.129v2.057A5.215 5.215 0 0 0 24 12.483V1.005A1.001 1.001 0 0 0 23.013 0Z" }) });
318
- }
319
- var MARKUP_RULES = [
320
- { cls: "com", re: /<!--[\s\S]*?-->|\/\*[\s\S]*?\*\/|\/\/[^\n]*/y },
321
- { cls: "str", re: /"(?:[^"\\\n]|\\.)*"|'(?:[^'\\\n]|\\.)*'|`(?:[^`\\]|\\.)*`/y },
322
- { cls: "tag", re: /<\/?[a-zA-Z][\w.-]*|\/>|>/y },
323
- {
324
- cls: "kw",
325
- re: /\b(?:import|from|export|function|return|const|let|var|async|await|new|window|default)\b/y
326
- },
327
- { cls: "attr", re: /\b[a-zA-Z_][\w-]*(?==)/y },
328
- { cls: "num", re: /\b\d+(?:\.\d+)?\b/y }
329
- ];
330
- var ENV_RULES = [
331
- { cls: "com", re: /#[^\n]*/y },
332
- { cls: "attr", re: /^[A-Z][A-Z0-9_]*(?==)/my },
333
- { cls: "str", re: /(?<==)[^\n]+/y }
334
- ];
335
- function highlightCode(code, lang) {
336
- const rules = lang === "env" ? ENV_RULES : MARKUP_RULES;
337
- const out = [];
338
- let pos = 0;
339
- let plainStart = 0;
340
- let key = 0;
341
- const flushPlain = (until) => {
342
- if (until > plainStart) out.push(code.slice(plainStart, until));
343
- };
344
- while (pos < code.length) {
345
- let matched = false;
346
- for (const rule of rules) {
347
- rule.re.lastIndex = pos;
348
- const m = rule.re.exec(code);
349
- if (m && m[0].length > 0) {
350
- flushPlain(pos);
351
- out.push(
352
- /* @__PURE__ */ jsx3("span", { className: `lens-db__tok-${rule.cls}`, children: m[0] }, key++)
353
- );
354
- pos += m[0].length;
355
- plainStart = pos;
356
- matched = true;
357
- break;
358
- }
359
- }
360
- if (!matched) pos += 1;
361
- }
362
- flushPlain(code.length);
363
- return out;
364
- }
365
- function relativeTime(iso, now = Date.now()) {
366
- if (!iso) return "\u2014";
367
- const then = new Date(iso).getTime();
368
- if (Number.isNaN(then)) return "\u2014";
369
- const diff = now - then;
370
- const sec = Math.round(diff / 1e3);
371
- if (sec < 5) return "just now";
372
- if (sec < 60) return `${sec}s ago`;
373
- const min = Math.round(sec / 60);
374
- if (min < 60) return `${min}m ago`;
375
- const hr = Math.round(min / 60);
376
- if (hr < 24) return `${hr}h ago`;
377
- const day = Math.round(hr / 24);
378
- if (day < 7) return `${day}d ago`;
379
- const wk = Math.round(day / 7);
380
- if (wk < 5) return `${wk}w ago`;
381
- const mo = Math.round(day / 30);
382
- if (mo < 12) return `${mo}mo ago`;
383
- const yr = Math.round(day / 365);
384
- return `${yr}y ago`;
385
- }
386
- function formatTimestamp(iso) {
387
- if (!iso) return "\u2014";
388
- const d = new Date(iso);
389
- if (Number.isNaN(d.getTime())) return "\u2014";
390
- return d.toLocaleString();
391
- }
392
- function truncate(value, max = 80) {
393
- if (value.length <= max) return value;
394
- return `${value.slice(0, max - 1).trimEnd()}\u2026`;
395
- }
396
- function urlPath(raw) {
397
- if (!raw) return "\u2014";
398
- try {
399
- const u = new URL(raw);
400
- return `${u.pathname}${u.search}` || "/";
401
- } catch {
402
- return raw;
403
- }
404
- }
405
- function formatDuration(ms) {
406
- if (ms == null) return "\u2014";
407
- if (ms < 1e3) return `${Math.round(ms)}ms`;
408
- return `${(ms / 1e3).toFixed(2)}s`;
409
- }
410
- var LABELS = {
411
- initiated: "Initiated",
412
- submitted: "Submitted",
413
- triaged: "Triaged",
414
- linked: "Linked",
415
- resolved: "Resolved"
416
- };
417
- function StatusBadge({ status }) {
418
- return /* @__PURE__ */ jsx4("span", { className: clsx_default("lens-db__badge", `lens-db__badge--${status}`), children: LABELS[status] });
419
- }
420
- var SEARCH_DEBOUNCE_MS = 350;
421
- function ReportsTable(props) {
422
- const { onSelect, status, pageSize = 20, className } = props;
423
- const api = useLensApi();
424
- const [reports, setReports] = useState([]);
425
- const [nextCursor, setNextCursor] = useState(null);
426
- const [loading, setLoading] = useState(true);
427
- const [error, setError] = useState(void 0);
428
- const [input, setInput] = useState("");
429
- const [query, setQuery] = useState("");
430
- const [pageCursor, setPageCursor] = useState(void 0);
431
- const [history, setHistory] = useState([]);
432
- const load = useCallback(
433
- async (cursor, q) => {
434
- setLoading(true);
435
- setError(void 0);
436
- try {
437
- const result = await api.listReports({
438
- status,
439
- q: q || void 0,
440
- limit: pageSize,
441
- cursor
442
- });
443
- setReports(result.reports);
444
- setNextCursor(result.nextCursor);
445
- } catch (err) {
446
- setError(err instanceof Error ? err : new Error(String(err)));
447
- } finally {
448
- setLoading(false);
449
- }
450
- },
451
- [api, status, pageSize]
452
- );
453
- useEffect(() => {
454
- const timer = setTimeout(() => setQuery(input.trim()), SEARCH_DEBOUNCE_MS);
455
- return () => clearTimeout(timer);
456
- }, [input]);
457
- useEffect(() => {
458
- setHistory([]);
459
- setPageCursor(void 0);
460
- void load(void 0, query);
461
- }, [query, load]);
462
- function goNext() {
463
- if (!nextCursor) return;
464
- setHistory((h) => [...h, pageCursor]);
465
- setPageCursor(nextCursor);
466
- void load(nextCursor, query);
467
- }
468
- function goPrev() {
469
- if (history.length === 0) return;
470
- const prev = history[history.length - 1];
471
- setHistory((h) => h.slice(0, -1));
472
- setPageCursor(prev);
473
- void load(prev, query);
474
- }
475
- const canPrev = history.length > 0;
476
- const canNext = nextCursor !== null;
477
- const isOnlyPage = !canPrev && !canNext;
478
- const contextText = isOnlyPage ? `Showing ${reports.length}` : `${reports.length} on this page`;
479
- return /* @__PURE__ */ jsxs2("div", { className: clsx_default("lens-db", "lens-db__table-wrap", className), children: [
480
- /* @__PURE__ */ jsxs2("div", { className: "lens-db__table-toolbar", children: [
481
- /* @__PURE__ */ jsx5(
482
- "input",
483
- {
484
- type: "search",
485
- className: "lens-db__input lens-db__table-search",
486
- value: input,
487
- placeholder: "Search comment or URL\u2026",
488
- "aria-label": "Search reports by comment or URL",
489
- onChange: (e) => setInput(e.target.value),
490
- onKeyDown: (e) => {
491
- if (e.key === "Escape") {
492
- e.preventDefault();
493
- setInput("");
494
- }
495
- }
496
- }
497
- ),
498
- !loading && !error && reports.length > 0 && /* @__PURE__ */ jsx5("span", { className: "lens-db__table-context lens-db__muted", children: contextText })
499
- ] }),
500
- /* @__PURE__ */ jsxs2("table", { className: "lens-db__table", children: [
501
- /* @__PURE__ */ jsx5("thead", { children: /* @__PURE__ */ jsxs2("tr", { children: [
502
- /* @__PURE__ */ jsx5("th", { children: "Created" }),
503
- /* @__PURE__ */ jsx5("th", { children: "Comment" }),
504
- /* @__PURE__ */ jsx5("th", { children: "Path" }),
505
- /* @__PURE__ */ jsx5("th", { children: "Status" }),
506
- /* @__PURE__ */ jsx5("th", { className: "lens-db__num", children: "Errors" }),
507
- /* @__PURE__ */ jsx5("th", { className: "lens-db__center", children: "Replay" }),
508
- /* @__PURE__ */ jsx5("th", { children: "Issue" })
509
- ] }) }),
510
- /* @__PURE__ */ jsx5("tbody", { children: loading ? /* @__PURE__ */ jsx5(SkeletonRows, { rows: 6 }) : error ? /* @__PURE__ */ jsx5("tr", { children: /* @__PURE__ */ jsxs2("td", { colSpan: 7, className: "lens-db__error", children: [
511
- "Failed to load reports: ",
512
- error.message,
513
- " ",
514
- /* @__PURE__ */ jsx5(
515
- "button",
516
- {
517
- type: "button",
518
- className: "lens-db__link-btn",
519
- onClick: () => void load(pageCursor, query),
520
- children: "Retry"
521
- }
522
- )
523
- ] }) }) : reports.length === 0 ? /* @__PURE__ */ jsx5("tr", { children: /* @__PURE__ */ jsxs2("td", { colSpan: 7, className: "lens-db__empty", children: [
524
- /* @__PURE__ */ jsx5("span", { className: "lens-db__empty-icon", children: /* @__PURE__ */ jsx5(IconInbox, { size: 32 }) }),
525
- query ? /* @__PURE__ */ jsxs2(Fragment, { children: [
526
- /* @__PURE__ */ jsx5("div", { className: "lens-db__empty-title", children: "No matching reports" }),
527
- /* @__PURE__ */ jsxs2("div", { className: "lens-db__empty-hint", children: [
528
- "No reports match \u201C",
529
- query,
530
- "\u201D. Try a different search."
531
- ] })
532
- ] }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
533
- /* @__PURE__ */ jsx5("div", { className: "lens-db__empty-title", children: "No reports yet" }),
534
- /* @__PURE__ */ jsxs2("div", { className: "lens-db__empty-hint", children: [
535
- "Visit your app with ",
536
- /* @__PURE__ */ jsx5("code", { children: "?lens=true" }),
537
- " and submit one."
538
- ] })
539
- ] })
540
- ] }) }) : reports.map((r2) => /* @__PURE__ */ jsxs2(
541
- "tr",
542
- {
543
- className: clsx_default("lens-db__row", onSelect && "lens-db__row--clickable"),
544
- onClick: onSelect ? () => onSelect(r2.id) : void 0,
545
- tabIndex: onSelect ? 0 : void 0,
546
- onKeyDown: onSelect ? (e) => {
547
- if (e.key === "Enter" || e.key === " ") {
548
- e.preventDefault();
549
- onSelect(r2.id);
550
- }
551
- } : void 0,
552
- children: [
553
- /* @__PURE__ */ jsx5("td", { className: "lens-db__muted lens-db__tnum", title: r2.createdAt, children: relativeTime(r2.createdAt) }),
554
- /* @__PURE__ */ jsx5("td", { children: r2.comment ? /* @__PURE__ */ jsx5("span", { className: "lens-db__cell-comment", title: r2.comment, children: r2.comment }) : /* @__PURE__ */ jsx5("span", { className: "lens-db__muted", children: "\u2014" }) }),
555
- /* @__PURE__ */ jsx5("td", { className: "lens-db__mono lens-db__muted", title: r2.url, children: urlPath(r2.url) }),
556
- /* @__PURE__ */ jsx5("td", { children: /* @__PURE__ */ jsx5(StatusBadge, { status: r2.status }) }),
557
- /* @__PURE__ */ jsx5("td", { className: "lens-db__num", children: r2.errorCount > 0 ? /* @__PURE__ */ jsx5("span", { className: "lens-db__badge lens-db__badge--danger", children: r2.errorCount }) : /* @__PURE__ */ jsx5("span", { className: "lens-db__muted", children: "0" }) }),
558
- /* @__PURE__ */ jsx5("td", { className: "lens-db__center", children: r2.hasReplay ? /* @__PURE__ */ jsx5("span", { className: "lens-db__play", title: "Session replay captured", "aria-label": "Has replay", children: /* @__PURE__ */ jsx5(IconPlay, { size: 14 }) }) : /* @__PURE__ */ jsx5("span", { className: "lens-db__muted", "aria-label": "No replay", children: "\u2014" }) }),
559
- /* @__PURE__ */ jsx5("td", { children: r2.externalIssue ? /* @__PURE__ */ jsx5(
560
- "a",
561
- {
562
- className: "lens-db__link",
563
- href: r2.externalIssue.url,
564
- target: "_blank",
565
- rel: "noreferrer",
566
- onClick: (e) => e.stopPropagation(),
567
- children: r2.externalIssue.key
568
- }
569
- ) : /* @__PURE__ */ jsx5("span", { className: "lens-db__muted", children: "\u2014" }) })
570
- ]
571
- },
572
- r2.id
573
- )) })
574
- ] }),
575
- !error && (canPrev || canNext) && /* @__PURE__ */ jsxs2("div", { className: "lens-db__table-footer lens-db__pager", children: [
576
- /* @__PURE__ */ jsxs2(
577
- "button",
578
- {
579
- type: "button",
580
- className: "lens-db__btn lens-db__btn--ghost",
581
- disabled: !canPrev || loading,
582
- onClick: goPrev,
583
- children: [
584
- /* @__PURE__ */ jsx5(IconChevronLeft, { size: 16 }),
585
- "Prev"
586
- ]
587
- }
588
- ),
589
- /* @__PURE__ */ jsxs2(
590
- "button",
591
- {
592
- type: "button",
593
- className: "lens-db__btn lens-db__btn--ghost",
594
- disabled: !canNext || loading,
595
- onClick: goNext,
596
- children: [
597
- "Next",
598
- /* @__PURE__ */ jsx5(IconChevronRight, { size: 16 })
599
- ]
600
- }
601
- )
602
- ] })
603
- ] });
604
- }
605
- function SkeletonRows({ rows }) {
606
- return /* @__PURE__ */ jsx5(Fragment, { children: Array.from({ length: rows }).map((_, i) => /* @__PURE__ */ jsx5("tr", { className: "lens-db__row lens-db__row--skeleton", "aria-hidden": true, children: Array.from({ length: 7 }).map((__, j) => /* @__PURE__ */ jsx5("td", { children: /* @__PURE__ */ jsx5("span", { className: "lens-db__skeleton" }) }, j)) }, i)) });
607
- }
608
- function useAsync(fn, deps) {
609
- const [data, setData] = useState2(void 0);
610
- const [error, setError] = useState2(void 0);
611
- const [loading, setLoading] = useState2(true);
612
- const [nonce, setNonce] = useState2(0);
613
- const runId = useRef(0);
614
- const reload = useCallback2(() => setNonce((n) => n + 1), []);
615
- useEffect2(() => {
616
- const id = ++runId.current;
617
- let active = true;
618
- setLoading(true);
619
- setError(void 0);
620
- fn().then((result) => {
621
- if (active && id === runId.current) {
622
- setData(result);
623
- setLoading(false);
624
- }
625
- }).catch((err) => {
626
- if (active && id === runId.current) {
627
- setError(err instanceof Error ? err : new Error(String(err)));
628
- setLoading(false);
629
- }
630
- });
631
- return () => {
632
- active = false;
633
- };
634
- }, [...deps, nonce]);
635
- return { data, error, loading, reload };
636
- }
637
- var SPEEDS = [1, 2, 4];
638
- var FALLBACK_W = 16;
639
- var FALLBACK_H = 10;
640
- function formatTime(ms) {
641
- const total = Math.max(0, Math.floor(ms / 1e3));
642
- const m = Math.floor(total / 60);
643
- const s = total % 60;
644
- return `${m}:${s.toString().padStart(2, "0")}`;
645
- }
646
- function readViewport(events) {
647
- for (const e of events) {
648
- if (e && typeof e === "object" && e.type === 4) {
649
- const data = e.data;
650
- if (data && typeof data.width === "number" && typeof data.height === "number" && data.width > 0 && data.height > 0) {
651
- return { width: data.width, height: data.height };
652
- }
653
- }
654
- }
655
- return null;
656
- }
657
- function ReplayPlayer(props) {
658
- const { reportId, className } = props;
659
- const api = useLensApi();
660
- const frameRef = useRef2(null);
661
- const replayerRef = useRef2(null);
662
- const rafRef = useRef2(null);
663
- const totalRef = useRef2(0);
664
- const finishedRef = useRef2(false);
665
- const playingRef = useRef2(false);
666
- const [status, setStatus] = useState3("loading");
667
- const [message, setMessage] = useState3(void 0);
668
- const [playing, setPlaying] = useState3(false);
669
- const [speed, setSpeed] = useState3(1);
670
- const [skipInactive, setSkipInactive] = useState3(true);
671
- const [elapsed, setElapsed] = useState3(0);
672
- const [total, setTotal] = useState3(0);
673
- const stopRaf = useCallback3(() => {
674
- if (rafRef.current != null) {
675
- cancelAnimationFrame(rafRef.current);
676
- rafRef.current = null;
677
- }
678
- }, []);
679
- const safeCurrentTime = useCallback3(() => {
680
- const r2 = replayerRef.current;
681
- if (!r2) return 0;
682
- const t = r2.getCurrentTime();
683
- if (!Number.isFinite(t) || t < 0) return 0;
684
- return totalRef.current > 0 ? Math.min(t, totalRef.current) : t;
685
- }, []);
686
- const tick = useCallback3(() => {
687
- if (!replayerRef.current) return;
688
- setElapsed(safeCurrentTime());
689
- rafRef.current = requestAnimationFrame(tick);
690
- }, [safeCurrentTime]);
691
- const startRaf = useCallback3(() => {
692
- stopRaf();
693
- rafRef.current = requestAnimationFrame(tick);
694
- }, [stopRaf, tick]);
695
- const togglePlay = useCallback3(() => {
696
- const r2 = replayerRef.current;
697
- if (!r2) return;
698
- if (playingRef.current) {
699
- r2.pause();
700
- playingRef.current = false;
701
- setPlaying(false);
702
- stopRaf();
703
- } else {
704
- const from = finishedRef.current ? 0 : safeCurrentTime();
705
- finishedRef.current = false;
706
- r2.play(from);
707
- playingRef.current = true;
708
- setPlaying(true);
709
- startRaf();
710
- }
711
- }, [safeCurrentTime, startRaf, stopRaf]);
712
- const seekTo = useCallback3((offsetMs) => {
713
- const r2 = replayerRef.current;
714
- if (!r2) return;
715
- const clamped = totalRef.current > 0 ? Math.max(0, Math.min(offsetMs, totalRef.current)) : Math.max(0, offsetMs);
716
- finishedRef.current = false;
717
- if (playingRef.current) {
718
- r2.play(clamped);
719
- } else {
720
- r2.pause(clamped);
721
- }
722
- setElapsed(clamped);
723
- }, []);
724
- const changeSpeed = useCallback3((next) => {
725
- setSpeed(next);
726
- replayerRef.current?.setConfig({ speed: next });
727
- }, []);
728
- const toggleSkipInactive = useCallback3(() => {
729
- setSkipInactive((prev) => {
730
- const next = !prev;
731
- replayerRef.current?.setConfig({ skipInactive: next });
732
- return next;
733
- });
734
- }, []);
735
- const trackRef = useRef2(null);
736
- const offsetFromPointer = useCallback3((clientX) => {
737
- const track = trackRef.current;
738
- if (!track || totalRef.current <= 0) return 0;
739
- const rect = track.getBoundingClientRect();
740
- const frac = rect.width > 0 ? (clientX - rect.left) / rect.width : 0;
741
- return Math.max(0, Math.min(1, frac)) * totalRef.current;
742
- }, []);
743
- const onTrackPointerDown = useCallback3(
744
- (e) => {
745
- if (!replayerRef.current || totalRef.current <= 0) return;
746
- e.currentTarget.setPointerCapture(e.pointerId);
747
- seekTo(offsetFromPointer(e.clientX));
748
- },
749
- [offsetFromPointer, seekTo]
750
- );
751
- const onTrackPointerMove = useCallback3(
752
- (e) => {
753
- if (!e.currentTarget.hasPointerCapture(e.pointerId)) return;
754
- seekTo(offsetFromPointer(e.clientX));
755
- },
756
- [offsetFromPointer, seekTo]
757
- );
758
- const onTrackKeyDown = useCallback3(
759
- (e) => {
760
- const step = totalRef.current * 0.05;
761
- if (e.key === "ArrowLeft") {
762
- e.preventDefault();
763
- seekTo(safeCurrentTime() - step);
764
- } else if (e.key === "ArrowRight") {
765
- e.preventDefault();
766
- seekTo(safeCurrentTime() + step);
767
- }
768
- },
769
- [seekTo]
770
- );
771
- useEffect3(() => {
772
- let cancelled = false;
773
- let observer = null;
774
- setStatus("loading");
775
- setMessage(void 0);
776
- setPlaying(false);
777
- playingRef.current = false;
778
- finishedRef.current = false;
779
- setElapsed(0);
780
- setTotal(0);
781
- totalRef.current = 0;
782
- async function mount() {
783
- try {
784
- const events = await api.getAssetJson(reportId, "replay");
785
- if (cancelled) return;
786
- if (!Array.isArray(events) || events.length === 0) {
787
- setStatus("empty");
788
- return;
789
- }
790
- const mod = await import("./rrweb-EYUUZIYR.js");
791
- if (cancelled) return;
792
- const frame = frameRef.current;
793
- if (!frame) return;
794
- frame.innerHTML = "";
795
- const viewport = readViewport(events) ?? { width: FALLBACK_W, height: FALLBACK_H };
796
- frame.style.aspectRatio = `${viewport.width} / ${viewport.height}`;
797
- const replayer = new mod.Replayer(events, {
798
- root: frame,
799
- mouseTail: false,
800
- skipInactive: true,
801
- speed: 1
802
- });
803
- if (cancelled) {
804
- replayer.destroy();
805
- return;
806
- }
807
- replayerRef.current = replayer;
808
- const meta = replayer.getMetaData();
809
- totalRef.current = meta.totalTime;
810
- setTotal(meta.totalTime);
811
- const baseWidth = replayer.iframe.offsetWidth || viewport.width;
812
- const applyScale = () => {
813
- const el = frameRef.current;
814
- if (!el || baseWidth <= 0) return;
815
- const scale = el.clientWidth / baseWidth;
816
- const wrapper = replayer.wrapper;
817
- wrapper.style.transformOrigin = "top left";
818
- wrapper.style.transform = `scale(${scale})`;
819
- };
820
- applyScale();
821
- observer = new ResizeObserver(applyScale);
822
- observer.observe(frame);
823
- replayer.on(mod.ReplayerEvents.Finish, () => {
824
- finishedRef.current = true;
825
- playingRef.current = false;
826
- setPlaying(false);
827
- stopRaf();
828
- setElapsed(totalRef.current);
829
- });
830
- setStatus("ready");
831
- } catch (err) {
832
- if (cancelled) return;
833
- setStatus("error");
834
- setMessage(err instanceof Error ? err.message : String(err));
835
- }
836
- }
837
- void mount();
838
- return () => {
839
- cancelled = true;
840
- stopRaf();
841
- observer?.disconnect();
842
- try {
843
- replayerRef.current?.destroy();
844
- } catch {
845
- }
846
- replayerRef.current = null;
847
- };
848
- }, [api, reportId, stopRaf]);
849
- const pct = total > 0 ? Math.min(100, elapsed / total * 100) : 0;
850
- return /* @__PURE__ */ jsxs3("div", { className: clsx_default("lens-db", "lens-db__replay", className), children: [
851
- status === "loading" && /* @__PURE__ */ jsx6("div", { className: "lens-db__replay-state", children: "Loading replay\u2026" }),
852
- status === "empty" && /* @__PURE__ */ jsx6("div", { className: "lens-db__replay-state lens-db__muted", children: "No replay captured." }),
853
- status === "error" && /* @__PURE__ */ jsxs3("div", { className: "lens-db__replay-state lens-db__error", children: [
854
- "No replay captured",
855
- message ? ` \u2014 ${message}` : "",
856
- "."
857
- ] }),
858
- /* @__PURE__ */ jsxs3("div", { className: "lens-db__rp", "data-visible": status === "ready", children: [
859
- /* @__PURE__ */ jsx6("div", { ref: frameRef, className: "lens-db__rp-frame" }),
860
- /* @__PURE__ */ jsxs3("div", { className: "lens-db__rp-bar", children: [
861
- /* @__PURE__ */ jsx6(
862
- "button",
863
- {
864
- type: "button",
865
- className: "lens-db__rp-play",
866
- onClick: togglePlay,
867
- "aria-label": playing ? "Pause replay" : "Play replay",
868
- "aria-pressed": playing,
869
- children: playing ? /* @__PURE__ */ jsx6(IconPause, { size: 16 }) : /* @__PURE__ */ jsx6(IconPlay, { size: 16 })
870
- }
871
- ),
872
- /* @__PURE__ */ jsx6("span", { className: "lens-db__rp-time", children: formatTime(elapsed) }),
873
- /* @__PURE__ */ jsx6(
874
- "div",
875
- {
876
- ref: trackRef,
877
- className: "lens-db__rp-track",
878
- role: "slider",
879
- tabIndex: 0,
880
- "aria-label": "Seek",
881
- "aria-valuemin": 0,
882
- "aria-valuemax": Math.round(total),
883
- "aria-valuenow": Math.round(elapsed),
884
- "aria-valuetext": `${formatTime(elapsed)} of ${formatTime(total)}`,
885
- onPointerDown: onTrackPointerDown,
886
- onPointerMove: onTrackPointerMove,
887
- onKeyDown: onTrackKeyDown,
888
- children: /* @__PURE__ */ jsx6("div", { className: "lens-db__rp-fill", style: { width: `${pct}%` } })
889
- }
890
- ),
891
- /* @__PURE__ */ jsx6("span", { className: "lens-db__rp-time", children: formatTime(total) }),
892
- /* @__PURE__ */ jsx6("div", { className: "lens-db__rp-speeds", role: "group", "aria-label": "Playback speed", children: SPEEDS.map((s) => /* @__PURE__ */ jsxs3(
893
- "button",
894
- {
895
- type: "button",
896
- className: clsx_default("lens-db__rp-speed", speed === s && "lens-db__rp-speed--on"),
897
- onClick: () => changeSpeed(s),
898
- "aria-pressed": speed === s,
899
- children: [
900
- s,
901
- "\xD7"
902
- ]
903
- },
904
- s
905
- )) }),
906
- /* @__PURE__ */ jsx6(
907
- "button",
908
- {
909
- type: "button",
910
- className: clsx_default("lens-db__rp-skip", skipInactive && "lens-db__rp-skip--on"),
911
- onClick: toggleSkipInactive,
912
- "aria-pressed": skipInactive,
913
- children: "Skip idle"
914
- }
915
- )
916
- ] })
917
- ] })
918
- ] });
919
- }
920
- function ReportDetail(props) {
921
- const { reportId, onClose, className } = props;
922
- const api = useLensApi();
923
- const { data: report, error, loading, reload } = useAsync(
924
- () => api.getReport(reportId),
925
- [reportId]
926
- );
927
- return /* @__PURE__ */ jsxs4("div", { className: clsx_default("lens-db", "lens-db__detail", className), children: [
928
- loading && /* @__PURE__ */ jsx7("div", { className: "lens-db__detail-loading", children: "Loading report\u2026" }),
929
- error && /* @__PURE__ */ jsxs4("div", { className: "lens-db__error", children: [
930
- "Failed to load report: ",
931
- error.message,
932
- " ",
933
- /* @__PURE__ */ jsx7("button", { type: "button", className: "lens-db__link-btn", onClick: reload, children: "Retry" })
934
- ] }),
935
- report && /* @__PURE__ */ jsx7(ReportDetailBody, { report, onClose })
936
- ] });
937
- }
938
- function ReportDetailBody({ report, onClose }) {
939
- const meta = report.metadata;
940
- const [tab, setTab] = useState4("overview");
941
- const [replayMounted, setReplayMounted] = useState4(false);
942
- const uid = useId();
943
- const tabRefs = useRef3({
944
- overview: null,
945
- replay: null,
946
- logs: null,
947
- network: null
948
- });
949
- const errorCount = meta?.errors.length ?? 0;
950
- const consoleCount = meta?.consoleTail.length ?? 0;
951
- const networkCount = meta?.networkTail.length ?? 0;
952
- const tabs = [
953
- { id: "overview", label: "Overview" },
954
- { id: "replay", label: "Replay" },
955
- { id: "logs", label: "Logs", count: errorCount + consoleCount },
956
- { id: "network", label: "Network", count: networkCount }
957
- ];
958
- function select(id) {
959
- if (id === "replay") setReplayMounted(true);
960
- setTab(id);
961
- }
962
- function onTabKeyDown(e, index) {
963
- let next = index;
964
- if (e.key === "ArrowRight" || e.key === "ArrowDown") next = (index + 1) % tabs.length;
965
- else if (e.key === "ArrowLeft" || e.key === "ArrowUp") next = (index - 1 + tabs.length) % tabs.length;
966
- else if (e.key === "Home") next = 0;
967
- else if (e.key === "End") next = tabs.length - 1;
968
- else return;
969
- e.preventDefault();
970
- const nextTab = tabs[next]?.id;
971
- if (!nextTab) return;
972
- select(nextTab);
973
- tabRefs.current[nextTab]?.focus();
974
- }
975
- const tabId = (id) => `${uid}-tab-${id}`;
976
- const panelId = (id) => `${uid}-panel-${id}`;
977
- return /* @__PURE__ */ jsxs4(Fragment2, { children: [
978
- /* @__PURE__ */ jsxs4("header", { className: "lens-db__detail-header", children: [
979
- /* @__PURE__ */ jsxs4("div", { className: "lens-db__detail-header-main", children: [
980
- /* @__PURE__ */ jsx7(StatusBadge, { status: report.status }),
981
- /* @__PURE__ */ jsx7("span", { className: "lens-db__detail-time", title: formatTimestamp(report.createdAt), children: relativeTime(report.createdAt) }),
982
- report.externalIssue && /* @__PURE__ */ jsxs4(
983
- "a",
984
- {
985
- className: "lens-db__link lens-db__link-inline",
986
- href: report.externalIssue.url,
987
- target: "_blank",
988
- rel: "noreferrer",
989
- children: [
990
- report.externalIssue.provider,
991
- " \xB7 ",
992
- report.externalIssue.key,
993
- /* @__PURE__ */ jsx7(IconExternal, { size: 13 })
994
- ]
995
- }
996
- )
997
- ] }),
998
- onClose && /* @__PURE__ */ jsx7("button", { type: "button", className: "lens-db__icon-btn", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx7(IconClose, {}) })
999
- ] }),
1000
- /* @__PURE__ */ jsx7("div", { className: "lens-db__tabs", role: "tablist", "aria-label": "Report sections", children: tabs.map((t, i) => {
1001
- const active = t.id === tab;
1002
- return /* @__PURE__ */ jsxs4(
1003
- "button",
1004
- {
1005
- type: "button",
1006
- role: "tab",
1007
- id: tabId(t.id),
1008
- ref: (el) => {
1009
- tabRefs.current[t.id] = el;
1010
- },
1011
- "aria-selected": active,
1012
- "aria-controls": panelId(t.id),
1013
- tabIndex: active ? 0 : -1,
1014
- className: clsx_default("lens-db__tab", active && "lens-db__tab--active"),
1015
- onClick: () => select(t.id),
1016
- onKeyDown: (e) => onTabKeyDown(e, i),
1017
- children: [
1018
- /* @__PURE__ */ jsx7("span", { className: "lens-db__tab-label", children: t.label }),
1019
- t.count != null && t.count > 0 && /* @__PURE__ */ jsx7("span", { className: "lens-db__tab-count", children: t.count })
1020
- ]
1021
- },
1022
- t.id
1023
- );
1024
- }) }),
1025
- /* @__PURE__ */ jsxs4("div", { className: "lens-db__tabpanels", children: [
1026
- tab === "overview" && /* @__PURE__ */ jsx7("div", { role: "tabpanel", id: panelId("overview"), "aria-labelledby": tabId("overview"), tabIndex: 0, children: /* @__PURE__ */ jsx7(OverviewPanel, { report, meta }) }),
1027
- tab === "logs" && /* @__PURE__ */ jsx7("div", { role: "tabpanel", id: panelId("logs"), "aria-labelledby": tabId("logs"), tabIndex: 0, children: /* @__PURE__ */ jsx7(LogsPanel, { errors: meta?.errors ?? [], consoleEntries: meta?.consoleTail ?? [] }) }),
1028
- tab === "network" && /* @__PURE__ */ jsx7("div", { role: "tabpanel", id: panelId("network"), "aria-labelledby": tabId("network"), tabIndex: 0, children: /* @__PURE__ */ jsx7(NetworkPanel, { entries: meta?.networkTail ?? [] }) }),
1029
- replayMounted && /* @__PURE__ */ jsx7(
1030
- "div",
1031
- {
1032
- role: "tabpanel",
1033
- id: panelId("replay"),
1034
- "aria-labelledby": tabId("replay"),
1035
- tabIndex: 0,
1036
- hidden: tab !== "replay",
1037
- children: /* @__PURE__ */ jsx7("div", { className: "lens-db__panel", children: /* @__PURE__ */ jsx7(ReplayPlayer, { reportId: report.id }) })
1038
- }
1039
- )
1040
- ] })
1041
- ] });
1042
- }
1043
- function OverviewPanel({ report, meta }) {
1044
- const hasScreenshot = report.assets.some((a) => a.kind === "screenshot");
1045
- const attachments = report.assets.filter((a) => a.kind !== "screenshot");
1046
- if (!meta) {
1047
- return /* @__PURE__ */ jsxs4(Fragment2, { children: [
1048
- hasScreenshot && /* @__PURE__ */ jsx7(Section, { title: "Screenshot", children: /* @__PURE__ */ jsx7(Screenshot, { reportId: report.id }) }),
1049
- /* @__PURE__ */ jsx7(Section, { title: "Report", children: /* @__PURE__ */ jsx7("p", { className: "lens-db__muted", children: "This report has no metadata yet (still initiating)." }) })
1050
- ] });
1051
- }
1052
- const hasTarget = Boolean(meta.target && (meta.target.domPath || meta.target.reactComponents?.length));
1053
- return /* @__PURE__ */ jsxs4(Fragment2, { children: [
1054
- meta.comment && /* @__PURE__ */ jsx7(Section, { title: "Comment", children: /* @__PURE__ */ jsx7("p", { className: "lens-db__comment", children: meta.comment }) }),
1055
- hasScreenshot && /* @__PURE__ */ jsx7(Section, { title: "Screenshot", children: /* @__PURE__ */ jsx7(Screenshot, { reportId: report.id }) }),
1056
- /* @__PURE__ */ jsx7(Section, { title: "Environment", children: /* @__PURE__ */ jsx7(EnvironmentGrid, { env: meta.env, app: meta.app }) }),
1057
- hasTarget && meta.target && /* @__PURE__ */ jsxs4(Section, { title: "Target element", children: [
1058
- meta.target.domPath && /* @__PURE__ */ jsx7("code", { className: "lens-db__dompath", children: meta.target.domPath }),
1059
- meta.target.reactComponents && meta.target.reactComponents.length > 0 && /* @__PURE__ */ jsx7("div", { className: "lens-db__chips", children: meta.target.reactComponents.map((c, i) => /* @__PURE__ */ jsx7("span", { className: "lens-db__chip", children: c }, `${c}-${i}`)) })
1060
- ] }),
1061
- attachments.length > 0 && /* @__PURE__ */ jsx7(Section, { title: "Attachments", children: /* @__PURE__ */ jsx7(Attachments, { reportId: report.id, assets: attachments }) })
1062
- ] });
1063
- }
1064
- function Screenshot({ reportId }) {
1065
- const api = useLensApi();
1066
- const { data: url, error, loading } = useAsync(
1067
- () => api.getAssetUrl(reportId, "screenshot"),
1068
- [reportId]
1069
- );
1070
- if (loading) {
1071
- return /* @__PURE__ */ jsx7("div", { className: "lens-db__shot lens-db__shot--loading", "aria-hidden": true });
1072
- }
1073
- if (error || !url) return null;
1074
- return /* @__PURE__ */ jsx7(
1075
- "a",
1076
- {
1077
- className: "lens-db__shot",
1078
- href: url,
1079
- target: "_blank",
1080
- rel: "noreferrer",
1081
- title: "Open full screenshot in a new tab",
1082
- children: /* @__PURE__ */ jsx7("img", { className: "lens-db__shot-img", src: url, alt: "Captured screenshot", loading: "lazy" })
1083
- }
1084
- );
1085
- }
1086
- function dedupeErrors(errors) {
1087
- const byMessage = /* @__PURE__ */ new Map();
1088
- const order = [];
1089
- for (const entry of errors) {
1090
- const existing = byMessage.get(entry.message);
1091
- if (existing) {
1092
- existing.count += 1;
1093
- } else {
1094
- const group = { entry, count: 1 };
1095
- byMessage.set(entry.message, group);
1096
- order.push(group);
1097
- }
1098
- }
1099
- return order;
1100
- }
1101
- function LogsPanel({ errors, consoleEntries }) {
1102
- const groups = dedupeErrors(errors);
1103
- return /* @__PURE__ */ jsxs4(Fragment2, { children: [
1104
- /* @__PURE__ */ jsx7(Section, { title: `Errors${errors.length ? ` (${groups.length})` : ""}`, children: groups.length === 0 ? /* @__PURE__ */ jsx7("p", { className: "lens-db__muted", children: "Nothing captured." }) : /* @__PURE__ */ jsx7("div", { className: "lens-db__scroll", children: /* @__PURE__ */ jsx7("ul", { className: "lens-db__errlog", children: groups.map((g, i) => /* @__PURE__ */ jsx7(ErrorRow, { group: g }, i)) }) }) }),
1105
- /* @__PURE__ */ jsx7(Section, { title: "Console", children: consoleEntries.length === 0 ? /* @__PURE__ */ jsx7("p", { className: "lens-db__muted", children: "Nothing captured." }) : /* @__PURE__ */ jsx7("div", { className: "lens-db__scroll", children: /* @__PURE__ */ jsx7(ConsoleTail, { entries: consoleEntries }) }) })
1106
- ] });
1107
- }
1108
- function ErrorRow({ group }) {
1109
- const { entry, count } = group;
1110
- const firstLine = entry.message.split("\n")[0] ?? entry.message;
1111
- return /* @__PURE__ */ jsx7("li", { className: "lens-db__errlog-item", children: /* @__PURE__ */ jsxs4("details", { className: "lens-db__errlog-details", children: [
1112
- /* @__PURE__ */ jsxs4("summary", { className: "lens-db__errlog-summary", children: [
1113
- /* @__PURE__ */ jsx7("span", { className: "lens-db__errlog-msg", children: truncate(firstLine, 140) }),
1114
- count > 1 && /* @__PURE__ */ jsxs4("span", { className: "lens-db__count-badge", children: [
1115
- "\xD7",
1116
- count
1117
- ] }),
1118
- /* @__PURE__ */ jsx7("span", { className: "lens-db__errlog-source", children: entry.source })
1119
- ] }),
1120
- /* @__PURE__ */ jsxs4("div", { className: "lens-db__errlog-body", children: [
1121
- entry.message !== firstLine && /* @__PURE__ */ jsx7("pre", { className: "lens-db__stack", children: entry.message }),
1122
- entry.stack && /* @__PURE__ */ jsx7("pre", { className: "lens-db__stack", children: entry.stack })
1123
- ] })
1124
- ] }) });
1125
- }
1126
- function ConsoleTail({ entries }) {
1127
- return /* @__PURE__ */ jsx7("div", { className: "lens-db__console", children: entries.map((e, i) => /* @__PURE__ */ jsxs4("div", { className: clsx_default("lens-db__console-line", `lens-db__console-line--${e.level}`), children: [
1128
- /* @__PURE__ */ jsx7("span", { className: "lens-db__console-level", children: e.level }),
1129
- /* @__PURE__ */ jsx7("span", { className: "lens-db__console-args", children: e.args.join(" ") })
1130
- ] }, i)) });
1131
- }
1132
- function NetworkPanel({ entries }) {
1133
- return /* @__PURE__ */ jsx7("div", { className: "lens-db__panel", children: entries.length === 0 ? /* @__PURE__ */ jsx7("p", { className: "lens-db__muted", children: "Nothing captured." }) : /* @__PURE__ */ jsx7(NetworkTail, { entries }) });
1134
- }
1135
- function NetworkTail({ entries }) {
1136
- return /* @__PURE__ */ jsxs4("table", { className: "lens-db__nettable", children: [
1137
- /* @__PURE__ */ jsx7("thead", { children: /* @__PURE__ */ jsxs4("tr", { children: [
1138
- /* @__PURE__ */ jsx7("th", { children: "Method" }),
1139
- /* @__PURE__ */ jsx7("th", { children: "Path" }),
1140
- /* @__PURE__ */ jsx7("th", { className: "lens-db__num", children: "Status" }),
1141
- /* @__PURE__ */ jsx7("th", { className: "lens-db__num", children: "Duration" })
1142
- ] }) }),
1143
- /* @__PURE__ */ jsx7("tbody", { children: entries.map((e, i) => {
1144
- const bad = e.error != null || e.ok === false || e.status != null && (e.status < 200 || e.status >= 300);
1145
- return /* @__PURE__ */ jsxs4("tr", { className: clsx_default(bad && "lens-db__netrow--bad"), children: [
1146
- /* @__PURE__ */ jsx7("td", { className: "lens-db__mono", children: e.method }),
1147
- /* @__PURE__ */ jsx7("td", { className: "lens-db__mono lens-db__muted", title: e.url, children: urlPath(e.url) }),
1148
- /* @__PURE__ */ jsx7("td", { className: "lens-db__num lens-db__mono lens-db__net-status", children: e.error ? "ERR" : e.status ?? "\u2014" }),
1149
- /* @__PURE__ */ jsx7("td", { className: "lens-db__num lens-db__muted", children: formatDuration(e.durationMs) })
1150
- ] }, i);
1151
- }) })
1152
- ] });
1153
- }
1154
- function Section({ title, children }) {
1155
- return /* @__PURE__ */ jsxs4("section", { className: "lens-db__section", children: [
1156
- /* @__PURE__ */ jsx7("h3", { className: "lens-db__section-title", children: title }),
1157
- /* @__PURE__ */ jsx7("div", { className: "lens-db__section-body", children })
1158
- ] });
1159
- }
1160
- function EnvironmentGrid({ env, app }) {
1161
- const rows = [
1162
- ["Browser", env.browser],
1163
- ["OS", env.os],
1164
- ["Viewport", `${env.viewport.width}\xD7${env.viewport.height}`],
1165
- ["Language", env.language],
1166
- ["Timezone", env.timezone],
1167
- ["URL", env.url],
1168
- ["Route", env.route],
1169
- ["Release", app?.release],
1170
- ["Git SHA", app?.gitSha]
1171
- ];
1172
- const monoLabels = /* @__PURE__ */ new Set(["URL", "Route", "Release", "Git SHA"]);
1173
- return /* @__PURE__ */ jsx7("dl", { className: "lens-db__grid", children: rows.filter(([, v]) => v != null && v !== "").map(([label, value]) => /* @__PURE__ */ jsxs4("div", { className: "lens-db__grid-row", children: [
1174
- /* @__PURE__ */ jsx7("dt", { className: "lens-db__grid-label", children: label }),
1175
- /* @__PURE__ */ jsx7("dd", { className: clsx_default("lens-db__grid-value", monoLabels.has(label) && "lens-db__mono"), children: value })
1176
- ] }, label)) });
1177
- }
1178
- function Attachments({ reportId, assets }) {
1179
- const api = useLensApi();
1180
- const [busy, setBusy] = useState4(null);
1181
- const [err, setErr] = useState4(void 0);
1182
- async function open(kind) {
1183
- setBusy(kind);
1184
- setErr(void 0);
1185
- try {
1186
- const url = await api.getAssetUrl(reportId, kind);
1187
- window.open(url, "_blank", "noopener,noreferrer");
1188
- } catch (e) {
1189
- setErr(e instanceof Error ? e.message : String(e));
1190
- } finally {
1191
- setBusy(null);
1192
- }
1193
- }
1194
- return /* @__PURE__ */ jsxs4("div", { className: "lens-db__attachments", children: [
1195
- err && /* @__PURE__ */ jsx7("div", { className: "lens-db__error", children: err }),
1196
- /* @__PURE__ */ jsx7("ul", { className: "lens-db__attachment-list", children: assets.map((a) => /* @__PURE__ */ jsxs4("li", { className: "lens-db__attachment", children: [
1197
- /* @__PURE__ */ jsx7("span", { className: "lens-db__attachment-kind", children: a.kind }),
1198
- /* @__PURE__ */ jsx7("span", { className: "lens-db__muted lens-db__attachment-type", children: a.contentType }),
1199
- a.byteSize != null && /* @__PURE__ */ jsx7("span", { className: "lens-db__muted lens-db__attachment-size", children: formatBytes(a.byteSize) }),
1200
- /* @__PURE__ */ jsx7(
1201
- "button",
1202
- {
1203
- type: "button",
1204
- className: "lens-db__link-btn",
1205
- disabled: busy === a.kind,
1206
- onClick: () => void open(a.kind),
1207
- children: busy === a.kind ? "Opening\u2026" : "Download"
1208
- }
1209
- )
1210
- ] }, a.key)) })
1211
- ] });
1212
- }
1213
- function formatBytes(bytes) {
1214
- if (bytes < 1024) return `${bytes} B`;
1215
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1216
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1217
- }
1218
- var PROVIDER_META = {
1219
- linear: { name: "Linear", Icon: IconLinear },
1220
- jira: { name: "Jira", Icon: IconJira },
1221
- github: { name: "GitHub", Icon: IconGitHub }
1222
- };
1223
- var PROVIDER_ORDER = ["linear", "jira", "github"];
1224
- var CONFIG_FIELD = {
1225
- github: {
1226
- key: "repo",
1227
- label: "Repository",
1228
- placeholder: "owner/repo",
1229
- requiredHint: "Set a repository to enable issue creation"
1230
- },
1231
- jira: {
1232
- key: "projectKey",
1233
- label: "Project key",
1234
- placeholder: "BUG",
1235
- requiredHint: "Set a project key to enable issue creation"
1236
- },
1237
- linear: {
1238
- key: "teamKey",
1239
- label: "Team",
1240
- placeholder: "First team (default)"
1241
- }
1242
- };
1243
- var SELECT_PLACEHOLDER = {
1244
- linear: "First team (default)",
1245
- github: "Select a repository\u2026",
1246
- jira: "Select a project\u2026"
1247
- };
1248
- function IntegrationList(props) {
1249
- const {
1250
- integrations,
1251
- busy,
1252
- title,
1253
- description,
1254
- flash,
1255
- onFlashDismiss,
1256
- loading,
1257
- loadError,
1258
- onRetry,
1259
- onConnect,
1260
- onActivate,
1261
- onDisconnect,
1262
- onCompleteDev,
1263
- onSaveConfig,
1264
- loadOptions,
1265
- className
1266
- } = props;
1267
- useEffect4(() => {
1268
- if (!flash || !onFlashDismiss) return;
1269
- const t = setTimeout(onFlashDismiss, 4e3);
1270
- return () => clearTimeout(t);
1271
- }, [flash, onFlashDismiss]);
1272
- const byProvider = /* @__PURE__ */ new Map();
1273
- for (const it of integrations) byProvider.set(it.provider, it);
1274
- const hasHeader = Boolean(title || description);
1275
- return /* @__PURE__ */ jsxs5("div", { className: clsx_default("lens-db__intlist", className), children: [
1276
- hasHeader && /* @__PURE__ */ jsxs5("div", { className: "lens-db__intlist-head", children: [
1277
- title && /* @__PURE__ */ jsx8("div", { className: "lens-db__intlist-title", children: title }),
1278
- description && /* @__PURE__ */ jsx8("div", { className: "lens-db__intlist-desc lens-db__muted", children: description })
1279
- ] }),
1280
- flash && /* @__PURE__ */ jsxs5(
1281
- "div",
1282
- {
1283
- className: clsx_default(
1284
- "lens-db__intlist-banner",
1285
- flash.kind === "ok" ? "lens-db__intlist-banner--ok" : "lens-db__intlist-banner--error"
1286
- ),
1287
- role: "status",
1288
- children: [
1289
- flash.kind === "ok" ? /* @__PURE__ */ jsx8(IconCheckCircle, { size: 16 }) : /* @__PURE__ */ jsx8(IconAlertTriangle, { size: 16 }),
1290
- /* @__PURE__ */ jsx8("span", { children: flash.message })
1291
- ]
1292
- }
1293
- ),
1294
- loading ? /* @__PURE__ */ jsx8("div", { className: "lens-db__intlist-rows", "aria-busy": "true", children: PROVIDER_ORDER.map((p) => /* @__PURE__ */ jsxs5("div", { className: "lens-db__introw lens-db__introw--skeleton", children: [
1295
- /* @__PURE__ */ jsx8("span", { className: "lens-db__skeleton lens-db__intmark-skeleton" }),
1296
- /* @__PURE__ */ jsxs5("span", { className: "lens-db__intbody", children: [
1297
- /* @__PURE__ */ jsx8("span", { className: "lens-db__skeleton", style: { width: "35%" } }),
1298
- /* @__PURE__ */ jsx8(
1299
- "span",
1300
- {
1301
- className: "lens-db__skeleton",
1302
- style: { width: "55%", marginTop: "7px", height: "10px" }
1303
- }
1304
- )
1305
- ] })
1306
- ] }, p)) }) : loadError ? /* @__PURE__ */ jsxs5("div", { className: "lens-db__intlist-load-error", children: [
1307
- "Failed to load integrations: ",
1308
- loadError,
1309
- onRetry && /* @__PURE__ */ jsxs5(Fragment3, { children: [
1310
- " ",
1311
- /* @__PURE__ */ jsx8("button", { type: "button", className: "lens-db__link-btn", onClick: onRetry, children: "Retry" })
1312
- ] })
1313
- ] }) : /* @__PURE__ */ jsx8(
1314
- "div",
1315
- {
1316
- className: "lens-db__intlist-rows",
1317
- role: "radiogroup",
1318
- "aria-label": "Issue destination",
1319
- children: PROVIDER_ORDER.map((provider) => {
1320
- const meta = PROVIDER_META[provider];
1321
- const Icon = meta.Icon;
1322
- const integration = byProvider.get(provider);
1323
- const status = integration?.status ?? "disconnected";
1324
- const active = integration?.active ?? false;
1325
- const account = integration?.account ?? null;
1326
- const isBusy = busy === provider;
1327
- const isConnected = status === "connected";
1328
- const statusLine = status === "connected" ? account ? `Connected as ${account}` : "Connected" : status === "pending" ? "Waiting for authorization\u2026" : "Not connected";
1329
- return /* @__PURE__ */ jsxs5(
1330
- "div",
1331
- {
1332
- className: clsx_default("lens-db__introw", active && "lens-db__introw--active"),
1333
- children: [
1334
- /* @__PURE__ */ jsx8("span", { className: "lens-db__intmark", "aria-hidden": true, children: /* @__PURE__ */ jsx8(Icon, { size: 20 }) }),
1335
- /* @__PURE__ */ jsxs5("span", { className: "lens-db__intbody", children: [
1336
- /* @__PURE__ */ jsx8("span", { className: "lens-db__intname", children: meta.name }),
1337
- /* @__PURE__ */ jsxs5("span", { className: "lens-db__intstatus", children: [
1338
- /* @__PURE__ */ jsx8(
1339
- "span",
1340
- {
1341
- className: clsx_default(
1342
- "lens-db__intdot",
1343
- status === "connected" ? "lens-db__intdot--on" : status === "pending" ? "lens-db__intdot--pending" : "lens-db__intdot--off"
1344
- ),
1345
- "aria-hidden": true
1346
- }
1347
- ),
1348
- /* @__PURE__ */ jsx8("span", { children: statusLine })
1349
- ] })
1350
- ] }),
1351
- /* @__PURE__ */ jsxs5("span", { className: "lens-db__introw-right", children: [
1352
- isConnected && /* @__PURE__ */ jsxs5(
1353
- "button",
1354
- {
1355
- type: "button",
1356
- role: "radio",
1357
- "aria-checked": active,
1358
- "aria-label": active ? `${meta.name} is the issue destination` : `Make ${meta.name} the issue destination`,
1359
- className: clsx_default(
1360
- "lens-db__intradio",
1361
- active && "lens-db__intradio--active"
1362
- ),
1363
- disabled: isBusy || active,
1364
- onClick: () => !active && onActivate(provider),
1365
- children: [
1366
- /* @__PURE__ */ jsx8("span", { className: "lens-db__intradio-dot", children: active && /* @__PURE__ */ jsx8("span", { className: "lens-db__intradio-fill" }) }),
1367
- "Destination"
1368
- ]
1369
- }
1370
- ),
1371
- status === "pending" && /* @__PURE__ */ jsxs5(Fragment3, { children: [
1372
- /* @__PURE__ */ jsx8("span", { className: "lens-db__intwait", children: "waiting for authorization\u2026" }),
1373
- /* @__PURE__ */ jsx8(
1374
- "button",
1375
- {
1376
- type: "button",
1377
- className: "lens-db__btn lens-db__btn--sm",
1378
- disabled: isBusy,
1379
- onClick: () => onCompleteDev(provider),
1380
- children: isBusy ? "Completing\u2026" : "Complete connection (dev)"
1381
- }
1382
- )
1383
- ] }),
1384
- status === "disconnected" && /* @__PURE__ */ jsx8(
1385
- "button",
1386
- {
1387
- type: "button",
1388
- className: "lens-db__btn lens-db__btn--sm",
1389
- disabled: isBusy,
1390
- onClick: () => onConnect(provider),
1391
- children: isBusy ? "Opening\u2026" : "Connect"
1392
- }
1393
- ),
1394
- status !== "disconnected" && /* @__PURE__ */ jsx8(
1395
- "button",
1396
- {
1397
- type: "button",
1398
- className: "lens-db__btn lens-db__btn--sm lens-db__btn--danger-ghost",
1399
- "aria-label": `Disconnect ${meta.name}`,
1400
- disabled: isBusy,
1401
- onClick: () => onDisconnect(provider),
1402
- children: "Disconnect"
1403
- }
1404
- )
1405
- ] }),
1406
- isConnected && /* @__PURE__ */ jsx8(
1407
- IntegrationConfigLine,
1408
- {
1409
- provider,
1410
- config: integration?.config ?? {},
1411
- active,
1412
- disabled: isBusy,
1413
- onSave: (config) => onSaveConfig(provider, config),
1414
- loadOptions
1415
- }
1416
- )
1417
- ]
1418
- },
1419
- provider
1420
- );
1421
- })
1422
- }
1423
- )
1424
- ] });
1425
- }
1426
- function IntegrationConfigLine({
1427
- provider,
1428
- config,
1429
- active,
1430
- disabled,
1431
- onSave,
1432
- loadOptions
1433
- }) {
1434
- const field = CONFIG_FIELD[provider];
1435
- const saved = config[field.key] ?? "";
1436
- const [value, setValue] = useState5(saved);
1437
- const [busy, setBusy] = useState5(false);
1438
- const [done, setDone] = useState5(false);
1439
- const [options, setOptions] = useState5({ status: "idle" });
1440
- useEffect4(() => {
1441
- setValue(saved);
1442
- }, [saved]);
1443
- useEffect4(() => {
1444
- if (!loadOptions) return;
1445
- let cancelled = false;
1446
- setOptions({ status: "loading" });
1447
- loadOptions(provider).then((res) => {
1448
- if (!cancelled) setOptions({ status: "ready", options: res.options });
1449
- }).catch(() => {
1450
- if (!cancelled) setOptions({ status: "error" });
1451
- });
1452
- return () => {
1453
- cancelled = true;
1454
- };
1455
- }, [loadOptions, provider]);
1456
- const dirty = value.trim() !== saved;
1457
- async function commit(raw) {
1458
- if (busy) return;
1459
- setBusy(true);
1460
- setDone(false);
1461
- const next = { ...config };
1462
- next[field.key] = raw.trim() || void 0;
1463
- try {
1464
- await onSave(next);
1465
- setDone(true);
1466
- setTimeout(() => setDone(false), 2e3);
1467
- } catch {
1468
- } finally {
1469
- setBusy(false);
1470
- }
1471
- }
1472
- const saveText = () => {
1473
- if (dirty) void commit(value);
1474
- };
1475
- const onSelect = (next) => {
1476
- setValue(next);
1477
- void commit(next);
1478
- };
1479
- const showHint = Boolean(field.requiredHint) && active && !saved;
1480
- const useSelect = Boolean(loadOptions) && (options.status === "loading" || options.status === "ready");
1481
- return /* @__PURE__ */ jsxs5("div", { className: "lens-db__intconfig", children: [
1482
- /* @__PURE__ */ jsxs5("label", { className: "lens-db__intconfig-field", children: [
1483
- /* @__PURE__ */ jsx8("span", { className: "lens-db__intconfig-label", children: field.label }),
1484
- useSelect ? /* @__PURE__ */ jsx8(
1485
- ConfigSelect,
1486
- {
1487
- provider,
1488
- value,
1489
- options: options.status === "ready" ? options.options : null,
1490
- loading: options.status === "loading",
1491
- disabled: disabled || busy,
1492
- onChange: onSelect
1493
- }
1494
- ) : /* @__PURE__ */ jsx8(
1495
- "input",
1496
- {
1497
- className: "lens-db__intconfig-input",
1498
- type: "text",
1499
- value,
1500
- placeholder: field.placeholder,
1501
- disabled: disabled || busy,
1502
- spellCheck: false,
1503
- autoCapitalize: "off",
1504
- autoCorrect: "off",
1505
- onChange: (e) => setValue(e.target.value),
1506
- onKeyDown: (e) => {
1507
- if (e.key === "Enter") {
1508
- e.preventDefault();
1509
- saveText();
1510
- }
1511
- }
1512
- }
1513
- )
1514
- ] }),
1515
- busy ? /* @__PURE__ */ jsx8("span", { className: "lens-db__intconfig-busy", "aria-live": "polite", "aria-label": "Saving", children: /* @__PURE__ */ jsx8("span", { className: "lens-db__spinner", "aria-hidden": true }) }) : done ? /* @__PURE__ */ jsx8("span", { className: "lens-db__intconfig-done", "aria-live": "polite", "aria-label": "Saved", children: /* @__PURE__ */ jsx8(IconCheck, { size: 14 }) }) : !useSelect && dirty ? /* @__PURE__ */ jsx8(
1516
- "button",
1517
- {
1518
- type: "button",
1519
- className: "lens-db__btn lens-db__btn--sm",
1520
- disabled,
1521
- onClick: saveText,
1522
- children: "Save"
1523
- }
1524
- ) : null,
1525
- !useSelect && options.status === "error" && loadOptions && /* @__PURE__ */ jsx8("span", { className: "lens-db__intconfig-note", children: "Couldn't load options \u2014 enter manually." }),
1526
- showHint && /* @__PURE__ */ jsx8("span", { className: "lens-db__intconfig-hint", children: field.requiredHint })
1527
- ] });
1528
- }
1529
- function ConfigSelect({
1530
- provider,
1531
- value,
1532
- options,
1533
- loading,
1534
- disabled,
1535
- onChange
1536
- }) {
1537
- if (loading || !options) {
1538
- return /* @__PURE__ */ jsx8("select", { className: "lens-db__intconfig-select", disabled: true, "aria-label": "Loading options", children: /* @__PURE__ */ jsx8("option", { children: "Loading\u2026" }) });
1539
- }
1540
- const placeholderDisabled = provider !== "linear";
1541
- const known = new Set(options.map((o) => o.value));
1542
- const orphan = value && !known.has(value) ? value : null;
1543
- return /* @__PURE__ */ jsxs5(
1544
- "select",
1545
- {
1546
- className: "lens-db__intconfig-select",
1547
- value,
1548
- disabled,
1549
- onChange: (e) => onChange(e.target.value),
1550
- children: [
1551
- /* @__PURE__ */ jsx8("option", { value: "", disabled: placeholderDisabled, children: SELECT_PLACEHOLDER[provider] }),
1552
- orphan && /* @__PURE__ */ jsx8("option", { value: orphan, children: orphan }),
1553
- options.map((o) => /* @__PURE__ */ jsx8("option", { value: o.value, children: o.label }, o.value))
1554
- ]
1555
- }
1556
- );
1557
- }
1558
- var PROVIDER_NAME = {
1559
- linear: "Linear",
1560
- jira: "Jira",
1561
- github: "GitHub"
1562
- };
1563
- function currentReturnUrl() {
1564
- return typeof window !== "undefined" ? window.location.href : void 0;
1565
- }
1566
- function providerLabel(raw) {
1567
- return PROVIDER_NAME[raw] ?? raw;
1568
- }
1569
- function consumeOAuthCallback() {
1570
- if (typeof window === "undefined") return null;
1571
- const params = new URLSearchParams(window.location.search);
1572
- const connected = params.get("lens_connected");
1573
- const error = params.get("lens_error");
1574
- if (connected == null && error == null) return null;
1575
- params.delete("lens_connected");
1576
- params.delete("lens_error");
1577
- try {
1578
- const qs = params.toString();
1579
- const url = window.location.pathname + (qs ? `?${qs}` : "") + window.location.hash;
1580
- window.history.replaceState(window.history.state, "", url);
1581
- } catch {
1582
- }
1583
- return { connected: connected ?? void 0, error: error ?? void 0 };
1584
- }
1585
- function IntegrationSettings(props) {
1586
- const api = useLensApi();
1587
- const { data, error, loading, reload } = useAsync(() => api.listIntegrations(), []);
1588
- const [busy, setBusy] = useState6(null);
1589
- const [list, setList] = useState6(null);
1590
- const [flash, setFlash] = useState6(null);
1591
- useEffect5(() => {
1592
- const outcome = consumeOAuthCallback();
1593
- if (!outcome) return;
1594
- if (outcome.connected) {
1595
- setFlash({ kind: "ok", message: `${providerLabel(outcome.connected)} connected` });
1596
- reload();
1597
- }
1598
- if (outcome.error) {
1599
- setFlash({ kind: "error", message: outcome.error });
1600
- }
1601
- }, []);
1602
- const integrations = list ?? data ?? [];
1603
- async function run(provider, fn) {
1604
- setBusy(provider);
1605
- try {
1606
- const next = await fn();
1607
- if (Array.isArray(next)) setList(next);
1608
- else setList(await api.listIntegrations());
1609
- } catch (err) {
1610
- setFlash({ kind: "error", message: err instanceof Error ? err.message : String(err) });
1611
- } finally {
1612
- setBusy(null);
1613
- }
1614
- }
1615
- function connect(provider) {
1616
- return run(provider, async () => {
1617
- const { authorizeUrl } = await api.connectIntegration(provider, {
1618
- returnUrl: currentReturnUrl()
1619
- });
1620
- window.open(authorizeUrl, "_blank", "noopener,noreferrer");
1621
- return api.listIntegrations();
1622
- });
1623
- }
1624
- const completeDev = (provider) => run(provider, async () => {
1625
- await api.completeIntegrationDev(provider);
1626
- return api.listIntegrations();
1627
- });
1628
- const activate = (provider) => run(provider, () => api.activateIntegration(provider));
1629
- const disconnect = (provider) => run(provider, () => api.disconnectIntegration(provider));
1630
- async function saveConfig(provider, config) {
1631
- setBusy(provider);
1632
- try {
1633
- setList(await api.updateIntegrationConfig(provider, config));
1634
- } catch (err) {
1635
- setFlash({ kind: "error", message: err instanceof Error ? err.message : String(err) });
1636
- throw err;
1637
- } finally {
1638
- setBusy(null);
1639
- }
1640
- }
1641
- return /* @__PURE__ */ jsx9("div", { className: clsx_default("lens-db", props.className), children: /* @__PURE__ */ jsx9(
1642
- IntegrationList,
1643
- {
1644
- integrations,
1645
- busy,
1646
- title: "Issue destination",
1647
- description: "Reports create issues in exactly one connected tracker.",
1648
- flash,
1649
- onFlashDismiss: () => setFlash(null),
1650
- loading: loading && !list,
1651
- loadError: error && !list ? error.message : null,
1652
- onRetry: reload,
1653
- onConnect: (p) => void connect(p),
1654
- onActivate: (p) => void activate(p),
1655
- onDisconnect: (p) => void disconnect(p),
1656
- onCompleteDev: (p) => void completeDev(p),
1657
- onSaveConfig: saveConfig,
1658
- loadOptions: (p) => api.getIntegrationOptions(p)
1659
- }
1660
- ) });
1661
- }
1662
- var STEPS = ["Project", "Destination", "Asset storage", "Install"];
1663
- var PROVIDER_NAME2 = {
1664
- linear: "Linear",
1665
- jira: "Jira",
1666
- github: "GitHub"
1667
- };
1668
- var S3_ENV_SNIPPET = `# Lens API asset storage (S3 or compatible)
1669
- LENS_S3_BUCKET=your-bucket
1670
- LENS_S3_REGION=us-east-1
1671
- AWS_ACCESS_KEY_ID=AKIA...
1672
- AWS_SECRET_ACCESS_KEY=...
1673
- # Optional (R2 / MinIO):
1674
- # LENS_S3_ENDPOINT=https://<account>.r2.cloudflarestorage.com
1675
- # LENS_S3_FORCE_PATH_STYLE=true`;
1676
- function parseOrigins(raw) {
1677
- return raw.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
1678
- }
1679
- function currentReturnUrl2() {
1680
- return typeof window !== "undefined" ? window.location.href : void 0;
1681
- }
1682
- function providerLabel2(raw) {
1683
- return PROVIDER_NAME2[raw] ?? raw;
1684
- }
1685
- function sameOrigins(a, b) {
1686
- return a.length === b.length && a.every((v, i) => v === b[i]);
1687
- }
1688
- function consumeOAuthCallback2() {
1689
- if (typeof window === "undefined") return null;
1690
- const params = new URLSearchParams(window.location.search);
1691
- const connected = params.get("lens_connected");
1692
- const error = params.get("lens_error");
1693
- if (connected == null && error == null) return null;
1694
- params.delete("lens_connected");
1695
- params.delete("lens_error");
1696
- try {
1697
- const qs = params.toString();
1698
- const url = window.location.pathname + (qs ? `?${qs}` : "") + window.location.hash;
1699
- window.history.replaceState(window.history.state, "", url);
1700
- } catch {
1701
- }
1702
- return { connected: connected ?? void 0, error: error ?? void 0 };
1703
- }
1704
- function CopyButton({
1705
- value,
1706
- label = "Copy",
1707
- ghost = false
1708
- }) {
1709
- const [copied, setCopied] = useState7(false);
1710
- return /* @__PURE__ */ jsxs6(
1711
- "button",
1712
- {
1713
- type: "button",
1714
- className: clsx_default(
1715
- "lens-db__copy-btn",
1716
- ghost && "lens-db__copy-btn--ghost",
1717
- copied && "lens-db__copy-btn--done"
1718
- ),
1719
- "aria-label": copied ? "Copied" : label,
1720
- onClick: () => {
1721
- void navigator.clipboard?.writeText(value).then(
1722
- () => {
1723
- setCopied(true);
1724
- setTimeout(() => setCopied(false), 2e3);
1725
- },
1726
- () => void 0
1727
- );
1728
- },
1729
- children: [
1730
- copied ? /* @__PURE__ */ jsx10(IconCheck, { size: 14 }) : /* @__PURE__ */ jsx10(IconCopy, { size: 14 }),
1731
- copied ? "Copied" : label
1732
- ]
1733
- }
1734
- );
1735
- }
1736
- function OnboardingWizard(props) {
1737
- const api = useLensApi();
1738
- const [step, setStep] = useState7(0);
1739
- const [name, setName] = useState7("");
1740
- const [origins, setOrigins] = useState7("");
1741
- const [dashboardUrl, setDashboardUrl] = useState7("");
1742
- const [project, setProject] = useState7(null);
1743
- const [accessId, setAccessId] = useState7(null);
1744
- const [accessSecret, setAccessSecret] = useState7(null);
1745
- const [step1Busy, setStep1Busy] = useState7(false);
1746
- const [step1Error, setStep1Error] = useState7(void 0);
1747
- const [loadingExisting, setLoadingExisting] = useState7(Boolean(props.existingProject));
1748
- const [integrations, setIntegrations] = useState7(null);
1749
- const [intBusy, setIntBusy] = useState7(null);
1750
- const [intLoadError, setIntLoadError] = useState7(void 0);
1751
- const [intFlash, setIntFlash] = useState7(null);
1752
- const [storage, setStorage] = useState7(null);
1753
- const [storageError, setStorageError] = useState7(void 0);
1754
- const [finishBusy, setFinishBusy] = useState7(false);
1755
- const [finishError, setFinishError] = useState7(void 0);
1756
- useEffect6(() => {
1757
- if (!props.existingProject) return;
1758
- let active = true;
1759
- setLoadingExisting(true);
1760
- api.getProject().then((p) => {
1761
- if (!active) return;
1762
- setProject(p);
1763
- setAccessId(p.accessId);
1764
- setName(p.name);
1765
- setOrigins(p.allowedOrigins.join("\n"));
1766
- setDashboardUrl(p.dashboardUrl ?? "");
1767
- setLoadingExisting(false);
1768
- }).catch((err) => {
1769
- if (!active) return;
1770
- setStep1Error(err instanceof Error ? err.message : String(err));
1771
- setLoadingExisting(false);
1772
- });
1773
- return () => {
1774
- active = false;
1775
- };
1776
- }, []);
1777
- useEffect6(() => {
1778
- if (step !== 1) return;
1779
- const outcome = consumeOAuthCallback2();
1780
- if (!outcome) return;
1781
- if (outcome.connected) {
1782
- setIntFlash({ kind: "ok", message: `${providerLabel2(outcome.connected)} connected` });
1783
- api.listIntegrations().then(setIntegrations).catch(() => void 0);
1784
- }
1785
- if (outcome.error) setIntFlash({ kind: "error", message: outcome.error });
1786
- }, [step]);
1787
- useEffect6(() => {
1788
- if (step !== 1 || integrations !== null) return;
1789
- let active = true;
1790
- api.listIntegrations().then((list) => active && setIntegrations(list)).catch(
1791
- (err) => active ? setIntLoadError(err instanceof Error ? err.message : String(err)) : void 0
1792
- );
1793
- return () => {
1794
- active = false;
1795
- };
1796
- }, [step, integrations, api]);
1797
- async function submitStep1() {
1798
- if (props.existingProject) {
1799
- if (!project) return;
1800
- if (!name.trim()) {
1801
- setStep1Error("Project name is required.");
1802
- return;
1803
- }
1804
- const nextOrigins = parseOrigins(origins);
1805
- const nextDashboardUrl = dashboardUrl.trim() || null;
1806
- const changed = name.trim() !== project.name || !sameOrigins(nextOrigins, project.allowedOrigins) || nextDashboardUrl !== (project.dashboardUrl ?? null);
1807
- if (!changed) {
1808
- setStep(1);
1809
- return;
1810
- }
1811
- setStep1Busy(true);
1812
- setStep1Error(void 0);
1813
- try {
1814
- const p = await api.updateProject({
1815
- name: name.trim(),
1816
- allowedOrigins: nextOrigins,
1817
- dashboardUrl: nextDashboardUrl
1818
- });
1819
- setProject(p);
1820
- setName(p.name);
1821
- setOrigins(p.allowedOrigins.join("\n"));
1822
- setDashboardUrl(p.dashboardUrl ?? "");
1823
- setStep(1);
1824
- } catch (err) {
1825
- setStep1Error(err instanceof Error ? err.message : String(err));
1826
- } finally {
1827
- setStep1Busy(false);
1828
- }
1829
- return;
1830
- }
1831
- if (!name.trim()) {
1832
- setStep1Error("Project name is required.");
1833
- return;
1834
- }
1835
- setStep1Busy(true);
1836
- setStep1Error(void 0);
1837
- try {
1838
- const res = await api.createProject({
1839
- name: name.trim(),
1840
- allowedOrigins: parseOrigins(origins)
1841
- });
1842
- setAccessId(res.accessId);
1843
- setAccessSecret(res.accessSecret);
1844
- props.onProjectCreated?.(res);
1845
- let created = res.project;
1846
- const nextDashboardUrl = dashboardUrl.trim();
1847
- if (nextDashboardUrl) {
1848
- try {
1849
- created = await api.updateProject({ dashboardUrl: nextDashboardUrl });
1850
- setDashboardUrl(created.dashboardUrl ?? "");
1851
- } catch {
1852
- }
1853
- }
1854
- setProject(created);
1855
- setStep(1);
1856
- } catch (err) {
1857
- setStep1Error(err instanceof Error ? err.message : String(err));
1858
- } finally {
1859
- setStep1Busy(false);
1860
- }
1861
- }
1862
- async function runIntegration(provider, fn) {
1863
- setIntBusy(provider);
1864
- try {
1865
- await fn();
1866
- setIntegrations(await api.listIntegrations());
1867
- } catch (err) {
1868
- setIntFlash({ kind: "error", message: err instanceof Error ? err.message : String(err) });
1869
- } finally {
1870
- setIntBusy(null);
1871
- }
1872
- }
1873
- const connect = (provider) => runIntegration(provider, async () => {
1874
- const { authorizeUrl } = await api.connectIntegration(provider, {
1875
- returnUrl: currentReturnUrl2()
1876
- });
1877
- window.open(authorizeUrl, "_blank", "noopener,noreferrer");
1878
- });
1879
- const completeDev = (provider) => runIntegration(provider, async () => {
1880
- await api.completeIntegrationDev(provider);
1881
- });
1882
- const activate = (provider) => runIntegration(provider, async () => {
1883
- await api.activateIntegration(provider);
1884
- });
1885
- const disconnect = (provider) => runIntegration(provider, async () => {
1886
- await api.disconnectIntegration(provider);
1887
- });
1888
- async function saveConfig(provider, config) {
1889
- setIntBusy(provider);
1890
- try {
1891
- setIntegrations(await api.updateIntegrationConfig(provider, config));
1892
- } catch (err) {
1893
- setIntFlash({ kind: "error", message: err instanceof Error ? err.message : String(err) });
1894
- throw err;
1895
- } finally {
1896
- setIntBusy(null);
1897
- }
1898
- }
1899
- useEffect6(() => {
1900
- if (step !== 2 || storage !== null) return;
1901
- let active = true;
1902
- api.getStorageStatus().then((s) => active && setStorage(s)).catch(
1903
- (err) => active ? setStorageError(err instanceof Error ? err.message : String(err)) : void 0
1904
- );
1905
- return () => {
1906
- active = false;
1907
- };
1908
- }, [step, storage, api]);
1909
- async function finish() {
1910
- setFinishBusy(true);
1911
- setFinishError(void 0);
1912
- try {
1913
- const p = await api.updateProject({ onboarded: true });
1914
- setProject(p);
1915
- props.onComplete?.({ project: p, accessId: accessId ?? p.accessId, accessSecret });
1916
- } catch (err) {
1917
- setFinishError(err instanceof Error ? err.message : String(err));
1918
- } finally {
1919
- setFinishBusy(false);
1920
- }
1921
- }
1922
- const activeProvider = integrations?.find((i) => i.active)?.provider ?? null;
1923
- const idForSnippets = accessId ?? project?.accessId ?? "YOUR_ACCESS_ID";
1924
- const scriptSnippet = `<script>
1925
- window.Lens=window.Lens||function(){(Lens.q=Lens.q||[]).push(arguments)};
1926
- Lens('init', '${idForSnippets}');
1927
- </script>
1928
- <script async src="https://cdn.merittdev.com/lens/v1/lens.min.js"></script>`;
1929
- const reactSnippet = `import { LensProvider } from '@merittdev/horus-lens/react';
1930
-
1931
- export function App() {
1932
- return (
1933
- <LensProvider accessId="${idForSnippets}">
1934
- {/* your app */}
1935
- </LensProvider>
1936
- );
1937
- }`;
1938
- return /* @__PURE__ */ jsxs6("div", { className: clsx_default("lens-db", "lens-db__wizard", props.className), children: [
1939
- /* @__PURE__ */ jsx10("ol", { className: "lens-db__stepper", children: STEPS.map((label, i) => /* @__PURE__ */ jsxs6(
1940
- "li",
1941
- {
1942
- className: clsx_default(
1943
- "lens-db__step",
1944
- i === step && "lens-db__step--current",
1945
- i < step && "lens-db__step--done"
1946
- ),
1947
- children: [
1948
- /* @__PURE__ */ jsx10("span", { className: "lens-db__step-dot", children: i < step ? /* @__PURE__ */ jsx10(IconCheck, { size: 14 }) : i + 1 }),
1949
- /* @__PURE__ */ jsx10("span", { className: "lens-db__step-label", children: label })
1950
- ]
1951
- },
1952
- label
1953
- )) }),
1954
- /* @__PURE__ */ jsxs6("div", { className: "lens-db__wizard-body", children: [
1955
- step === 0 && /* @__PURE__ */ jsxs6("div", { className: "lens-db__step-panel", children: [
1956
- /* @__PURE__ */ jsx10("h3", { className: "lens-db__wizard-title", children: "Create your project" }),
1957
- /* @__PURE__ */ jsx10("p", { className: "lens-db__muted", children: "Name it and list the origins allowed to submit reports." }),
1958
- loadingExisting ? /* @__PURE__ */ jsx10("p", { className: "lens-db__muted", children: "Loading project\u2026" }) : /* @__PURE__ */ jsxs6(Fragment4, { children: [
1959
- /* @__PURE__ */ jsxs6("label", { className: "lens-db__field", children: [
1960
- /* @__PURE__ */ jsx10("span", { className: "lens-db__label", children: "Project name" }),
1961
- /* @__PURE__ */ jsx10(
1962
- "input",
1963
- {
1964
- className: "lens-db__input",
1965
- type: "text",
1966
- value: name,
1967
- disabled: step1Busy,
1968
- placeholder: "Acme Web App",
1969
- onChange: (e) => setName(e.target.value)
1970
- }
1971
- )
1972
- ] }),
1973
- /* @__PURE__ */ jsxs6("label", { className: "lens-db__field", children: [
1974
- /* @__PURE__ */ jsx10("span", { className: "lens-db__label", children: "Allowed origins" }),
1975
- /* @__PURE__ */ jsx10(
1976
- "textarea",
1977
- {
1978
- className: "lens-db__input lens-db__textarea",
1979
- rows: 4,
1980
- value: origins,
1981
- disabled: step1Busy,
1982
- placeholder: "https://app.acme.com\nhttps://admin.acme.com",
1983
- onChange: (e) => setOrigins(e.target.value)
1984
- }
1985
- ),
1986
- /* @__PURE__ */ jsx10("span", { className: "lens-db__hint", children: "One origin per line." })
1987
- ] }),
1988
- /* @__PURE__ */ jsxs6("label", { className: "lens-db__field", children: [
1989
- /* @__PURE__ */ jsx10("span", { className: "lens-db__label", children: "Dashboard URL" }),
1990
- /* @__PURE__ */ jsx10(
1991
- "input",
1992
- {
1993
- className: "lens-db__input",
1994
- type: "url",
1995
- value: dashboardUrl,
1996
- disabled: step1Busy,
1997
- placeholder: "https://admin.acme.com/lens",
1998
- onChange: (e) => setDashboardUrl(e.target.value)
1999
- }
2000
- ),
2001
- /* @__PURE__ */ jsx10("span", { className: "lens-db__hint", children: "Issues link reports back to this page." })
2002
- ] }),
2003
- props.existingProject && /* @__PURE__ */ jsx10("div", { className: "lens-db__note", children: "Loaded from your existing project \u2014 edit the name or origins and Continue to save. The access secret was shown only when the project was created and can't be displayed again." })
2004
- ] }),
2005
- step1Error && /* @__PURE__ */ jsx10("div", { className: "lens-db__error", children: step1Error })
2006
- ] }),
2007
- step === 1 && /* @__PURE__ */ jsxs6("div", { className: "lens-db__step-panel", children: [
2008
- /* @__PURE__ */ jsx10("h3", { className: "lens-db__wizard-title", children: "Choose an issue destination" }),
2009
- /* @__PURE__ */ jsx10("p", { className: "lens-db__muted", children: "Reports create issues in exactly one connected tracker. You can skip this and pick a destination later." }),
2010
- /* @__PURE__ */ jsx10(
2011
- IntegrationList,
2012
- {
2013
- integrations: integrations ?? [],
2014
- busy: intBusy,
2015
- flash: intFlash,
2016
- onFlashDismiss: () => setIntFlash(null),
2017
- loading: integrations === null && !intLoadError,
2018
- loadError: intLoadError ?? null,
2019
- onRetry: () => {
2020
- setIntLoadError(void 0);
2021
- setIntegrations(null);
2022
- },
2023
- onConnect: (p) => void connect(p),
2024
- onActivate: (p) => void activate(p),
2025
- onDisconnect: (p) => void disconnect(p),
2026
- onCompleteDev: (p) => void completeDev(p),
2027
- onSaveConfig: saveConfig,
2028
- loadOptions: (p) => api.getIntegrationOptions(p)
2029
- }
2030
- ),
2031
- /* @__PURE__ */ jsx10("p", { className: "lens-db__hint", children: activeProvider ? `Issues will route to ${providerLabel2(activeProvider)}.` : "No destination selected yet \u2014 issues will queue until you pick one." })
2032
- ] }),
2033
- step === 2 && /* @__PURE__ */ jsxs6("div", { className: "lens-db__step-panel", children: [
2034
- /* @__PURE__ */ jsx10("h3", { className: "lens-db__wizard-title", children: "Asset storage" }),
2035
- /* @__PURE__ */ jsx10("p", { className: "lens-db__muted", children: "Replays and screenshots upload straight to S3. Credentials are configured through environment variables on your Lens API deployment \u2014 never through this UI." }),
2036
- !storage && !storageError && /* @__PURE__ */ jsxs6("div", { className: "lens-db__verify", children: [
2037
- /* @__PURE__ */ jsx10("span", { className: "lens-db__spinner", "aria-hidden": true }),
2038
- " Checking storage\u2026"
2039
- ] }),
2040
- storage?.mode === "s3" && /* @__PURE__ */ jsxs6("div", { className: "lens-db__storage-ok", children: [
2041
- /* @__PURE__ */ jsx10(IconCheckCircle, { size: 16 }),
2042
- /* @__PURE__ */ jsxs6("div", { children: [
2043
- /* @__PURE__ */ jsx10("div", { className: "lens-db__storage-ok-title", children: "Asset storage configured" }),
2044
- /* @__PURE__ */ jsxs6("div", { className: "lens-db__muted", children: [
2045
- /* @__PURE__ */ jsx10("span", { className: "lens-db__mono", children: storage.bucket }),
2046
- " \xB7 ",
2047
- /* @__PURE__ */ jsx10("span", { className: "lens-db__mono", children: storage.region }),
2048
- storage.source === "project" ? " \u2014 project credentials" : " \u2014 from the Lens API environment"
2049
- ] })
2050
- ] })
2051
- ] }),
2052
- storage?.mode === "local" && /* @__PURE__ */ jsxs6(Fragment4, { children: [
2053
- /* @__PURE__ */ jsx10(
2054
- "div",
2055
- {
2056
- className: storage.localAllowed ? "lens-db__warn-banner" : "lens-db__error",
2057
- children: storage.localAllowed ? "Development mode: assets are stored on the API host disk. Configure S3 before going to production \u2014 report intake is rejected without it." : "No S3 bucket is configured. Report intake is disabled until asset storage is connected."
2058
- }
2059
- ),
2060
- /* @__PURE__ */ jsx10("p", { className: "lens-db__label", children: "Set these on your Lens API deployment, then restart it:" }),
2061
- /* @__PURE__ */ jsxs6("div", { className: "lens-db__codewrap", children: [
2062
- /* @__PURE__ */ jsx10("pre", { className: "lens-db__pre", children: highlightCode(S3_ENV_SNIPPET, "env") }),
2063
- /* @__PURE__ */ jsx10(CopyButton, { value: S3_ENV_SNIPPET })
2064
- ] })
2065
- ] }),
2066
- storageError && /* @__PURE__ */ jsx10("div", { className: "lens-db__error", children: storageError })
2067
- ] }),
2068
- step === 3 && /* @__PURE__ */ jsxs6("div", { className: "lens-db__step-panel", children: [
2069
- /* @__PURE__ */ jsxs6("h3", { className: "lens-db__wizard-title lens-db__wizard-title--ok", children: [
2070
- /* @__PURE__ */ jsx10(IconCheckCircle, { size: 16 }),
2071
- "Setup complete"
2072
- ] }),
2073
- /* @__PURE__ */ jsx10("p", { className: "lens-db__muted", children: "Use these credentials to install the Lens widget in your app." }),
2074
- /* @__PURE__ */ jsxs6("div", { className: "lens-db__cred", children: [
2075
- /* @__PURE__ */ jsx10("span", { className: "lens-db__label", children: "Access ID" }),
2076
- /* @__PURE__ */ jsxs6("div", { className: "lens-db__cred-row", children: [
2077
- /* @__PURE__ */ jsx10("code", { className: "lens-db__code-inline", children: idForSnippets }),
2078
- /* @__PURE__ */ jsx10(CopyButton, { value: idForSnippets })
2079
- ] })
2080
- ] }),
2081
- accessSecret ? /* @__PURE__ */ jsxs6("div", { className: "lens-db__warn-banner", children: [
2082
- /* @__PURE__ */ jsx10("span", { className: "lens-db__warn-icon", children: /* @__PURE__ */ jsx10(IconAlertTriangle, { size: 16 }) }),
2083
- /* @__PURE__ */ jsxs6("div", { className: "lens-db__warn-body", children: [
2084
- /* @__PURE__ */ jsx10("div", { className: "lens-db__warn-title", children: "Store your access secret now" }),
2085
- /* @__PURE__ */ jsx10("p", { className: "lens-db__warn-text", children: "This is the only time it will be shown. Save it in your server-side secret store \u2014 it will never be displayed again." }),
2086
- /* @__PURE__ */ jsxs6("div", { className: "lens-db__cred-row", children: [
2087
- /* @__PURE__ */ jsx10("code", { className: "lens-db__code-inline lens-db__code-inline--secret", children: accessSecret }),
2088
- /* @__PURE__ */ jsx10(CopyButton, { value: accessSecret, label: "Copy secret" })
2089
- ] })
2090
- ] })
2091
- ] }) : /* @__PURE__ */ jsx10("div", { className: "lens-db__note", children: "The access secret isn't available here \u2014 it's shown only once, when the project is first created." }),
2092
- /* @__PURE__ */ jsxs6("div", { className: "lens-db__snippet", children: [
2093
- /* @__PURE__ */ jsx10("div", { className: "lens-db__snippet-head", children: /* @__PURE__ */ jsx10("span", { className: "lens-db__label", children: "Script-tag loader" }) }),
2094
- /* @__PURE__ */ jsxs6("div", { className: "lens-db__codeblock", children: [
2095
- /* @__PURE__ */ jsx10("pre", { className: "lens-db__pre", children: highlightCode(scriptSnippet, "markup") }),
2096
- /* @__PURE__ */ jsx10(CopyButton, { value: scriptSnippet, ghost: true })
2097
- ] })
2098
- ] }),
2099
- /* @__PURE__ */ jsxs6("div", { className: "lens-db__snippet", children: [
2100
- /* @__PURE__ */ jsx10("div", { className: "lens-db__snippet-head", children: /* @__PURE__ */ jsx10("span", { className: "lens-db__label", children: "React" }) }),
2101
- /* @__PURE__ */ jsxs6("div", { className: "lens-db__codeblock", children: [
2102
- /* @__PURE__ */ jsx10("pre", { className: "lens-db__pre", children: highlightCode(reactSnippet, "markup") }),
2103
- /* @__PURE__ */ jsx10(CopyButton, { value: reactSnippet, ghost: true })
2104
- ] })
2105
- ] }),
2106
- finishError && /* @__PURE__ */ jsx10("div", { className: "lens-db__error", children: finishError })
2107
- ] })
2108
- ] }),
2109
- /* @__PURE__ */ jsxs6("div", { className: "lens-db__wizard-footer", children: [
2110
- /* @__PURE__ */ jsx10(
2111
- "button",
2112
- {
2113
- type: "button",
2114
- className: "lens-db__btn lens-db__btn--ghost",
2115
- disabled: step === 0 || step1Busy || finishBusy,
2116
- onClick: () => setStep((s) => Math.max(0, s - 1)),
2117
- children: "Back"
2118
- }
2119
- ),
2120
- /* @__PURE__ */ jsxs6("div", { className: "lens-db__wizard-footer-right", children: [
2121
- step === 0 && /* @__PURE__ */ jsx10(
2122
- "button",
2123
- {
2124
- type: "button",
2125
- className: "lens-db__btn lens-db__btn--primary",
2126
- disabled: step1Busy || loadingExisting || props.existingProject && !project,
2127
- onClick: () => void submitStep1(),
2128
- children: props.existingProject ? step1Busy ? "Saving\u2026" : "Continue" : step1Busy ? "Creating\u2026" : "Create project"
2129
- }
2130
- ),
2131
- step === 1 && /* @__PURE__ */ jsxs6(Fragment4, { children: [
2132
- /* @__PURE__ */ jsx10(
2133
- "button",
2134
- {
2135
- type: "button",
2136
- className: "lens-db__btn lens-db__btn--ghost",
2137
- onClick: () => setStep(2),
2138
- children: "Skip"
2139
- }
2140
- ),
2141
- /* @__PURE__ */ jsx10(
2142
- "button",
2143
- {
2144
- type: "button",
2145
- className: "lens-db__btn lens-db__btn--primary",
2146
- onClick: () => setStep(2),
2147
- children: "Next"
2148
- }
2149
- )
2150
- ] }),
2151
- step === 2 && /* @__PURE__ */ jsx10(
2152
- "button",
2153
- {
2154
- type: "button",
2155
- className: "lens-db__btn lens-db__btn--primary",
2156
- disabled: !storage || storage.mode === "local" && !storage.localAllowed,
2157
- onClick: () => setStep(3),
2158
- children: "Continue"
2159
- }
2160
- ),
2161
- step === 3 && /* @__PURE__ */ jsx10(
2162
- "button",
2163
- {
2164
- type: "button",
2165
- className: "lens-db__btn lens-db__btn--primary",
2166
- disabled: finishBusy,
2167
- onClick: () => void finish(),
2168
- children: finishBusy ? "Finishing\u2026" : "Finish"
2169
- }
2170
- )
2171
- ] })
2172
- ] })
2173
- ] });
2174
- }
2175
- var HOSTED_LENS_API_URL = "https://api-lens.meritt.app";
2176
- var LensConfigContext = createContext2(null);
2177
- function LensProvider({
2178
- config,
2179
- children
2180
- }) {
2181
- return /* @__PURE__ */ jsx11(LensConfigContext.Provider, { value: config, children });
2182
- }
2183
- var TABS = [
2184
- { id: "reports", label: "Issue Reports", Icon: IconBug },
2185
- { id: "integrations", label: "Integrations", Icon: IconPlug },
2186
- { id: "setup", label: "Setup", Icon: IconRocket }
2187
- ];
2188
- function readInitialParams() {
2189
- if (typeof window === "undefined") return { report: null, project: null };
2190
- const params = new URLSearchParams(window.location.search);
2191
- return { report: params.get("report"), project: params.get("project") };
2192
- }
2193
- function syncReportParam(reportId) {
2194
- if (typeof window === "undefined") return;
2195
- try {
2196
- const params = new URLSearchParams(window.location.search);
2197
- if (reportId) params.set("report", reportId);
2198
- else params.delete("report");
2199
- const qs = params.toString();
2200
- const url = window.location.pathname + (qs ? `?${qs}` : "") + window.location.hash;
2201
- window.history.replaceState(window.history.state, "", url);
2202
- } catch {
2203
- }
2204
- }
2205
- function CopyButton2({ value, label = "Copy" }) {
2206
- const [copied, setCopied] = useState8(false);
2207
- return /* @__PURE__ */ jsxs7(
2208
- "button",
2209
- {
2210
- type: "button",
2211
- className: clsx_default("lens-db__copy-btn", copied && "lens-db__copy-btn--done"),
2212
- "aria-label": copied ? "Copied" : label,
2213
- onClick: () => {
2214
- void navigator.clipboard?.writeText(value).then(
2215
- () => {
2216
- setCopied(true);
2217
- setTimeout(() => setCopied(false), 2e3);
2218
- },
2219
- () => void 0
2220
- );
2221
- },
2222
- children: [
2223
- copied ? /* @__PURE__ */ jsx11(IconCheck, { size: 14 }) : /* @__PURE__ */ jsx11(IconCopy, { size: 14 }),
2224
- copied ? "Copied" : label
2225
- ]
2226
- }
2227
- );
2228
- }
2229
- function LensAdmin(props) {
2230
- const contextConfig = useContext2(LensConfigContext);
2231
- const {
2232
- config = contextConfig ?? void 0,
2233
- baseUrl = "/api/lens",
2234
- projectsUrl = "/api/lens-projects",
2235
- projects: propProjects,
2236
- defaultTab = "reports",
2237
- theme,
2238
- className
2239
- } = props;
2240
- const direct = !!config;
2241
- const staticProjects = useMemo(
2242
- () => config?.projects.map(({ name, accessId }) => ({ name, accessId })) ?? propProjects,
2243
- [config, propProjects]
2244
- );
2245
- const wizardSecretRef = useRef4("");
2246
- const activeProjectRef = useRef4(null);
2247
- const configRef = useRef4(config);
2248
- configRef.current = config;
2249
- const api = useMemo(
2250
- () => direct ? createLensApi({
2251
- baseUrl: configRef.current?.apiUrl ?? HOSTED_LENS_API_URL,
2252
- getToken: () => {
2253
- const cfg = configRef.current;
2254
- const active = cfg?.projects.find((p) => p.accessId === activeProjectRef.current);
2255
- return wizardSecretRef.current || active?.secretKey || cfg?.adminToken || "";
2256
- }
2257
- }) : createLensApi({
2258
- baseUrl,
2259
- tokenHeader: "x-lens-token",
2260
- getToken: () => wizardSecretRef.current,
2261
- project: () => activeProjectRef.current
2262
- }),
2263
- [direct, baseUrl]
2264
- );
2265
- const [initialParams] = useState8(readInitialParams);
2266
- const [projects, setProjects] = useState8(staticProjects ?? []);
2267
- const [loadingProjects, setLoadingProjects] = useState8(!staticProjects);
2268
- const [activeAccessId, setActiveAccessId] = useState8(null);
2269
- const [firstReportId, setFirstReportId] = useState8(initialParams.report);
2270
- useEffect7(() => {
2271
- if (staticProjects) return;
2272
- let active = true;
2273
- setLoadingProjects(true);
2274
- fetch(projectsUrl, { headers: { Accept: "application/json" } }).then((res) => res.ok ? res.json() : Promise.reject(new Error(String(res.status)))).then((body) => {
2275
- if (!active) return;
2276
- const list = body && typeof body === "object" && Array.isArray(body.projects) ? body.projects.filter(
2277
- (p) => !!p && typeof p === "object" && typeof p.name === "string" && typeof p.accessId === "string"
2278
- ) : [];
2279
- setProjects(list);
2280
- setLoadingProjects(false);
2281
- }).catch(() => {
2282
- if (!active) return;
2283
- setProjects([]);
2284
- setLoadingProjects(false);
2285
- });
2286
- return () => {
2287
- active = false;
2288
- };
2289
- }, [projectsUrl, staticProjects]);
2290
- useEffect7(() => {
2291
- const first = projects[0];
2292
- if (!first) {
2293
- setActiveAccessId(null);
2294
- return;
2295
- }
2296
- setActiveAccessId((current) => {
2297
- if (current && projects.some((p) => p.accessId === current)) return current;
2298
- const fromUrl = initialParams.project && projects.find((p) => p.accessId === initialParams.project);
2299
- return fromUrl ? fromUrl.accessId : first.accessId;
2300
- });
2301
- }, [projects, initialParams.project]);
2302
- if (activeAccessId) activeProjectRef.current = activeAccessId;
2303
- const provisioning = !loadingProjects && projects.length === 0;
2304
- function switchProject(accessId) {
2305
- setFirstReportId(null);
2306
- setActiveAccessId(accessId);
2307
- }
2308
- return /* @__PURE__ */ jsx11("div", { className: clsx_default("lens-db", "lens-db__shell", className), children: /* @__PURE__ */ jsx11(LensDashboardProvider, { api, theme, children: loadingProjects ? /* @__PURE__ */ jsx11("div", { className: "lens-db__shell-loading", children: "Loading\u2026" }) : provisioning ? /* @__PURE__ */ jsx11(
2309
- Provisioning,
2310
- {
2311
- onProjectCreated: (res) => {
2312
- wizardSecretRef.current = res.accessSecret;
2313
- activeProjectRef.current = res.accessId;
2314
- }
2315
- }
2316
- ) : /* @__PURE__ */ jsx11(
2317
- DashboardSurface,
2318
- {
2319
- projects,
2320
- activeAccessId,
2321
- onSwitchProject: switchProject,
2322
- defaultTab,
2323
- initialReportId: firstReportId
2324
- },
2325
- activeAccessId ?? "none"
2326
- ) }) });
2327
- }
2328
- function Provisioning({
2329
- onProjectCreated
2330
- }) {
2331
- const [minted, setMinted] = useState8(
2332
- null
2333
- );
2334
- const envBlock = minted ? `LENS_PROJECTS=[${JSON.stringify({
2335
- name: minted.name,
2336
- accessId: minted.accessId,
2337
- secretKey: minted.secretKey
2338
- })}]` : "";
2339
- return /* @__PURE__ */ jsxs7("div", { className: "lens-db__shell-provision", children: [
2340
- /* @__PURE__ */ jsxs7("div", { className: "lens-db__shell-notice", children: [
2341
- /* @__PURE__ */ jsx11("p", { className: "lens-db__shell-notice-title", children: "No Lens projects configured" }),
2342
- /* @__PURE__ */ jsx11("p", { className: "lens-db__muted", children: "Create your first project below. The minted access secret is shown once \u2014 add it to your server environment to finish wiring up the proxy." })
2343
- ] }),
2344
- /* @__PURE__ */ jsx11(
2345
- OnboardingWizard,
2346
- {
2347
- onProjectCreated: (res) => {
2348
- onProjectCreated(res);
2349
- setMinted({
2350
- name: res.project.name,
2351
- accessId: res.accessId,
2352
- secretKey: res.accessSecret
2353
- });
2354
- }
2355
- }
2356
- ),
2357
- minted && /* @__PURE__ */ jsxs7("div", { className: "lens-db__shell-env", children: [
2358
- /* @__PURE__ */ jsx11("p", { className: "lens-db__label", children: "Add this to your server env and restart \u2014 the proxy reads it to route requests to the new project:" }),
2359
- /* @__PURE__ */ jsxs7("div", { className: "lens-db__codeblock", children: [
2360
- /* @__PURE__ */ jsx11("pre", { className: "lens-db__pre", children: highlightCode(envBlock, "env") }),
2361
- /* @__PURE__ */ jsx11(CopyButton2, { value: envBlock, label: "Copy" })
2362
- ] })
2363
- ] })
2364
- ] });
2365
- }
2366
- function DashboardSurface({
2367
- projects,
2368
- activeAccessId,
2369
- onSwitchProject,
2370
- defaultTab,
2371
- initialReportId
2372
- }) {
2373
- const [tab, setTab] = useState8(defaultTab);
2374
- const [selected, setSelected] = useState8(initialReportId);
2375
- useEffect7(() => {
2376
- syncReportParam(selected);
2377
- }, [selected]);
2378
- const showSwitcher = projects.length > 1;
2379
- return /* @__PURE__ */ jsxs7(Fragment5, { children: [
2380
- /* @__PURE__ */ jsxs7("div", { className: "lens-db__shell-bar", children: [
2381
- /* @__PURE__ */ jsx11("div", { className: "lens-db__shell-tabs", role: "tablist", "aria-label": "Lens sections", children: TABS.map(({ id, label, Icon }) => {
2382
- const active = id === tab;
2383
- return /* @__PURE__ */ jsxs7(
2384
- "button",
2385
- {
2386
- type: "button",
2387
- role: "tab",
2388
- "aria-selected": active,
2389
- className: clsx_default("lens-db__shell-tab", active && "lens-db__shell-tab--active"),
2390
- onClick: () => setTab(id),
2391
- children: [
2392
- /* @__PURE__ */ jsx11(Icon, { size: 16 }),
2393
- /* @__PURE__ */ jsx11("span", { className: "lens-db__shell-tab-label", children: label })
2394
- ]
2395
- },
2396
- id
2397
- );
2398
- }) }),
2399
- showSwitcher && /* @__PURE__ */ jsx11("div", { className: "lens-db__shell-switcher", children: /* @__PURE__ */ jsx11(
2400
- "select",
2401
- {
2402
- className: "lens-db__shell-select",
2403
- "aria-label": "Switch project",
2404
- value: activeAccessId ?? "",
2405
- onChange: (e) => onSwitchProject(e.target.value),
2406
- children: projects.map((p) => /* @__PURE__ */ jsx11("option", { value: p.accessId, children: p.name }, p.accessId))
2407
- }
2408
- ) })
2409
- ] }),
2410
- /* @__PURE__ */ jsxs7("div", { className: "lens-db__shell-content", children: [
2411
- tab === "reports" && (selected ? /* @__PURE__ */ jsx11(ReportDetail, { reportId: selected, onClose: () => setSelected(null) }) : /* @__PURE__ */ jsx11(ReportsTable, { onSelect: setSelected })),
2412
- tab === "integrations" && /* @__PURE__ */ jsx11(IntegrationSettings, {}),
2413
- tab === "setup" && /* @__PURE__ */ jsx11(OnboardingWizard, { existingProject: true })
2414
- ] })
2415
- ] });
2416
- }
2417
- var LensDashboard = LensAdmin;
2418
- function parseOrigins2(raw) {
2419
- return raw.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
2420
- }
2421
- function ProjectSettings(props) {
2422
- const api = useLensApi();
2423
- const { data, error, loading, reload } = useAsync(() => api.getProject(), []);
2424
- const [name, setName] = useState9("");
2425
- const [origins, setOrigins] = useState9("");
2426
- const [dashboardUrl, setDashboardUrl] = useState9("");
2427
- const [saving, setSaving] = useState9(false);
2428
- const [saveError, setSaveError] = useState9(void 0);
2429
- const [saved, setSaved] = useState9(false);
2430
- const [copied, setCopied] = useState9(false);
2431
- useEffect8(() => {
2432
- if (!data) return;
2433
- setName(data.name);
2434
- setOrigins(data.allowedOrigins.join("\n"));
2435
- setDashboardUrl(data.dashboardUrl ?? "");
2436
- }, [data]);
2437
- async function save() {
2438
- if (!name.trim()) {
2439
- setSaveError("Project name is required.");
2440
- return;
2441
- }
2442
- setSaving(true);
2443
- setSaveError(void 0);
2444
- setSaved(false);
2445
- try {
2446
- const p = await api.updateProject({
2447
- name: name.trim(),
2448
- allowedOrigins: parseOrigins2(origins),
2449
- dashboardUrl: dashboardUrl.trim() || null
2450
- });
2451
- setName(p.name);
2452
- setOrigins(p.allowedOrigins.join("\n"));
2453
- setDashboardUrl(p.dashboardUrl ?? "");
2454
- setSaved(true);
2455
- setTimeout(() => setSaved(false), 2e3);
2456
- } catch (err) {
2457
- setSaveError(err instanceof Error ? err.message : String(err));
2458
- } finally {
2459
- setSaving(false);
2460
- }
2461
- }
2462
- const accessId = data?.accessId ?? "";
2463
- return /* @__PURE__ */ jsxs8("div", { className: clsx_default("lens-db", "lens-db__panel", props.className), children: [
2464
- /* @__PURE__ */ jsxs8("div", { children: [
2465
- /* @__PURE__ */ jsx12("h3", { className: "lens-db__wizard-title", children: "Project settings" }),
2466
- /* @__PURE__ */ jsx12("p", { className: "lens-db__muted", children: "Update the project name and the origins allowed to submit reports." })
2467
- ] }),
2468
- loading && !data && /* @__PURE__ */ jsx12("p", { className: "lens-db__muted", children: "Loading project\u2026" }),
2469
- error && !data && /* @__PURE__ */ jsxs8("div", { className: "lens-db__error", children: [
2470
- "Failed to load project: ",
2471
- error.message,
2472
- " ",
2473
- /* @__PURE__ */ jsx12("button", { type: "button", className: "lens-db__link-btn", onClick: reload, children: "Retry" })
2474
- ] }),
2475
- data && /* @__PURE__ */ jsxs8(Fragment6, { children: [
2476
- /* @__PURE__ */ jsxs8("label", { className: "lens-db__field", children: [
2477
- /* @__PURE__ */ jsx12("span", { className: "lens-db__label", children: "Project name" }),
2478
- /* @__PURE__ */ jsx12(
2479
- "input",
2480
- {
2481
- className: "lens-db__input",
2482
- type: "text",
2483
- value: name,
2484
- disabled: saving,
2485
- placeholder: "Acme Web App",
2486
- onChange: (e) => setName(e.target.value)
2487
- }
2488
- )
2489
- ] }),
2490
- /* @__PURE__ */ jsxs8("label", { className: "lens-db__field", children: [
2491
- /* @__PURE__ */ jsx12("span", { className: "lens-db__label", children: "Allowed origins" }),
2492
- /* @__PURE__ */ jsx12(
2493
- "textarea",
2494
- {
2495
- className: "lens-db__input lens-db__textarea",
2496
- rows: 4,
2497
- value: origins,
2498
- disabled: saving,
2499
- placeholder: "https://app.acme.com\nhttps://admin.acme.com",
2500
- onChange: (e) => setOrigins(e.target.value)
2501
- }
2502
- ),
2503
- /* @__PURE__ */ jsx12("span", { className: "lens-db__hint", children: "One origin per line." })
2504
- ] }),
2505
- /* @__PURE__ */ jsxs8("label", { className: "lens-db__field", children: [
2506
- /* @__PURE__ */ jsx12("span", { className: "lens-db__label", children: "Dashboard URL" }),
2507
- /* @__PURE__ */ jsx12(
2508
- "input",
2509
- {
2510
- className: "lens-db__input",
2511
- type: "url",
2512
- value: dashboardUrl,
2513
- disabled: saving,
2514
- placeholder: "https://admin.acme.com/lens",
2515
- onChange: (e) => setDashboardUrl(e.target.value)
2516
- }
2517
- ),
2518
- /* @__PURE__ */ jsx12("span", { className: "lens-db__hint", children: "Issues link reports back to this page." })
2519
- ] }),
2520
- /* @__PURE__ */ jsxs8("div", { className: "lens-db__field", children: [
2521
- /* @__PURE__ */ jsx12("span", { className: "lens-db__label", children: "Access ID" }),
2522
- /* @__PURE__ */ jsxs8("div", { className: "lens-db__cred-row", children: [
2523
- /* @__PURE__ */ jsx12("code", { className: "lens-db__code-inline", children: accessId }),
2524
- /* @__PURE__ */ jsxs8(
2525
- "button",
2526
- {
2527
- type: "button",
2528
- className: clsx_default("lens-db__copy-btn", copied && "lens-db__copy-btn--done"),
2529
- "aria-label": copied ? "Copied" : "Copy access ID",
2530
- onClick: () => {
2531
- void navigator.clipboard?.writeText(accessId).then(
2532
- () => {
2533
- setCopied(true);
2534
- setTimeout(() => setCopied(false), 2e3);
2535
- },
2536
- () => void 0
2537
- );
2538
- },
2539
- children: [
2540
- copied ? /* @__PURE__ */ jsx12(IconCheck, { size: 14 }) : /* @__PURE__ */ jsx12(IconCopy, { size: 14 }),
2541
- copied ? "Copied" : "Copy"
2542
- ]
2543
- }
2544
- )
2545
- ] })
2546
- ] }),
2547
- saveError && /* @__PURE__ */ jsx12("div", { className: "lens-db__error", children: saveError }),
2548
- /* @__PURE__ */ jsxs8("div", { className: "lens-db__panel-actions", children: [
2549
- /* @__PURE__ */ jsx12(
2550
- "button",
2551
- {
2552
- type: "button",
2553
- className: "lens-db__btn lens-db__btn--primary",
2554
- disabled: saving,
2555
- onClick: () => void save(),
2556
- children: saving ? "Saving\u2026" : "Save"
2557
- }
2558
- ),
2559
- saved && /* @__PURE__ */ jsxs8("span", { className: "lens-db__flash lens-db__flash--ok", children: [
2560
- /* @__PURE__ */ jsx12(IconCheck, { size: 14 }),
2561
- "Saved"
2562
- ] })
2563
- ] })
2564
- ] })
2565
- ] });
2566
- }
2567
- export {
2568
- IntegrationSettings,
2569
- LensAdmin,
2570
- LensApiError,
2571
- LensDashboard,
2572
- LensDashboardProvider,
2573
- LensProvider,
2574
- OnboardingWizard,
2575
- ProjectSettings,
2576
- ReplayPlayer,
2577
- ReportDetail,
2578
- ReportsTable,
2579
- StatusBadge,
2580
- createLensApi,
2581
- useAsync,
2582
- useLensApi
2583
- };
2584
- //# sourceMappingURL=dashboard.js.map