rails-profiler 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/app/assets/builds/profiler-toolbar.js +1191 -0
  3. data/app/assets/builds/profiler.css +2668 -0
  4. data/app/assets/builds/profiler.js +2772 -0
  5. data/app/controllers/profiler/api/ajax_controller.rb +36 -0
  6. data/app/controllers/profiler/api/jobs_controller.rb +39 -0
  7. data/app/controllers/profiler/api/outbound_http_controller.rb +36 -0
  8. data/app/controllers/profiler/api/profiles_controller.rb +60 -0
  9. data/app/controllers/profiler/api/toolbar_controller.rb +44 -0
  10. data/app/controllers/profiler/application_controller.rb +19 -0
  11. data/app/controllers/profiler/assets_controller.rb +29 -0
  12. data/app/controllers/profiler/profiles_controller.rb +107 -0
  13. data/app/views/layouts/profiler/application.html.erb +16 -0
  14. data/app/views/layouts/profiler/embedded.html.erb +34 -0
  15. data/app/views/profiler/profiles/index.html.erb +1 -0
  16. data/app/views/profiler/profiles/show.html.erb +4 -0
  17. data/config/routes.rb +36 -0
  18. data/exe/profiler-mcp +8 -0
  19. data/lib/profiler/collectors/ajax_collector.rb +109 -0
  20. data/lib/profiler/collectors/base_collector.rb +92 -0
  21. data/lib/profiler/collectors/cache_collector.rb +96 -0
  22. data/lib/profiler/collectors/database_collector.rb +113 -0
  23. data/lib/profiler/collectors/dump_collector.rb +98 -0
  24. data/lib/profiler/collectors/flamegraph_collector.rb +182 -0
  25. data/lib/profiler/collectors/http_collector.rb +112 -0
  26. data/lib/profiler/collectors/job_collector.rb +50 -0
  27. data/lib/profiler/collectors/performance_collector.rb +103 -0
  28. data/lib/profiler/collectors/request_collector.rb +80 -0
  29. data/lib/profiler/collectors/view_collector.rb +79 -0
  30. data/lib/profiler/configuration.rb +81 -0
  31. data/lib/profiler/engine.rb +17 -0
  32. data/lib/profiler/instrumentation/active_job_instrumentation.rb +22 -0
  33. data/lib/profiler/instrumentation/net_http_instrumentation.rb +153 -0
  34. data/lib/profiler/instrumentation/sidekiq_middleware.rb +18 -0
  35. data/lib/profiler/job_profiler.rb +118 -0
  36. data/lib/profiler/mcp/resources/n1_patterns.rb +62 -0
  37. data/lib/profiler/mcp/resources/recent_jobs.rb +39 -0
  38. data/lib/profiler/mcp/resources/recent_requests.rb +35 -0
  39. data/lib/profiler/mcp/resources/slow_queries.rb +47 -0
  40. data/lib/profiler/mcp/server.rb +217 -0
  41. data/lib/profiler/mcp/tools/analyze_queries.rb +124 -0
  42. data/lib/profiler/mcp/tools/clear_profiles.rb +22 -0
  43. data/lib/profiler/mcp/tools/get_profile_ajax.rb +66 -0
  44. data/lib/profiler/mcp/tools/get_profile_detail.rb +326 -0
  45. data/lib/profiler/mcp/tools/get_profile_dumps.rb +51 -0
  46. data/lib/profiler/mcp/tools/get_profile_http.rb +104 -0
  47. data/lib/profiler/mcp/tools/query_jobs.rb +60 -0
  48. data/lib/profiler/mcp/tools/query_profiles.rb +66 -0
  49. data/lib/profiler/middleware/cors_middleware.rb +55 -0
  50. data/lib/profiler/middleware/profiler_middleware.rb +151 -0
  51. data/lib/profiler/middleware/toolbar_injector.rb +378 -0
  52. data/lib/profiler/models/profile.rb +182 -0
  53. data/lib/profiler/models/sql_query.rb +48 -0
  54. data/lib/profiler/models/timeline_event.rb +40 -0
  55. data/lib/profiler/railtie.rb +75 -0
  56. data/lib/profiler/storage/base_store.rb +41 -0
  57. data/lib/profiler/storage/blob_store.rb +46 -0
  58. data/lib/profiler/storage/file_store.rb +119 -0
  59. data/lib/profiler/storage/memory_store.rb +94 -0
  60. data/lib/profiler/storage/redis_store.rb +98 -0
  61. data/lib/profiler/storage/sqlite_store.rb +272 -0
  62. data/lib/profiler/tasks/profiler.rake +79 -0
  63. data/lib/profiler/version.rb +5 -0
  64. data/lib/profiler.rb +68 -0
  65. metadata +194 -0
@@ -0,0 +1,2772 @@
1
+ "use strict";
2
+ (() => {
3
+ // node_modules/preact/dist/preact.module.js
4
+ var n;
5
+ var l;
6
+ var u;
7
+ var t;
8
+ var i;
9
+ var r;
10
+ var o;
11
+ var e;
12
+ var f;
13
+ var c;
14
+ var s;
15
+ var a;
16
+ var h;
17
+ var p = {};
18
+ var v = [];
19
+ var y = /acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;
20
+ var d = Array.isArray;
21
+ function w(n2, l3) {
22
+ for (var u4 in l3) n2[u4] = l3[u4];
23
+ return n2;
24
+ }
25
+ function g(n2) {
26
+ n2 && n2.parentNode && n2.parentNode.removeChild(n2);
27
+ }
28
+ function _(l3, u4, t3) {
29
+ var i3, r3, o3, e3 = {};
30
+ for (o3 in u4) "key" == o3 ? i3 = u4[o3] : "ref" == o3 ? r3 = u4[o3] : e3[o3] = u4[o3];
31
+ if (arguments.length > 2 && (e3.children = arguments.length > 3 ? n.call(arguments, 2) : t3), "function" == typeof l3 && null != l3.defaultProps) for (o3 in l3.defaultProps) void 0 === e3[o3] && (e3[o3] = l3.defaultProps[o3]);
32
+ return m(l3, e3, i3, r3, null);
33
+ }
34
+ function m(n2, t3, i3, r3, o3) {
35
+ var e3 = { type: n2, props: t3, key: i3, ref: r3, __k: null, __: null, __b: 0, __e: null, __c: null, constructor: void 0, __v: null == o3 ? ++u : o3, __i: -1, __u: 0 };
36
+ return null == o3 && null != l.vnode && l.vnode(e3), e3;
37
+ }
38
+ function k(n2) {
39
+ return n2.children;
40
+ }
41
+ function x(n2, l3) {
42
+ this.props = n2, this.context = l3;
43
+ }
44
+ function S(n2, l3) {
45
+ if (null == l3) return n2.__ ? S(n2.__, n2.__i + 1) : null;
46
+ for (var u4; l3 < n2.__k.length; l3++) if (null != (u4 = n2.__k[l3]) && null != u4.__e) return u4.__e;
47
+ return "function" == typeof n2.type ? S(n2) : null;
48
+ }
49
+ function C(n2) {
50
+ if (n2.__P && n2.__d) {
51
+ var u4 = n2.__v, t3 = u4.__e, i3 = [], r3 = [], o3 = w({}, u4);
52
+ o3.__v = u4.__v + 1, l.vnode && l.vnode(o3), z(n2.__P, o3, u4, n2.__n, n2.__P.namespaceURI, 32 & u4.__u ? [t3] : null, i3, null == t3 ? S(u4) : t3, !!(32 & u4.__u), r3), o3.__v = u4.__v, o3.__.__k[o3.__i] = o3, V(i3, o3, r3), u4.__e = u4.__ = null, o3.__e != t3 && M(o3);
53
+ }
54
+ }
55
+ function M(n2) {
56
+ if (null != (n2 = n2.__) && null != n2.__c) return n2.__e = n2.__c.base = null, n2.__k.some(function(l3) {
57
+ if (null != l3 && null != l3.__e) return n2.__e = n2.__c.base = l3.__e;
58
+ }), M(n2);
59
+ }
60
+ function $(n2) {
61
+ (!n2.__d && (n2.__d = true) && i.push(n2) && !I.__r++ || r != l.debounceRendering) && ((r = l.debounceRendering) || o)(I);
62
+ }
63
+ function I() {
64
+ try {
65
+ for (var n2, l3 = 1; i.length; ) i.length > l3 && i.sort(e), n2 = i.shift(), l3 = i.length, C(n2);
66
+ } finally {
67
+ i.length = I.__r = 0;
68
+ }
69
+ }
70
+ function P(n2, l3, u4, t3, i3, r3, o3, e3, f4, c3, s3) {
71
+ var a3, h3, y3, d3, w3, g2, _2, m3 = t3 && t3.__k || v, b = l3.length;
72
+ for (f4 = A(u4, l3, m3, f4, b), a3 = 0; a3 < b; a3++) null != (y3 = u4.__k[a3]) && (h3 = -1 != y3.__i && m3[y3.__i] || p, y3.__i = a3, g2 = z(n2, y3, h3, i3, r3, o3, e3, f4, c3, s3), d3 = y3.__e, y3.ref && h3.ref != y3.ref && (h3.ref && D(h3.ref, null, y3), s3.push(y3.ref, y3.__c || d3, y3)), null == w3 && null != d3 && (w3 = d3), (_2 = !!(4 & y3.__u)) || h3.__k === y3.__k ? f4 = H(y3, f4, n2, _2) : "function" == typeof y3.type && void 0 !== g2 ? f4 = g2 : d3 && (f4 = d3.nextSibling), y3.__u &= -7);
73
+ return u4.__e = w3, f4;
74
+ }
75
+ function A(n2, l3, u4, t3, i3) {
76
+ var r3, o3, e3, f4, c3, s3 = u4.length, a3 = s3, h3 = 0;
77
+ for (n2.__k = new Array(i3), r3 = 0; r3 < i3; r3++) null != (o3 = l3[r3]) && "boolean" != typeof o3 && "function" != typeof o3 ? ("string" == typeof o3 || "number" == typeof o3 || "bigint" == typeof o3 || o3.constructor == String ? o3 = n2.__k[r3] = m(null, o3, null, null, null) : d(o3) ? o3 = n2.__k[r3] = m(k, { children: o3 }, null, null, null) : void 0 === o3.constructor && o3.__b > 0 ? o3 = n2.__k[r3] = m(o3.type, o3.props, o3.key, o3.ref ? o3.ref : null, o3.__v) : n2.__k[r3] = o3, f4 = r3 + h3, o3.__ = n2, o3.__b = n2.__b + 1, e3 = null, -1 != (c3 = o3.__i = T(o3, u4, f4, a3)) && (a3--, (e3 = u4[c3]) && (e3.__u |= 2)), null == e3 || null == e3.__v ? (-1 == c3 && (i3 > s3 ? h3-- : i3 < s3 && h3++), "function" != typeof o3.type && (o3.__u |= 4)) : c3 != f4 && (c3 == f4 - 1 ? h3-- : c3 == f4 + 1 ? h3++ : (c3 > f4 ? h3-- : h3++, o3.__u |= 4))) : n2.__k[r3] = null;
78
+ if (a3) for (r3 = 0; r3 < s3; r3++) null != (e3 = u4[r3]) && 0 == (2 & e3.__u) && (e3.__e == t3 && (t3 = S(e3)), E(e3, e3));
79
+ return t3;
80
+ }
81
+ function H(n2, l3, u4, t3) {
82
+ var i3, r3;
83
+ if ("function" == typeof n2.type) {
84
+ for (i3 = n2.__k, r3 = 0; i3 && r3 < i3.length; r3++) i3[r3] && (i3[r3].__ = n2, l3 = H(i3[r3], l3, u4, t3));
85
+ return l3;
86
+ }
87
+ n2.__e != l3 && (t3 && (l3 && n2.type && !l3.parentNode && (l3 = S(n2)), u4.insertBefore(n2.__e, l3 || null)), l3 = n2.__e);
88
+ do {
89
+ l3 = l3 && l3.nextSibling;
90
+ } while (null != l3 && 8 == l3.nodeType);
91
+ return l3;
92
+ }
93
+ function T(n2, l3, u4, t3) {
94
+ var i3, r3, o3, e3 = n2.key, f4 = n2.type, c3 = l3[u4], s3 = null != c3 && 0 == (2 & c3.__u);
95
+ if (null === c3 && null == e3 || s3 && e3 == c3.key && f4 == c3.type) return u4;
96
+ if (t3 > (s3 ? 1 : 0)) {
97
+ for (i3 = u4 - 1, r3 = u4 + 1; i3 >= 0 || r3 < l3.length; ) if (null != (c3 = l3[o3 = i3 >= 0 ? i3-- : r3++]) && 0 == (2 & c3.__u) && e3 == c3.key && f4 == c3.type) return o3;
98
+ }
99
+ return -1;
100
+ }
101
+ function j(n2, l3, u4) {
102
+ "-" == l3[0] ? n2.setProperty(l3, null == u4 ? "" : u4) : n2[l3] = null == u4 ? "" : "number" != typeof u4 || y.test(l3) ? u4 : u4 + "px";
103
+ }
104
+ function F(n2, l3, u4, t3, i3) {
105
+ var r3, o3;
106
+ n: if ("style" == l3) if ("string" == typeof u4) n2.style.cssText = u4;
107
+ else {
108
+ if ("string" == typeof t3 && (n2.style.cssText = t3 = ""), t3) for (l3 in t3) u4 && l3 in u4 || j(n2.style, l3, "");
109
+ if (u4) for (l3 in u4) t3 && u4[l3] == t3[l3] || j(n2.style, l3, u4[l3]);
110
+ }
111
+ else if ("o" == l3[0] && "n" == l3[1]) r3 = l3 != (l3 = l3.replace(f, "$1")), o3 = l3.toLowerCase(), l3 = o3 in n2 || "onFocusOut" == l3 || "onFocusIn" == l3 ? o3.slice(2) : l3.slice(2), n2.l || (n2.l = {}), n2.l[l3 + r3] = u4, u4 ? t3 ? u4.u = t3.u : (u4.u = c, n2.addEventListener(l3, r3 ? a : s, r3)) : n2.removeEventListener(l3, r3 ? a : s, r3);
112
+ else {
113
+ if ("http://www.w3.org/2000/svg" == i3) l3 = l3.replace(/xlink(H|:h)/, "h").replace(/sName$/, "s");
114
+ else if ("width" != l3 && "height" != l3 && "href" != l3 && "list" != l3 && "form" != l3 && "tabIndex" != l3 && "download" != l3 && "rowSpan" != l3 && "colSpan" != l3 && "role" != l3 && "popover" != l3 && l3 in n2) try {
115
+ n2[l3] = null == u4 ? "" : u4;
116
+ break n;
117
+ } catch (n3) {
118
+ }
119
+ "function" == typeof u4 || (null == u4 || false === u4 && "-" != l3[4] ? n2.removeAttribute(l3) : n2.setAttribute(l3, "popover" == l3 && 1 == u4 ? "" : u4));
120
+ }
121
+ }
122
+ function O(n2) {
123
+ return function(u4) {
124
+ if (this.l) {
125
+ var t3 = this.l[u4.type + n2];
126
+ if (null == u4.t) u4.t = c++;
127
+ else if (u4.t < t3.u) return;
128
+ return t3(l.event ? l.event(u4) : u4);
129
+ }
130
+ };
131
+ }
132
+ function z(n2, u4, t3, i3, r3, o3, e3, f4, c3, s3) {
133
+ var a3, h3, p3, y3, _2, m3, b, S2, C3, M2, $2, I2, A3, H2, L, T3 = u4.type;
134
+ if (void 0 !== u4.constructor) return null;
135
+ 128 & t3.__u && (c3 = !!(32 & t3.__u), o3 = [f4 = u4.__e = t3.__e]), (a3 = l.__b) && a3(u4);
136
+ n: if ("function" == typeof T3) try {
137
+ if (S2 = u4.props, C3 = T3.prototype && T3.prototype.render, M2 = (a3 = T3.contextType) && i3[a3.__c], $2 = a3 ? M2 ? M2.props.value : a3.__ : i3, t3.__c ? b = (h3 = u4.__c = t3.__c).__ = h3.__E : (C3 ? u4.__c = h3 = new T3(S2, $2) : (u4.__c = h3 = new x(S2, $2), h3.constructor = T3, h3.render = G), M2 && M2.sub(h3), h3.state || (h3.state = {}), h3.__n = i3, p3 = h3.__d = true, h3.__h = [], h3._sb = []), C3 && null == h3.__s && (h3.__s = h3.state), C3 && null != T3.getDerivedStateFromProps && (h3.__s == h3.state && (h3.__s = w({}, h3.__s)), w(h3.__s, T3.getDerivedStateFromProps(S2, h3.__s))), y3 = h3.props, _2 = h3.state, h3.__v = u4, p3) C3 && null == T3.getDerivedStateFromProps && null != h3.componentWillMount && h3.componentWillMount(), C3 && null != h3.componentDidMount && h3.__h.push(h3.componentDidMount);
138
+ else {
139
+ if (C3 && null == T3.getDerivedStateFromProps && S2 !== y3 && null != h3.componentWillReceiveProps && h3.componentWillReceiveProps(S2, $2), u4.__v == t3.__v || !h3.__e && null != h3.shouldComponentUpdate && false === h3.shouldComponentUpdate(S2, h3.__s, $2)) {
140
+ u4.__v != t3.__v && (h3.props = S2, h3.state = h3.__s, h3.__d = false), u4.__e = t3.__e, u4.__k = t3.__k, u4.__k.some(function(n3) {
141
+ n3 && (n3.__ = u4);
142
+ }), v.push.apply(h3.__h, h3._sb), h3._sb = [], h3.__h.length && e3.push(h3);
143
+ break n;
144
+ }
145
+ null != h3.componentWillUpdate && h3.componentWillUpdate(S2, h3.__s, $2), C3 && null != h3.componentDidUpdate && h3.__h.push(function() {
146
+ h3.componentDidUpdate(y3, _2, m3);
147
+ });
148
+ }
149
+ if (h3.context = $2, h3.props = S2, h3.__P = n2, h3.__e = false, I2 = l.__r, A3 = 0, C3) h3.state = h3.__s, h3.__d = false, I2 && I2(u4), a3 = h3.render(h3.props, h3.state, h3.context), v.push.apply(h3.__h, h3._sb), h3._sb = [];
150
+ else do {
151
+ h3.__d = false, I2 && I2(u4), a3 = h3.render(h3.props, h3.state, h3.context), h3.state = h3.__s;
152
+ } while (h3.__d && ++A3 < 25);
153
+ h3.state = h3.__s, null != h3.getChildContext && (i3 = w(w({}, i3), h3.getChildContext())), C3 && !p3 && null != h3.getSnapshotBeforeUpdate && (m3 = h3.getSnapshotBeforeUpdate(y3, _2)), H2 = null != a3 && a3.type === k && null == a3.key ? q(a3.props.children) : a3, f4 = P(n2, d(H2) ? H2 : [H2], u4, t3, i3, r3, o3, e3, f4, c3, s3), h3.base = u4.__e, u4.__u &= -161, h3.__h.length && e3.push(h3), b && (h3.__E = h3.__ = null);
154
+ } catch (n3) {
155
+ if (u4.__v = null, c3 || null != o3) if (n3.then) {
156
+ for (u4.__u |= c3 ? 160 : 128; f4 && 8 == f4.nodeType && f4.nextSibling; ) f4 = f4.nextSibling;
157
+ o3[o3.indexOf(f4)] = null, u4.__e = f4;
158
+ } else {
159
+ for (L = o3.length; L--; ) g(o3[L]);
160
+ N(u4);
161
+ }
162
+ else u4.__e = t3.__e, u4.__k = t3.__k, n3.then || N(u4);
163
+ l.__e(n3, u4, t3);
164
+ }
165
+ else null == o3 && u4.__v == t3.__v ? (u4.__k = t3.__k, u4.__e = t3.__e) : f4 = u4.__e = B(t3.__e, u4, t3, i3, r3, o3, e3, c3, s3);
166
+ return (a3 = l.diffed) && a3(u4), 128 & u4.__u ? void 0 : f4;
167
+ }
168
+ function N(n2) {
169
+ n2 && (n2.__c && (n2.__c.__e = true), n2.__k && n2.__k.some(N));
170
+ }
171
+ function V(n2, u4, t3) {
172
+ for (var i3 = 0; i3 < t3.length; i3++) D(t3[i3], t3[++i3], t3[++i3]);
173
+ l.__c && l.__c(u4, n2), n2.some(function(u5) {
174
+ try {
175
+ n2 = u5.__h, u5.__h = [], n2.some(function(n3) {
176
+ n3.call(u5);
177
+ });
178
+ } catch (n3) {
179
+ l.__e(n3, u5.__v);
180
+ }
181
+ });
182
+ }
183
+ function q(n2) {
184
+ return "object" != typeof n2 || null == n2 || n2.__b > 0 ? n2 : d(n2) ? n2.map(q) : w({}, n2);
185
+ }
186
+ function B(u4, t3, i3, r3, o3, e3, f4, c3, s3) {
187
+ var a3, h3, v3, y3, w3, _2, m3, b = i3.props || p, k3 = t3.props, x2 = t3.type;
188
+ if ("svg" == x2 ? o3 = "http://www.w3.org/2000/svg" : "math" == x2 ? o3 = "http://www.w3.org/1998/Math/MathML" : o3 || (o3 = "http://www.w3.org/1999/xhtml"), null != e3) {
189
+ for (a3 = 0; a3 < e3.length; a3++) if ((w3 = e3[a3]) && "setAttribute" in w3 == !!x2 && (x2 ? w3.localName == x2 : 3 == w3.nodeType)) {
190
+ u4 = w3, e3[a3] = null;
191
+ break;
192
+ }
193
+ }
194
+ if (null == u4) {
195
+ if (null == x2) return document.createTextNode(k3);
196
+ u4 = document.createElementNS(o3, x2, k3.is && k3), c3 && (l.__m && l.__m(t3, e3), c3 = false), e3 = null;
197
+ }
198
+ if (null == x2) b === k3 || c3 && u4.data == k3 || (u4.data = k3);
199
+ else {
200
+ if (e3 = e3 && n.call(u4.childNodes), !c3 && null != e3) for (b = {}, a3 = 0; a3 < u4.attributes.length; a3++) b[(w3 = u4.attributes[a3]).name] = w3.value;
201
+ for (a3 in b) w3 = b[a3], "dangerouslySetInnerHTML" == a3 ? v3 = w3 : "children" == a3 || a3 in k3 || "value" == a3 && "defaultValue" in k3 || "checked" == a3 && "defaultChecked" in k3 || F(u4, a3, null, w3, o3);
202
+ for (a3 in k3) w3 = k3[a3], "children" == a3 ? y3 = w3 : "dangerouslySetInnerHTML" == a3 ? h3 = w3 : "value" == a3 ? _2 = w3 : "checked" == a3 ? m3 = w3 : c3 && "function" != typeof w3 || b[a3] === w3 || F(u4, a3, w3, b[a3], o3);
203
+ if (h3) c3 || v3 && (h3.__html == v3.__html || h3.__html == u4.innerHTML) || (u4.innerHTML = h3.__html), t3.__k = [];
204
+ else if (v3 && (u4.innerHTML = ""), P("template" == t3.type ? u4.content : u4, d(y3) ? y3 : [y3], t3, i3, r3, "foreignObject" == x2 ? "http://www.w3.org/1999/xhtml" : o3, e3, f4, e3 ? e3[0] : i3.__k && S(i3, 0), c3, s3), null != e3) for (a3 = e3.length; a3--; ) g(e3[a3]);
205
+ c3 || (a3 = "value", "progress" == x2 && null == _2 ? u4.removeAttribute("value") : null != _2 && (_2 !== u4[a3] || "progress" == x2 && !_2 || "option" == x2 && _2 != b[a3]) && F(u4, a3, _2, b[a3], o3), a3 = "checked", null != m3 && m3 != u4[a3] && F(u4, a3, m3, b[a3], o3));
206
+ }
207
+ return u4;
208
+ }
209
+ function D(n2, u4, t3) {
210
+ try {
211
+ if ("function" == typeof n2) {
212
+ var i3 = "function" == typeof n2.__u;
213
+ i3 && n2.__u(), i3 && null == u4 || (n2.__u = n2(u4));
214
+ } else n2.current = u4;
215
+ } catch (n3) {
216
+ l.__e(n3, t3);
217
+ }
218
+ }
219
+ function E(n2, u4, t3) {
220
+ var i3, r3;
221
+ if (l.unmount && l.unmount(n2), (i3 = n2.ref) && (i3.current && i3.current != n2.__e || D(i3, null, u4)), null != (i3 = n2.__c)) {
222
+ if (i3.componentWillUnmount) try {
223
+ i3.componentWillUnmount();
224
+ } catch (n3) {
225
+ l.__e(n3, u4);
226
+ }
227
+ i3.base = i3.__P = null;
228
+ }
229
+ if (i3 = n2.__k) for (r3 = 0; r3 < i3.length; r3++) i3[r3] && E(i3[r3], u4, t3 || "function" != typeof n2.type);
230
+ t3 || g(n2.__e), n2.__c = n2.__ = n2.__e = void 0;
231
+ }
232
+ function G(n2, l3, u4) {
233
+ return this.constructor(n2, u4);
234
+ }
235
+ function J(u4, t3, i3) {
236
+ var r3, o3, e3, f4;
237
+ t3 == document && (t3 = document.documentElement), l.__ && l.__(u4, t3), o3 = (r3 = "function" == typeof i3) ? null : i3 && i3.__k || t3.__k, e3 = [], f4 = [], z(t3, u4 = (!r3 && i3 || t3).__k = _(k, null, [u4]), o3 || p, p, t3.namespaceURI, !r3 && i3 ? [i3] : o3 ? null : t3.firstChild ? n.call(t3.childNodes) : null, e3, !r3 && i3 ? i3 : o3 ? o3.__e : t3.firstChild, r3, f4), V(e3, u4, f4);
238
+ }
239
+ n = v.slice, l = { __e: function(n2, l3, u4, t3) {
240
+ for (var i3, r3, o3; l3 = l3.__; ) if ((i3 = l3.__c) && !i3.__) try {
241
+ if ((r3 = i3.constructor) && null != r3.getDerivedStateFromError && (i3.setState(r3.getDerivedStateFromError(n2)), o3 = i3.__d), null != i3.componentDidCatch && (i3.componentDidCatch(n2, t3 || {}), o3 = i3.__d), o3) return i3.__E = i3;
242
+ } catch (l4) {
243
+ n2 = l4;
244
+ }
245
+ throw n2;
246
+ } }, u = 0, t = function(n2) {
247
+ return null != n2 && void 0 === n2.constructor;
248
+ }, x.prototype.setState = function(n2, l3) {
249
+ var u4;
250
+ u4 = null != this.__s && this.__s != this.state ? this.__s : this.__s = w({}, this.state), "function" == typeof n2 && (n2 = n2(w({}, u4), this.props)), n2 && w(u4, n2), null != n2 && this.__v && (l3 && this._sb.push(l3), $(this));
251
+ }, x.prototype.forceUpdate = function(n2) {
252
+ this.__v && (this.__e = true, n2 && this.__h.push(n2), $(this));
253
+ }, x.prototype.render = k, i = [], o = "function" == typeof Promise ? Promise.prototype.then.bind(Promise.resolve()) : setTimeout, e = function(n2, l3) {
254
+ return n2.__v.__b - l3.__v.__b;
255
+ }, I.__r = 0, f = /(PointerCapture)$|Capture$/i, c = 0, s = O(false), a = O(true), h = 0;
256
+
257
+ // node_modules/preact/hooks/dist/hooks.module.js
258
+ var t2;
259
+ var r2;
260
+ var u2;
261
+ var i2;
262
+ var o2 = 0;
263
+ var f2 = [];
264
+ var c2 = l;
265
+ var e2 = c2.__b;
266
+ var a2 = c2.__r;
267
+ var v2 = c2.diffed;
268
+ var l2 = c2.__c;
269
+ var m2 = c2.unmount;
270
+ var s2 = c2.__;
271
+ function p2(n2, t3) {
272
+ c2.__h && c2.__h(r2, n2, o2 || t3), o2 = 0;
273
+ var u4 = r2.__H || (r2.__H = { __: [], __h: [] });
274
+ return n2 >= u4.__.length && u4.__.push({}), u4.__[n2];
275
+ }
276
+ function d2(n2) {
277
+ return o2 = 1, h2(D2, n2);
278
+ }
279
+ function h2(n2, u4, i3) {
280
+ var o3 = p2(t2++, 2);
281
+ if (o3.t = n2, !o3.__c && (o3.__ = [i3 ? i3(u4) : D2(void 0, u4), function(n3) {
282
+ var t3 = o3.__N ? o3.__N[0] : o3.__[0], r3 = o3.t(t3, n3);
283
+ t3 !== r3 && (o3.__N = [r3, o3.__[1]], o3.__c.setState({}));
284
+ }], o3.__c = r2, !r2.__f)) {
285
+ var f4 = function(n3, t3, r3) {
286
+ if (!o3.__c.__H) return true;
287
+ var u5 = o3.__c.__H.__.filter(function(n4) {
288
+ return n4.__c;
289
+ });
290
+ if (u5.every(function(n4) {
291
+ return !n4.__N;
292
+ })) return !c3 || c3.call(this, n3, t3, r3);
293
+ var i4 = o3.__c.props !== n3;
294
+ return u5.some(function(n4) {
295
+ if (n4.__N) {
296
+ var t4 = n4.__[0];
297
+ n4.__ = n4.__N, n4.__N = void 0, t4 !== n4.__[0] && (i4 = true);
298
+ }
299
+ }), c3 && c3.call(this, n3, t3, r3) || i4;
300
+ };
301
+ r2.__f = true;
302
+ var c3 = r2.shouldComponentUpdate, e3 = r2.componentWillUpdate;
303
+ r2.componentWillUpdate = function(n3, t3, r3) {
304
+ if (this.__e) {
305
+ var u5 = c3;
306
+ c3 = void 0, f4(n3, t3, r3), c3 = u5;
307
+ }
308
+ e3 && e3.call(this, n3, t3, r3);
309
+ }, r2.shouldComponentUpdate = f4;
310
+ }
311
+ return o3.__N || o3.__;
312
+ }
313
+ function y2(n2, u4) {
314
+ var i3 = p2(t2++, 3);
315
+ !c2.__s && C2(i3.__H, u4) && (i3.__ = n2, i3.u = u4, r2.__H.__h.push(i3));
316
+ }
317
+ function A2(n2) {
318
+ return o2 = 5, T2(function() {
319
+ return { current: n2 };
320
+ }, []);
321
+ }
322
+ function T2(n2, r3) {
323
+ var u4 = p2(t2++, 7);
324
+ return C2(u4.__H, r3) && (u4.__ = n2(), u4.__H = r3, u4.__h = n2), u4.__;
325
+ }
326
+ function j2() {
327
+ for (var n2; n2 = f2.shift(); ) {
328
+ var t3 = n2.__H;
329
+ if (n2.__P && t3) try {
330
+ t3.__h.some(z2), t3.__h.some(B2), t3.__h = [];
331
+ } catch (r3) {
332
+ t3.__h = [], c2.__e(r3, n2.__v);
333
+ }
334
+ }
335
+ }
336
+ c2.__b = function(n2) {
337
+ r2 = null, e2 && e2(n2);
338
+ }, c2.__ = function(n2, t3) {
339
+ n2 && t3.__k && t3.__k.__m && (n2.__m = t3.__k.__m), s2 && s2(n2, t3);
340
+ }, c2.__r = function(n2) {
341
+ a2 && a2(n2), t2 = 0;
342
+ var i3 = (r2 = n2.__c).__H;
343
+ i3 && (u2 === r2 ? (i3.__h = [], r2.__h = [], i3.__.some(function(n3) {
344
+ n3.__N && (n3.__ = n3.__N), n3.u = n3.__N = void 0;
345
+ })) : (i3.__h.some(z2), i3.__h.some(B2), i3.__h = [], t2 = 0)), u2 = r2;
346
+ }, c2.diffed = function(n2) {
347
+ v2 && v2(n2);
348
+ var t3 = n2.__c;
349
+ t3 && t3.__H && (t3.__H.__h.length && (1 !== f2.push(t3) && i2 === c2.requestAnimationFrame || ((i2 = c2.requestAnimationFrame) || w2)(j2)), t3.__H.__.some(function(n3) {
350
+ n3.u && (n3.__H = n3.u), n3.u = void 0;
351
+ })), u2 = r2 = null;
352
+ }, c2.__c = function(n2, t3) {
353
+ t3.some(function(n3) {
354
+ try {
355
+ n3.__h.some(z2), n3.__h = n3.__h.filter(function(n4) {
356
+ return !n4.__ || B2(n4);
357
+ });
358
+ } catch (r3) {
359
+ t3.some(function(n4) {
360
+ n4.__h && (n4.__h = []);
361
+ }), t3 = [], c2.__e(r3, n3.__v);
362
+ }
363
+ }), l2 && l2(n2, t3);
364
+ }, c2.unmount = function(n2) {
365
+ m2 && m2(n2);
366
+ var t3, r3 = n2.__c;
367
+ r3 && r3.__H && (r3.__H.__.some(function(n3) {
368
+ try {
369
+ z2(n3);
370
+ } catch (n4) {
371
+ t3 = n4;
372
+ }
373
+ }), r3.__H = void 0, t3 && c2.__e(t3, r3.__v));
374
+ };
375
+ var k2 = "function" == typeof requestAnimationFrame;
376
+ function w2(n2) {
377
+ var t3, r3 = function() {
378
+ clearTimeout(u4), k2 && cancelAnimationFrame(t3), setTimeout(n2);
379
+ }, u4 = setTimeout(r3, 35);
380
+ k2 && (t3 = requestAnimationFrame(r3));
381
+ }
382
+ function z2(n2) {
383
+ var t3 = r2, u4 = n2.__c;
384
+ "function" == typeof u4 && (n2.__c = void 0, u4()), r2 = t3;
385
+ }
386
+ function B2(n2) {
387
+ var t3 = r2;
388
+ n2.__c = n2.__(), r2 = t3;
389
+ }
390
+ function C2(n2, t3) {
391
+ return !n2 || n2.length !== t3.length || t3.some(function(t4, r3) {
392
+ return t4 !== n2[r3];
393
+ });
394
+ }
395
+ function D2(n2, t3) {
396
+ return "function" == typeof t3 ? t3(n2) : t3;
397
+ }
398
+
399
+ // node_modules/preact/jsx-runtime/dist/jsxRuntime.module.js
400
+ var f3 = 0;
401
+ function u3(e3, t3, n2, o3, i3, u4) {
402
+ t3 || (t3 = {});
403
+ var a3, c3, p3 = t3;
404
+ if ("ref" in p3) for (c3 in p3 = {}, t3) "ref" == c3 ? a3 = t3[c3] : p3[c3] = t3[c3];
405
+ var l3 = { type: e3, props: p3, key: n2, ref: a3, __k: null, __: null, __b: 0, __e: null, __c: null, constructor: void 0, __v: --f3, __i: -1, __u: 0, __source: i3, __self: u4 };
406
+ if ("function" == typeof e3 && (a3 = e3.defaultProps)) for (c3 in a3) void 0 === p3[c3] && (p3[c3] = a3[c3]);
407
+ return l.vnode && l.vnode(l3), l3;
408
+ }
409
+
410
+ // app/assets/typescript/profiler/components/dashboard/tabs/HttpTab.tsx
411
+ function methodBadge(method) {
412
+ const map = { GET: "info", POST: "success", PUT: "warning", PATCH: "warning", DELETE: "error" };
413
+ return map[method] || "default";
414
+ }
415
+ function statusBadge(status) {
416
+ if (status === 0) return "error";
417
+ if (status >= 200 && status < 300) return "success";
418
+ if (status >= 400) return "error";
419
+ return "warning";
420
+ }
421
+ function formatBytes(n2) {
422
+ if (n2 === 0) return "0 B";
423
+ if (n2 < 1024) return `${n2} B`;
424
+ return `${(n2 / 1024).toFixed(1)} KB`;
425
+ }
426
+ function HeadersTable({ headers }) {
427
+ const entries = Object.entries(headers);
428
+ if (!entries.length) return /* @__PURE__ */ u3("span", { class: "profiler-text--muted profiler-text--xs", children: "none" });
429
+ return /* @__PURE__ */ u3("table", { class: "profiler-table profiler-table--compact", children: /* @__PURE__ */ u3("tbody", { children: entries.map(([k3, v3]) => /* @__PURE__ */ u3("tr", { children: [
430
+ /* @__PURE__ */ u3("td", { class: "profiler-text--xs profiler-text--muted", style: "white-space:nowrap;padding-right:16px", children: k3 }),
431
+ /* @__PURE__ */ u3("td", { class: "profiler-text--xs", style: "word-break:break-all", children: v3 })
432
+ ] }, k3)) }) });
433
+ }
434
+ function detectContentType(headers) {
435
+ const ct = Object.entries(headers).find(([k3]) => k3.toLowerCase() === "content-type")?.[1] || "";
436
+ const mime = ct.split(";")[0].trim().toLowerCase();
437
+ if (mime.includes("json")) return "json";
438
+ if (mime === "image/svg+xml") return "svg";
439
+ if (mime.startsWith("image/")) return "image";
440
+ if (mime === "application/pdf") return "pdf";
441
+ if (mime.includes("xml")) return "xml";
442
+ if (mime === "text/csv" || mime === "application/csv") return "csv";
443
+ if (mime === "text/html") return "html";
444
+ if (mime.startsWith("application/") && !mime.includes("json") && !mime.includes("xml") && !mime.includes("javascript")) return "binary";
445
+ return "text";
446
+ }
447
+ function formatXml(raw) {
448
+ try {
449
+ let indent = 0;
450
+ return raw.replace(/>\s*</g, "><").replace(/(<\/?[^>]+>)/g, (tag) => {
451
+ if (tag.startsWith("</")) {
452
+ indent = Math.max(0, indent - 1);
453
+ return "\n" + " ".repeat(indent) + tag;
454
+ }
455
+ if (tag.endsWith("/>") || /<\?/.test(tag)) {
456
+ return "\n" + " ".repeat(indent) + tag;
457
+ }
458
+ const out = "\n" + " ".repeat(indent) + tag;
459
+ indent++;
460
+ return out;
461
+ }).trim();
462
+ } catch {
463
+ return raw;
464
+ }
465
+ }
466
+ function parseCsv(raw) {
467
+ return raw.split("\n").filter(Boolean).map((line) => {
468
+ const cols = [];
469
+ let cur = "";
470
+ let inQ = false;
471
+ for (let i3 = 0; i3 < line.length; i3++) {
472
+ const ch = line[i3];
473
+ if (ch === '"') {
474
+ inQ = !inQ;
475
+ } else if (ch === "," && !inQ) {
476
+ cols.push(cur);
477
+ cur = "";
478
+ } else {
479
+ cur += ch;
480
+ }
481
+ }
482
+ cols.push(cur);
483
+ return cols;
484
+ });
485
+ }
486
+ function formatTextBody(body, category) {
487
+ if (category === "json") {
488
+ try {
489
+ return JSON.stringify(JSON.parse(body), null, 2);
490
+ } catch {
491
+ return body;
492
+ }
493
+ }
494
+ if (category === "xml") return formatXml(body);
495
+ return body;
496
+ }
497
+ var PREVIEW_LIMIT = 500;
498
+ var CSV_ROW_LIMIT = 10;
499
+ function SmartBodyPreview({ body, encoding, headers }) {
500
+ const [expanded, setExpanded] = d2(false);
501
+ const [objectUrl, setObjectUrl] = d2(null);
502
+ const category = detectContentType(headers);
503
+ y2(() => {
504
+ if (encoding !== "base64" || !body) return;
505
+ const mime = Object.entries(headers).find(([k3]) => k3.toLowerCase() === "content-type")?.[1]?.split(";")[0].trim() || "application/octet-stream";
506
+ const raw = atob(body);
507
+ const bytes = new Uint8Array(raw.length);
508
+ for (let i3 = 0; i3 < raw.length; i3++) bytes[i3] = raw.charCodeAt(i3);
509
+ const url = URL.createObjectURL(new Blob([bytes], { type: mime }));
510
+ setObjectUrl(url);
511
+ return () => URL.revokeObjectURL(url);
512
+ }, [body, encoding, headers]);
513
+ if (!body) return /* @__PURE__ */ u3("span", { class: "profiler-text--muted profiler-text--xs", children: "empty" });
514
+ if (encoding === "base64") {
515
+ const mime = Object.entries(headers).find(([k3]) => k3.toLowerCase() === "content-type")?.[1]?.split(";")[0].trim() || "application/octet-stream";
516
+ const filename = mime.replace("/", "_").replace(/[^a-z0-9_]/gi, "") + "_download";
517
+ return /* @__PURE__ */ u3("div", { class: "profiler-body-binary", children: [
518
+ objectUrl && (category === "image" || category === "svg") && /* @__PURE__ */ u3("img", { src: objectUrl, alt: "response preview", style: "max-width:100%;max-height:300px;display:block;margin-bottom:8px;border-radius:4px;border:1px solid var(--profiler-border)" }),
519
+ objectUrl && category === "pdf" && /* @__PURE__ */ u3("iframe", { src: objectUrl, class: "profiler-body-preview-frame", title: "PDF preview" }),
520
+ objectUrl && /* @__PURE__ */ u3("a", { href: objectUrl, download: filename, class: "profiler-body-download-btn profiler-text--xs", children: [
521
+ "Download ",
522
+ mime
523
+ ] }),
524
+ !objectUrl && /* @__PURE__ */ u3("span", { class: "profiler-text--muted profiler-text--xs", children: "Loading preview\u2026" })
525
+ ] });
526
+ }
527
+ if (category === "csv") {
528
+ const rows = parseCsv(body);
529
+ const header = rows[0] || [];
530
+ const dataRows = rows.slice(1);
531
+ const visible = expanded ? dataRows : dataRows.slice(0, CSV_ROW_LIMIT);
532
+ const hasMore = dataRows.length > CSV_ROW_LIMIT;
533
+ return /* @__PURE__ */ u3("div", { children: [
534
+ /* @__PURE__ */ u3("div", { style: "overflow-x:auto", children: /* @__PURE__ */ u3("table", { class: "profiler-body-csv", children: [
535
+ /* @__PURE__ */ u3("thead", { children: /* @__PURE__ */ u3("tr", { children: header.map((h3, i3) => /* @__PURE__ */ u3("th", { children: h3 }, i3)) }) }),
536
+ /* @__PURE__ */ u3("tbody", { children: visible.map((row, i3) => /* @__PURE__ */ u3("tr", { children: row.map((cell, j3) => /* @__PURE__ */ u3("td", { children: cell }, j3)) }, i3)) })
537
+ ] }) }),
538
+ hasMore && /* @__PURE__ */ u3(
539
+ "button",
540
+ {
541
+ class: "profiler-link profiler-text--xs",
542
+ onClick: () => setExpanded((e3) => !e3),
543
+ style: "margin-top:4px;background:none;border:none;cursor:pointer;color:var(--profiler-accent);padding:0",
544
+ children: expanded ? "Show less" : `Show all (${dataRows.length} rows)`
545
+ }
546
+ )
547
+ ] });
548
+ }
549
+ const formatted = formatTextBody(body, category) ?? body;
550
+ const preview = expanded ? formatted : formatted.slice(0, PREVIEW_LIMIT);
551
+ const truncated = formatted.length > PREVIEW_LIMIT && !expanded;
552
+ return /* @__PURE__ */ u3("div", { children: [
553
+ /* @__PURE__ */ u3("pre", { class: "profiler-code profiler-text--xs", style: "white-space:pre-wrap;word-break:break-all;margin:0", children: [
554
+ preview,
555
+ truncated ? "\u2026" : ""
556
+ ] }),
557
+ formatted.length > PREVIEW_LIMIT && /* @__PURE__ */ u3(
558
+ "button",
559
+ {
560
+ class: "profiler-link profiler-text--xs",
561
+ onClick: () => setExpanded((e3) => !e3),
562
+ style: "margin-top:4px;background:none;border:none;cursor:pointer;color:var(--profiler-accent);padding:0",
563
+ children: expanded ? "Show less" : `Show all (${formatted.length} chars)`
564
+ }
565
+ )
566
+ ] });
567
+ }
568
+ function HttpRequestDetail({ req, index, threshold }) {
569
+ const [open, setOpen] = d2(false);
570
+ const isError = req.status >= 400 || req.status === 0;
571
+ const isSlow = req.duration >= threshold;
572
+ const cardCls = isError ? "profiler-ajax-card--error" : isSlow ? "profiler-ajax-card--warning" : "profiler-ajax-card--success";
573
+ return /* @__PURE__ */ u3("div", { class: `profiler-ajax-card ${cardCls}`, style: "margin-bottom:8px", children: [
574
+ /* @__PURE__ */ u3(
575
+ "div",
576
+ {
577
+ class: "profiler-ajax-card__row",
578
+ style: "cursor:pointer;user-select:none",
579
+ onClick: () => setOpen((o3) => !o3),
580
+ children: [
581
+ /* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-3", children: [
582
+ /* @__PURE__ */ u3("span", { style: "font-size:11px;color:var(--profiler-muted)", children: open ? "\u25BE" : "\u25B8" }),
583
+ /* @__PURE__ */ u3("span", { class: `profiler-ajax-card__method badge-${methodBadge(req.method)}`, children: req.method }),
584
+ /* @__PURE__ */ u3("strong", { class: "profiler-ajax-card__path", style: "word-break:break-all", children: req.url })
585
+ ] }),
586
+ /* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-2", style: "flex-shrink:0", children: [
587
+ /* @__PURE__ */ u3("span", { class: `badge-${statusBadge(req.status)}`, children: req.status === 0 ? "ERR" : req.status }),
588
+ /* @__PURE__ */ u3("span", { class: req.duration >= 500 ? "badge-error" : req.duration >= 100 ? "badge-warning" : "badge-success", children: [
589
+ req.duration.toFixed(2),
590
+ " ms"
591
+ ] })
592
+ ] })
593
+ ]
594
+ }
595
+ ),
596
+ /* @__PURE__ */ u3("div", { class: "profiler-ajax-card__row", children: [
597
+ /* @__PURE__ */ u3("span", { class: "profiler-text--xs profiler-text--muted", children: [
598
+ "\u2191 ",
599
+ formatBytes(req.request_size),
600
+ " \xB7 \u2193 ",
601
+ formatBytes(req.response_size)
602
+ ] }),
603
+ req.backtrace && req.backtrace.length > 0 && /* @__PURE__ */ u3("span", { class: "profiler-text--xs profiler-text--muted", style: "margin-left:12px", children: req.backtrace[0] })
604
+ ] }),
605
+ req.error && /* @__PURE__ */ u3("div", { class: "profiler-ajax-card__row", children: /* @__PURE__ */ u3("span", { class: "profiler-text--xs profiler-text--error", children: req.error }) }),
606
+ open && /* @__PURE__ */ u3("div", { style: "padding:12px 4px 4px;border-top:1px solid rgba(0,0,0,0.08);margin-top:8px", children: [
607
+ /* @__PURE__ */ u3("div", { style: "margin-bottom:16px", children: [
608
+ /* @__PURE__ */ u3("div", { class: "profiler-text--sm", style: "font-weight:600;margin-bottom:8px;color:var(--profiler-muted)", children: "REQUEST" }),
609
+ /* @__PURE__ */ u3("div", { class: "profiler-text--xs profiler-text--muted", style: "margin-bottom:4px;text-transform:uppercase;letter-spacing:.5px", children: "Headers" }),
610
+ /* @__PURE__ */ u3(HeadersTable, { headers: req.request_headers || {} }),
611
+ req.request_body && /* @__PURE__ */ u3(k, { children: [
612
+ /* @__PURE__ */ u3("div", { class: "profiler-text--xs profiler-text--muted", style: "margin-top:10px;margin-bottom:4px;text-transform:uppercase;letter-spacing:.5px", children: "Body" }),
613
+ /* @__PURE__ */ u3(SmartBodyPreview, { body: req.request_body, encoding: req.request_body_encoding, headers: req.request_headers || {} })
614
+ ] })
615
+ ] }),
616
+ /* @__PURE__ */ u3("div", { children: [
617
+ /* @__PURE__ */ u3("div", { class: "profiler-text--sm", style: "font-weight:600;margin-bottom:8px;color:var(--profiler-muted)", children: "RESPONSE" }),
618
+ /* @__PURE__ */ u3("div", { class: "profiler-text--xs profiler-text--muted", style: "margin-bottom:4px;text-transform:uppercase;letter-spacing:.5px", children: "Headers" }),
619
+ /* @__PURE__ */ u3(HeadersTable, { headers: req.response_headers || {} }),
620
+ req.response_body && /* @__PURE__ */ u3(k, { children: [
621
+ /* @__PURE__ */ u3("div", { class: "profiler-text--xs profiler-text--muted", style: "margin-top:10px;margin-bottom:4px;text-transform:uppercase;letter-spacing:.5px", children: "Body" }),
622
+ /* @__PURE__ */ u3(SmartBodyPreview, { body: req.response_body, encoding: req.response_body_encoding, headers: req.response_headers || {} })
623
+ ] })
624
+ ] }),
625
+ req.backtrace && req.backtrace.length > 1 && /* @__PURE__ */ u3("div", { style: "margin-top:12px", children: [
626
+ /* @__PURE__ */ u3("div", { class: "profiler-text--xs profiler-text--muted", style: "margin-bottom:4px;text-transform:uppercase;letter-spacing:.5px", children: "Backtrace" }),
627
+ req.backtrace.map((frame, i3) => /* @__PURE__ */ u3("div", { class: "profiler-text--xs profiler-text--muted", style: "padding:2px 0", children: frame }, i3))
628
+ ] })
629
+ ] })
630
+ ] });
631
+ }
632
+ function HttpTab({ httpData }) {
633
+ if (!httpData?.requests?.length) {
634
+ return /* @__PURE__ */ u3("div", { class: "profiler-empty", children: [
635
+ /* @__PURE__ */ u3("div", { class: "profiler-empty__icon", children: "\u{1F517}" }),
636
+ /* @__PURE__ */ u3("h3", { class: "profiler-empty__title", children: "No outbound HTTP requests" }),
637
+ /* @__PURE__ */ u3("p", { class: "profiler-empty__description", children: "External API calls made during this request will appear here." })
638
+ ] });
639
+ }
640
+ const threshold = 500;
641
+ const avgDuration = httpData.total_duration / httpData.total_requests;
642
+ return /* @__PURE__ */ u3(k, { children: [
643
+ /* @__PURE__ */ u3("h2", { class: "profiler-section__header", children: [
644
+ "Outbound HTTP (",
645
+ httpData.total_requests,
646
+ ")"
647
+ ] }),
648
+ /* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-4 profiler-mb-4 profiler-text--sm", children: [
649
+ /* @__PURE__ */ u3("span", { children: [
650
+ "Total: ",
651
+ /* @__PURE__ */ u3("strong", { children: [
652
+ httpData.total_duration.toFixed(2),
653
+ " ms"
654
+ ] })
655
+ ] }),
656
+ /* @__PURE__ */ u3("span", { children: [
657
+ "Avg: ",
658
+ /* @__PURE__ */ u3("strong", { children: [
659
+ avgDuration.toFixed(2),
660
+ " ms"
661
+ ] })
662
+ ] }),
663
+ /* @__PURE__ */ u3("span", { children: [
664
+ "Slow (>",
665
+ threshold,
666
+ "ms): ",
667
+ /* @__PURE__ */ u3("strong", { class: httpData.slow_requests > 0 ? "profiler-text--error" : "", children: httpData.slow_requests })
668
+ ] }),
669
+ /* @__PURE__ */ u3("span", { children: [
670
+ "Errors: ",
671
+ /* @__PURE__ */ u3("strong", { class: httpData.error_requests > 0 ? "profiler-text--error" : "", children: httpData.error_requests })
672
+ ] })
673
+ ] }),
674
+ /* @__PURE__ */ u3("div", { class: "profiler-grid profiler-grid--2 profiler-mb-6", children: [
675
+ /* @__PURE__ */ u3("div", { class: "profiler-panel profiler-panel--sm", children: [
676
+ /* @__PURE__ */ u3("h3", { class: "profiler-text--sm profiler-text--muted profiler-text--uppercase profiler-mb-3", children: "By Host" }),
677
+ Object.entries(httpData.by_host).map(([host, count]) => /* @__PURE__ */ u3("div", { class: "profiler-kv-row", children: [
678
+ /* @__PURE__ */ u3("span", { class: "profiler-text--sm", children: host }),
679
+ /* @__PURE__ */ u3("strong", { children: count })
680
+ ] }, host))
681
+ ] }),
682
+ /* @__PURE__ */ u3("div", { class: "profiler-panel profiler-panel--sm", children: [
683
+ /* @__PURE__ */ u3("h3", { class: "profiler-text--sm profiler-text--muted profiler-text--uppercase profiler-mb-3", children: "By Status" }),
684
+ Object.entries(httpData.by_status).map(([status, count]) => /* @__PURE__ */ u3("div", { class: "profiler-kv-row", children: [
685
+ /* @__PURE__ */ u3("span", { class: `badge-${status.startsWith("2") ? "success" : status.startsWith("4") || status.startsWith("5") || status === "error" ? "error" : "warning"}`, children: status }),
686
+ /* @__PURE__ */ u3("strong", { children: count })
687
+ ] }, status))
688
+ ] })
689
+ ] }),
690
+ /* @__PURE__ */ u3("h3", { class: "profiler-text--lg profiler-mb-3", children: "Requests" }),
691
+ /* @__PURE__ */ u3("p", { class: "profiler-text--xs profiler-text--muted profiler-mb-3", children: "Click a request to expand headers and body." }),
692
+ httpData.requests.map((req, index) => /* @__PURE__ */ u3(HttpRequestDetail, { req, index, threshold }, index))
693
+ ] });
694
+ }
695
+
696
+ // app/assets/typescript/profiler/components/ProfileList.tsx
697
+ var BASE = "/_profiler";
698
+ function methodClass(method) {
699
+ const map = { GET: "badge-info", POST: "badge-success", PUT: "badge-warning", PATCH: "badge-warning", DELETE: "badge-error" };
700
+ return map[method] || "badge-default";
701
+ }
702
+ function statusClass(status) {
703
+ if (status >= 200 && status < 300) return "badge-success";
704
+ if (status >= 300 && status < 400) return "badge-info";
705
+ if (status >= 400 && status < 500) return "badge-warning";
706
+ if (status >= 500) return "badge-error";
707
+ return "badge-default";
708
+ }
709
+ function durationClass(duration) {
710
+ if (duration >= 500) return "badge-error";
711
+ if (duration >= 100) return "badge-warning";
712
+ return "badge-success";
713
+ }
714
+ function formatTime(iso) {
715
+ return new Date(iso).toLocaleTimeString("en", { hour12: false });
716
+ }
717
+ function formatMemory(bytes) {
718
+ if (!bytes) return "-";
719
+ return (bytes / 1024 / 1024).toFixed(2) + " MB";
720
+ }
721
+ function ProfileList() {
722
+ const initialSection = () => {
723
+ const s3 = new URLSearchParams(window.location.search).get("section");
724
+ return s3 === "http" || s3 === "jobs" || s3 === "outbound" ? s3 : "http";
725
+ };
726
+ const [section, setSection] = d2(initialSection);
727
+ const [profiles, setProfiles] = d2([]);
728
+ const [jobs, setJobs] = d2([]);
729
+ const [outboundRequests, setOutboundRequests] = d2([]);
730
+ const [loadingHttp, setLoadingHttp] = d2(true);
731
+ const [loadingJobs, setLoadingJobs] = d2(false);
732
+ const [loadingOutbound, setLoadingOutbound] = d2(false);
733
+ const [jobsLoaded, setJobsLoaded] = d2(false);
734
+ const [outboundLoaded, setOutboundLoaded] = d2(false);
735
+ const [error, setError] = d2(null);
736
+ const [jobsError, setJobsError] = d2(null);
737
+ const [outboundError, setOutboundError] = d2(null);
738
+ const [copiedToken, setCopiedToken] = d2(null);
739
+ const [httpSearch, setHttpSearch] = d2("");
740
+ const [httpMethod, setHttpMethod] = d2("");
741
+ const [httpStatus, setHttpStatus] = d2("");
742
+ const [httpDuration, setHttpDuration] = d2("");
743
+ const [jobSearch, setJobSearch] = d2("");
744
+ const [jobStatus, setJobStatus] = d2("");
745
+ const [jobDuration, setJobDuration] = d2("");
746
+ const [outboundSearch, setOutboundSearch] = d2("");
747
+ const [outboundMethod, setOutboundMethod] = d2("");
748
+ const [outboundStatus, setOutboundStatus] = d2("");
749
+ y2(() => {
750
+ fetch(`${BASE}/api/profiles`).then((res) => res.json()).then((data) => {
751
+ setProfiles(data.filter((p3) => p3.profile_type !== "job"));
752
+ setLoadingHttp(false);
753
+ }).catch(() => {
754
+ setError("Failed to load profiles");
755
+ setLoadingHttp(false);
756
+ });
757
+ }, []);
758
+ y2(() => {
759
+ if (section === "jobs") loadJobs();
760
+ if (section === "outbound") loadOutbound();
761
+ }, []);
762
+ const loadJobs = () => {
763
+ if (jobsLoaded) return;
764
+ setLoadingJobs(true);
765
+ fetch(`${BASE}/api/jobs`).then((res) => res.json()).then((data) => {
766
+ setJobs(data);
767
+ setLoadingJobs(false);
768
+ setJobsLoaded(true);
769
+ }).catch(() => {
770
+ setJobsError("Failed to load job profiles");
771
+ setLoadingJobs(false);
772
+ setJobsLoaded(true);
773
+ });
774
+ };
775
+ const loadOutbound = () => {
776
+ if (outboundLoaded) return;
777
+ setLoadingOutbound(true);
778
+ fetch(`${BASE}/api/outbound_http`).then((res) => res.json()).then((data) => {
779
+ setOutboundRequests(data);
780
+ setLoadingOutbound(false);
781
+ setOutboundLoaded(true);
782
+ }).catch(() => {
783
+ setOutboundError("Failed to load outbound HTTP requests");
784
+ setLoadingOutbound(false);
785
+ setOutboundLoaded(true);
786
+ });
787
+ };
788
+ const handleSectionChange = (s3) => {
789
+ setSection(s3);
790
+ const url = new URL(window.location.href);
791
+ url.searchParams.set("section", s3);
792
+ history.pushState(null, "", url.toString());
793
+ if (s3 === "jobs") loadJobs();
794
+ if (s3 === "outbound") loadOutbound();
795
+ setHttpSearch("");
796
+ setHttpMethod("");
797
+ setHttpStatus("");
798
+ setHttpDuration("");
799
+ setJobSearch("");
800
+ setJobStatus("");
801
+ setJobDuration("");
802
+ setOutboundSearch("");
803
+ setOutboundMethod("");
804
+ setOutboundStatus("");
805
+ };
806
+ const copyToken = (token) => {
807
+ navigator.clipboard.writeText(token).then(() => {
808
+ setCopiedToken(token);
809
+ setTimeout(() => setCopiedToken(null), 1500);
810
+ });
811
+ };
812
+ const deleteProfile = (token) => {
813
+ fetch(`${BASE}/api/profiles/${token}`, { method: "DELETE" }).then(() => {
814
+ setProfiles((prev) => prev.filter((p3) => p3.token !== token));
815
+ });
816
+ };
817
+ const deleteJob = (token) => {
818
+ fetch(`${BASE}/api/jobs/${token}`, { method: "DELETE" }).then(() => {
819
+ setJobs((prev) => prev.filter((p3) => p3.token !== token));
820
+ });
821
+ };
822
+ const clearProfiles = () => {
823
+ fetch(`${BASE}/api/profiles/clear`, { method: "DELETE" }).then(() => {
824
+ setProfiles([]);
825
+ });
826
+ };
827
+ const clearJobs = () => {
828
+ fetch(`${BASE}/api/jobs/clear`, { method: "DELETE" }).then(() => {
829
+ setJobs([]);
830
+ });
831
+ };
832
+ const refresh = () => {
833
+ if (section === "http") {
834
+ setLoadingHttp(true);
835
+ fetch(`${BASE}/api/profiles`).then((res) => res.json()).then((data) => {
836
+ setProfiles(data.filter((p3) => p3.profile_type !== "job"));
837
+ setLoadingHttp(false);
838
+ }).catch(() => {
839
+ setError("Failed to load profiles");
840
+ setLoadingHttp(false);
841
+ });
842
+ } else if (section === "jobs") {
843
+ setLoadingJobs(true);
844
+ fetch(`${BASE}/api/jobs`).then((res) => res.json()).then((data) => {
845
+ setJobs(data);
846
+ setLoadingJobs(false);
847
+ }).catch(() => {
848
+ setJobsError("Failed to load job profiles");
849
+ setLoadingJobs(false);
850
+ });
851
+ } else {
852
+ setLoadingOutbound(true);
853
+ fetch(`${BASE}/api/outbound_http`).then((res) => res.json()).then((data) => {
854
+ setOutboundRequests(data);
855
+ setLoadingOutbound(false);
856
+ }).catch(() => {
857
+ setOutboundError("Failed to load outbound HTTP requests");
858
+ setLoadingOutbound(false);
859
+ });
860
+ }
861
+ };
862
+ const tabClass = (s3) => `tab${section === s3 ? " active" : ""}`;
863
+ const filteredProfiles = profiles.filter((p3) => {
864
+ if (httpSearch && !p3.path.toLowerCase().includes(httpSearch.toLowerCase())) return false;
865
+ if (httpMethod && p3.method !== httpMethod) return false;
866
+ if (httpStatus) {
867
+ const base = parseInt(httpStatus);
868
+ if (!(p3.status >= base && p3.status < base + 100)) return false;
869
+ }
870
+ if (httpDuration) {
871
+ if (httpDuration === "lt100" ? p3.duration >= 100 : p3.duration < parseInt(httpDuration)) return false;
872
+ }
873
+ return true;
874
+ });
875
+ const filteredJobs = jobs.filter((p3) => {
876
+ if (jobSearch && !p3.path.toLowerCase().includes(jobSearch.toLowerCase())) return false;
877
+ if (jobStatus === "failed" && p3.status !== 500) return false;
878
+ if (jobStatus === "completed" && p3.status === 500) return false;
879
+ if (jobDuration) {
880
+ if (jobDuration === "lt100" ? p3.duration >= 100 : p3.duration < parseInt(jobDuration)) return false;
881
+ }
882
+ return true;
883
+ });
884
+ const filteredOutbound = outboundRequests.filter((req) => {
885
+ if (outboundSearch && !req.url.toLowerCase().includes(outboundSearch.toLowerCase())) return false;
886
+ if (outboundMethod && req.method !== outboundMethod) return false;
887
+ if (outboundStatus) {
888
+ if (outboundStatus === "error") {
889
+ if (req.status !== 0) return false;
890
+ } else {
891
+ const base = parseInt(outboundStatus);
892
+ if (!(req.status >= base && req.status < base + 100)) return false;
893
+ }
894
+ }
895
+ return true;
896
+ });
897
+ const httpFiltersActive = !!(httpSearch || httpMethod || httpStatus || httpDuration);
898
+ const jobFiltersActive = !!(jobSearch || jobStatus || jobDuration);
899
+ const outboundFiltersActive = !!(outboundSearch || outboundMethod || outboundStatus);
900
+ return /* @__PURE__ */ u3("div", { class: "container", children: [
901
+ /* @__PURE__ */ u3("div", { class: "header", children: [
902
+ /* @__PURE__ */ u3("h1", { children: [
903
+ /* @__PURE__ */ u3("span", { class: "h1-emoji", children: "\u{1F50D}" }),
904
+ " Rails Profiler"
905
+ ] }),
906
+ /* @__PURE__ */ u3("p", { children: "Recent profiled requests and jobs" })
907
+ ] }),
908
+ /* @__PURE__ */ u3("div", { class: "profiler-panel profiler-mb-6", children: [
909
+ /* @__PURE__ */ u3("div", { class: "tabs", children: [
910
+ /* @__PURE__ */ u3("a", { href: "#", class: tabClass("http"), onClick: (e3) => {
911
+ e3.preventDefault();
912
+ handleSectionChange("http");
913
+ }, children: "HTTP Requests" }),
914
+ /* @__PURE__ */ u3("a", { href: "#", class: tabClass("jobs"), onClick: (e3) => {
915
+ e3.preventDefault();
916
+ handleSectionChange("jobs");
917
+ }, children: "Background Jobs" }),
918
+ /* @__PURE__ */ u3("a", { href: "#", class: tabClass("outbound"), onClick: (e3) => {
919
+ e3.preventDefault();
920
+ handleSectionChange("outbound");
921
+ }, children: "Outbound HTTP" })
922
+ ] }),
923
+ /* @__PURE__ */ u3("div", { class: "profiler-p-4 tab-content active", children: [
924
+ section === "http" && (loadingHttp ? /* @__PURE__ */ u3("div", { class: "profiler-empty", children: /* @__PURE__ */ u3("div", { class: "profiler-empty__title", children: "Loading..." }) }) : error ? /* @__PURE__ */ u3("div", { class: "profiler-empty", children: /* @__PURE__ */ u3("div", { class: "profiler-empty__title", children: error }) }) : profiles.length === 0 ? /* @__PURE__ */ u3("div", { class: "profiler-empty", children: [
925
+ /* @__PURE__ */ u3("div", { class: "profiler-empty__title", children: "No profiles found" }),
926
+ /* @__PURE__ */ u3("p", { class: "profiler-empty__description", children: "Make some requests to your application to see profiling data" })
927
+ ] }) : /* @__PURE__ */ u3(k, { children: [
928
+ /* @__PURE__ */ u3("div", { class: "profiler-action-bar profiler-mb-3", children: [
929
+ /* @__PURE__ */ u3("div", { class: "profiler-filter-group", children: [
930
+ /* @__PURE__ */ u3(
931
+ "input",
932
+ {
933
+ type: "text",
934
+ class: "profiler-filter-input",
935
+ placeholder: "Search path\u2026",
936
+ value: httpSearch,
937
+ onInput: (e3) => setHttpSearch(e3.target.value)
938
+ }
939
+ ),
940
+ /* @__PURE__ */ u3("select", { class: "profiler-filter-select", value: httpMethod, onChange: (e3) => setHttpMethod(e3.target.value), children: [
941
+ /* @__PURE__ */ u3("option", { value: "", children: "All Methods" }),
942
+ /* @__PURE__ */ u3("option", { value: "GET", children: "GET" }),
943
+ /* @__PURE__ */ u3("option", { value: "POST", children: "POST" }),
944
+ /* @__PURE__ */ u3("option", { value: "PUT", children: "PUT" }),
945
+ /* @__PURE__ */ u3("option", { value: "PATCH", children: "PATCH" }),
946
+ /* @__PURE__ */ u3("option", { value: "DELETE", children: "DELETE" })
947
+ ] }),
948
+ /* @__PURE__ */ u3("select", { class: "profiler-filter-select", value: httpStatus, onChange: (e3) => setHttpStatus(e3.target.value), children: [
949
+ /* @__PURE__ */ u3("option", { value: "", children: "All Statuses" }),
950
+ /* @__PURE__ */ u3("option", { value: "200", children: "2xx" }),
951
+ /* @__PURE__ */ u3("option", { value: "300", children: "3xx" }),
952
+ /* @__PURE__ */ u3("option", { value: "400", children: "4xx" }),
953
+ /* @__PURE__ */ u3("option", { value: "500", children: "5xx" })
954
+ ] }),
955
+ /* @__PURE__ */ u3("select", { class: "profiler-filter-select", value: httpDuration, onChange: (e3) => setHttpDuration(e3.target.value), children: [
956
+ /* @__PURE__ */ u3("option", { value: "", children: "All Durations" }),
957
+ /* @__PURE__ */ u3("option", { value: "lt100", children: "<100ms" }),
958
+ /* @__PURE__ */ u3("option", { value: "100", children: "\u2265100ms" }),
959
+ /* @__PURE__ */ u3("option", { value: "500", children: "\u2265500ms" })
960
+ ] })
961
+ ] }),
962
+ /* @__PURE__ */ u3("div", { class: "profiler-filter-group", children: [
963
+ httpFiltersActive && /* @__PURE__ */ u3("span", { class: "profiler-filter-count", children: [
964
+ filteredProfiles.length,
965
+ " / ",
966
+ profiles.length
967
+ ] }),
968
+ /* @__PURE__ */ u3("button", { class: `btn-refresh${loadingHttp ? " btn-refresh--spinning" : ""}`, onClick: refresh, disabled: loadingHttp, title: "Refresh", children: "\u21BA" }),
969
+ /* @__PURE__ */ u3("button", { class: "btn btn-danger btn-sm", onClick: clearProfiles, children: "Clear All" })
970
+ ] })
971
+ ] }),
972
+ filteredProfiles.length === 0 ? /* @__PURE__ */ u3("div", { class: "profiler-empty", children: /* @__PURE__ */ u3("div", { class: "profiler-empty__title", children: "No results match filters" }) }) : /* @__PURE__ */ u3("table", { children: [
973
+ /* @__PURE__ */ u3("thead", { children: /* @__PURE__ */ u3("tr", { children: [
974
+ /* @__PURE__ */ u3("th", { children: "Time" }),
975
+ /* @__PURE__ */ u3("th", { children: "Method" }),
976
+ /* @__PURE__ */ u3("th", { children: "Path" }),
977
+ /* @__PURE__ */ u3("th", { children: "Duration" }),
978
+ /* @__PURE__ */ u3("th", { children: "Memory" }),
979
+ /* @__PURE__ */ u3("th", { children: "Status" }),
980
+ /* @__PURE__ */ u3("th", { children: "Token" }),
981
+ /* @__PURE__ */ u3("th", {})
982
+ ] }) }),
983
+ /* @__PURE__ */ u3("tbody", { children: filteredProfiles.map((p3) => /* @__PURE__ */ u3("tr", { children: [
984
+ /* @__PURE__ */ u3("td", { children: formatTime(p3.started_at) }),
985
+ /* @__PURE__ */ u3("td", { children: /* @__PURE__ */ u3("span", { class: methodClass(p3.method), children: p3.method }) }),
986
+ /* @__PURE__ */ u3("td", { children: /* @__PURE__ */ u3("a", { href: `${BASE}/profiles/${p3.token}`, children: p3.path }) }),
987
+ /* @__PURE__ */ u3("td", { children: /* @__PURE__ */ u3("span", { class: durationClass(p3.duration), children: [
988
+ p3.duration.toFixed(2),
989
+ " ms"
990
+ ] }) }),
991
+ /* @__PURE__ */ u3("td", { children: formatMemory(p3.memory) }),
992
+ /* @__PURE__ */ u3("td", { children: /* @__PURE__ */ u3("span", { class: statusClass(p3.status), children: p3.status }) }),
993
+ /* @__PURE__ */ u3("td", { class: "profiler-text--xs profiler-text--mono profiler-text--muted", children: /* @__PURE__ */ u3("button", { class: "token-copy", onClick: () => copyToken(p3.token), title: "Copy full token", children: copiedToken === p3.token ? "\u2713" : p3.token.substring(0, 8) + "\u2026" }) }),
994
+ /* @__PURE__ */ u3("td", { children: /* @__PURE__ */ u3("button", { class: "btn-row-delete", onClick: () => deleteProfile(p3.token), title: "Delete", children: "\xD7" }) })
995
+ ] }, p3.token)) })
996
+ ] })
997
+ ] })),
998
+ section === "jobs" && (loadingJobs ? /* @__PURE__ */ u3("div", { class: "profiler-empty", children: /* @__PURE__ */ u3("div", { class: "profiler-empty__title", children: "Loading..." }) }) : jobsError ? /* @__PURE__ */ u3("div", { class: "profiler-empty", children: /* @__PURE__ */ u3("div", { class: "profiler-empty__title", children: jobsError }) }) : jobs.length === 0 ? /* @__PURE__ */ u3("div", { class: "profiler-empty", children: [
999
+ /* @__PURE__ */ u3("div", { class: "profiler-empty__title", children: "No job profiles found" }),
1000
+ /* @__PURE__ */ u3("p", { class: "profiler-empty__description", children: "Run background jobs in your application to see profiling data" })
1001
+ ] }) : /* @__PURE__ */ u3(k, { children: [
1002
+ /* @__PURE__ */ u3("div", { class: "profiler-action-bar profiler-mb-3", children: [
1003
+ /* @__PURE__ */ u3("div", { class: "profiler-filter-group", children: [
1004
+ /* @__PURE__ */ u3(
1005
+ "input",
1006
+ {
1007
+ type: "text",
1008
+ class: "profiler-filter-input",
1009
+ placeholder: "Search job class\u2026",
1010
+ value: jobSearch,
1011
+ onInput: (e3) => setJobSearch(e3.target.value)
1012
+ }
1013
+ ),
1014
+ /* @__PURE__ */ u3("select", { class: "profiler-filter-select", value: jobStatus, onChange: (e3) => setJobStatus(e3.target.value), children: [
1015
+ /* @__PURE__ */ u3("option", { value: "", children: "All Statuses" }),
1016
+ /* @__PURE__ */ u3("option", { value: "completed", children: "Completed" }),
1017
+ /* @__PURE__ */ u3("option", { value: "failed", children: "Failed" })
1018
+ ] }),
1019
+ /* @__PURE__ */ u3("select", { class: "profiler-filter-select", value: jobDuration, onChange: (e3) => setJobDuration(e3.target.value), children: [
1020
+ /* @__PURE__ */ u3("option", { value: "", children: "All Durations" }),
1021
+ /* @__PURE__ */ u3("option", { value: "lt100", children: "<100ms" }),
1022
+ /* @__PURE__ */ u3("option", { value: "100", children: "\u2265100ms" }),
1023
+ /* @__PURE__ */ u3("option", { value: "500", children: "\u2265500ms" })
1024
+ ] })
1025
+ ] }),
1026
+ /* @__PURE__ */ u3("div", { class: "profiler-filter-group", children: [
1027
+ jobFiltersActive && /* @__PURE__ */ u3("span", { class: "profiler-filter-count", children: [
1028
+ filteredJobs.length,
1029
+ " / ",
1030
+ jobs.length
1031
+ ] }),
1032
+ /* @__PURE__ */ u3("button", { class: `btn-refresh${loadingJobs ? " btn-refresh--spinning" : ""}`, onClick: refresh, disabled: loadingJobs, title: "Refresh", children: "\u21BA" }),
1033
+ /* @__PURE__ */ u3("button", { class: "btn btn-danger btn-sm", onClick: clearJobs, children: "Clear All" })
1034
+ ] })
1035
+ ] }),
1036
+ filteredJobs.length === 0 ? /* @__PURE__ */ u3("div", { class: "profiler-empty", children: /* @__PURE__ */ u3("div", { class: "profiler-empty__title", children: "No results match filters" }) }) : /* @__PURE__ */ u3("table", { children: [
1037
+ /* @__PURE__ */ u3("thead", { children: /* @__PURE__ */ u3("tr", { children: [
1038
+ /* @__PURE__ */ u3("th", { children: "Time" }),
1039
+ /* @__PURE__ */ u3("th", { children: "Job Class" }),
1040
+ /* @__PURE__ */ u3("th", { children: "Queue" }),
1041
+ /* @__PURE__ */ u3("th", { children: "Duration" }),
1042
+ /* @__PURE__ */ u3("th", { children: "Status" }),
1043
+ /* @__PURE__ */ u3("th", { children: "Executions" }),
1044
+ /* @__PURE__ */ u3("th", { children: "Token" }),
1045
+ /* @__PURE__ */ u3("th", {})
1046
+ ] }) }),
1047
+ /* @__PURE__ */ u3("tbody", { children: filteredJobs.map((p3) => {
1048
+ const jobData = p3.collectors_data?.job;
1049
+ const isFailed = p3.status === 500;
1050
+ return /* @__PURE__ */ u3("tr", { children: [
1051
+ /* @__PURE__ */ u3("td", { children: formatTime(p3.started_at) }),
1052
+ /* @__PURE__ */ u3("td", { children: /* @__PURE__ */ u3("a", { href: `${BASE}/profiles/${p3.token}`, children: p3.path }) }),
1053
+ /* @__PURE__ */ u3("td", { children: /* @__PURE__ */ u3("span", { class: "profiler-text--xs profiler-text--mono", children: jobData?.queue || "-" }) }),
1054
+ /* @__PURE__ */ u3("td", { children: /* @__PURE__ */ u3("span", { class: durationClass(p3.duration), children: [
1055
+ p3.duration.toFixed(2),
1056
+ " ms"
1057
+ ] }) }),
1058
+ /* @__PURE__ */ u3("td", { children: /* @__PURE__ */ u3("span", { class: isFailed ? "badge-error" : "badge-success", children: isFailed ? "\u2717 Failed" : "\u2713 Completed" }) }),
1059
+ /* @__PURE__ */ u3("td", { children: jobData?.executions ?? "-" }),
1060
+ /* @__PURE__ */ u3("td", { class: "profiler-text--xs profiler-text--mono profiler-text--muted", children: /* @__PURE__ */ u3("button", { class: "token-copy", onClick: () => copyToken(p3.token), title: "Copy full token", children: copiedToken === p3.token ? "\u2713" : p3.token.substring(0, 8) + "\u2026" }) }),
1061
+ /* @__PURE__ */ u3("td", { children: /* @__PURE__ */ u3("button", { class: "btn-row-delete", onClick: () => deleteJob(p3.token), title: "Delete", children: "\xD7" }) })
1062
+ ] }, p3.token);
1063
+ }) })
1064
+ ] })
1065
+ ] })),
1066
+ section === "outbound" && (loadingOutbound ? /* @__PURE__ */ u3("div", { class: "profiler-empty", children: /* @__PURE__ */ u3("div", { class: "profiler-empty__title", children: "Loading..." }) }) : outboundError ? /* @__PURE__ */ u3("div", { class: "profiler-empty", children: /* @__PURE__ */ u3("div", { class: "profiler-empty__title", children: outboundError }) }) : outboundRequests.length === 0 ? /* @__PURE__ */ u3("div", { class: "profiler-empty", children: [
1067
+ /* @__PURE__ */ u3("div", { class: "profiler-empty__title", children: "No outbound HTTP requests found" }),
1068
+ /* @__PURE__ */ u3("p", { class: "profiler-empty__description", children: "Make requests to external services to see outbound HTTP data" })
1069
+ ] }) : /* @__PURE__ */ u3(k, { children: [
1070
+ /* @__PURE__ */ u3("div", { class: "profiler-action-bar profiler-mb-3", children: [
1071
+ /* @__PURE__ */ u3("div", { class: "profiler-filter-group", children: [
1072
+ /* @__PURE__ */ u3(
1073
+ "input",
1074
+ {
1075
+ type: "text",
1076
+ class: "profiler-filter-input",
1077
+ placeholder: "Search URL\u2026",
1078
+ value: outboundSearch,
1079
+ onInput: (e3) => setOutboundSearch(e3.target.value)
1080
+ }
1081
+ ),
1082
+ /* @__PURE__ */ u3("select", { class: "profiler-filter-select", value: outboundMethod, onChange: (e3) => setOutboundMethod(e3.target.value), children: [
1083
+ /* @__PURE__ */ u3("option", { value: "", children: "All Methods" }),
1084
+ /* @__PURE__ */ u3("option", { value: "GET", children: "GET" }),
1085
+ /* @__PURE__ */ u3("option", { value: "POST", children: "POST" }),
1086
+ /* @__PURE__ */ u3("option", { value: "PUT", children: "PUT" }),
1087
+ /* @__PURE__ */ u3("option", { value: "PATCH", children: "PATCH" }),
1088
+ /* @__PURE__ */ u3("option", { value: "DELETE", children: "DELETE" })
1089
+ ] }),
1090
+ /* @__PURE__ */ u3("select", { class: "profiler-filter-select", value: outboundStatus, onChange: (e3) => setOutboundStatus(e3.target.value), children: [
1091
+ /* @__PURE__ */ u3("option", { value: "", children: "All Statuses" }),
1092
+ /* @__PURE__ */ u3("option", { value: "200", children: "2xx" }),
1093
+ /* @__PURE__ */ u3("option", { value: "300", children: "3xx" }),
1094
+ /* @__PURE__ */ u3("option", { value: "400", children: "4xx" }),
1095
+ /* @__PURE__ */ u3("option", { value: "500", children: "5xx" }),
1096
+ /* @__PURE__ */ u3("option", { value: "error", children: "Error" })
1097
+ ] })
1098
+ ] }),
1099
+ /* @__PURE__ */ u3("div", { class: "profiler-filter-group", children: [
1100
+ outboundFiltersActive && /* @__PURE__ */ u3("span", { class: "profiler-filter-count", children: [
1101
+ filteredOutbound.length,
1102
+ " / ",
1103
+ outboundRequests.length
1104
+ ] }),
1105
+ /* @__PURE__ */ u3("button", { class: `btn-refresh${loadingOutbound ? " btn-refresh--spinning" : ""}`, onClick: refresh, disabled: loadingOutbound, title: "Refresh", children: "\u21BA" })
1106
+ ] })
1107
+ ] }),
1108
+ /* @__PURE__ */ u3("p", { class: "profiler-text--xs profiler-text--muted profiler-mb-3", children: [
1109
+ outboundRequests.length,
1110
+ " outbound request",
1111
+ outboundRequests.length !== 1 ? "s" : "",
1112
+ " across all profiles. Click a request to expand headers and body."
1113
+ ] }),
1114
+ filteredOutbound.length === 0 ? /* @__PURE__ */ u3("div", { class: "profiler-empty", children: /* @__PURE__ */ u3("div", { class: "profiler-empty__title", children: "No results match filters" }) }) : filteredOutbound.map((req, i3) => /* @__PURE__ */ u3("div", { style: "margin-bottom:4px", children: [
1115
+ /* @__PURE__ */ u3("div", { class: "profiler-text--xs profiler-text--muted", style: "margin-bottom:2px", children: [
1116
+ formatTime(req.profile_started_at),
1117
+ " \xB7 Profile: ",
1118
+ /* @__PURE__ */ u3("a", { href: `${BASE}/profiles/${req.profile_token}`, children: [
1119
+ req.profile_token.substring(0, 8),
1120
+ "\u2026"
1121
+ ] })
1122
+ ] }),
1123
+ /* @__PURE__ */ u3(HttpRequestDetail, { req, index: i3, threshold: 500 })
1124
+ ] }, i3))
1125
+ ] }))
1126
+ ] })
1127
+ ] })
1128
+ ] });
1129
+ }
1130
+
1131
+ // app/assets/typescript/profiler/components/dashboard/tabs/RequestTab.tsx
1132
+ function tryFormatJson(text) {
1133
+ try {
1134
+ return JSON.stringify(JSON.parse(text), null, 2);
1135
+ } catch {
1136
+ return text;
1137
+ }
1138
+ }
1139
+ function BodyBlock({ body, encoding }) {
1140
+ if (!body) return null;
1141
+ if (encoding === "base64") {
1142
+ return /* @__PURE__ */ u3("p", { class: "profiler-text--muted profiler-text--sm", children: "[binary content, base64-encoded]" });
1143
+ }
1144
+ const formatted = tryFormatJson(body);
1145
+ return /* @__PURE__ */ u3("pre", { class: "profiler-code profiler-text--xs", children: formatted });
1146
+ }
1147
+ function buildCurl(profile) {
1148
+ const headers = profile.headers ?? {};
1149
+ const params = profile.params ?? {};
1150
+ const reqBody = profile.request_body;
1151
+ const parts = [`curl -X ${profile.method}`];
1152
+ Object.entries(headers).filter(([k3]) => k3 !== "User-Agent").forEach(([k3, v3]) => parts.push(` -H '${k3}: ${v3}'`));
1153
+ if (["POST", "PUT", "PATCH"].includes(profile.method)) {
1154
+ if (reqBody && reqBody.length > 0) {
1155
+ parts.push(` -d '${reqBody.replace(/'/g, "'\\''")}'`);
1156
+ } else if (Object.keys(params).length > 0) {
1157
+ const ct = headers["Content-Type"] ?? "";
1158
+ if (ct.includes("application/json")) {
1159
+ parts.push(` -d '${JSON.stringify(params).replace(/'/g, "'\\''")}'`);
1160
+ } else {
1161
+ Object.entries(params).forEach(
1162
+ ([k3, v3]) => parts.push(` --data-urlencode '${k3}=${v3}'`)
1163
+ );
1164
+ }
1165
+ }
1166
+ }
1167
+ let url = `http://localhost:3000${profile.path}`;
1168
+ if (profile.method === "GET" && Object.keys(params).length > 0) {
1169
+ url += "?" + Object.entries(params).map(([k3, v3]) => `${encodeURIComponent(k3)}=${encodeURIComponent(String(v3))}`).join("&");
1170
+ }
1171
+ parts.push(` '${url}'`);
1172
+ return parts.join(" \\\n");
1173
+ }
1174
+ function RequestTab({ profile }) {
1175
+ const [copied, setCopied] = d2(false);
1176
+ const curl = buildCurl(profile);
1177
+ function copyToClipboard() {
1178
+ navigator.clipboard.writeText(curl).then(() => {
1179
+ setCopied(true);
1180
+ setTimeout(() => setCopied(false), 2e3);
1181
+ });
1182
+ }
1183
+ return /* @__PURE__ */ u3(k, { children: [
1184
+ /* @__PURE__ */ u3("h2", { class: "profiler-section__header", children: "Request" }),
1185
+ /* @__PURE__ */ u3("table", { children: [
1186
+ /* @__PURE__ */ u3("tr", { children: [
1187
+ /* @__PURE__ */ u3("th", { class: "profiler-text--sm", style: "width: 200px;", children: "Path" }),
1188
+ /* @__PURE__ */ u3("td", { children: profile.path })
1189
+ ] }),
1190
+ /* @__PURE__ */ u3("tr", { children: [
1191
+ /* @__PURE__ */ u3("th", { class: "profiler-text--sm", children: "Method" }),
1192
+ /* @__PURE__ */ u3("td", { children: profile.method })
1193
+ ] }),
1194
+ /* @__PURE__ */ u3("tr", { children: [
1195
+ /* @__PURE__ */ u3("th", { class: "profiler-text--sm", children: "Status" }),
1196
+ /* @__PURE__ */ u3("td", { children: profile.status })
1197
+ ] }),
1198
+ /* @__PURE__ */ u3("tr", { children: [
1199
+ /* @__PURE__ */ u3("th", { class: "profiler-text--sm", children: "Duration" }),
1200
+ /* @__PURE__ */ u3("td", { children: [
1201
+ profile.duration.toFixed(2),
1202
+ " ms"
1203
+ ] })
1204
+ ] })
1205
+ ] }),
1206
+ profile.headers && Object.keys(profile.headers).length > 0 && /* @__PURE__ */ u3(k, { children: [
1207
+ /* @__PURE__ */ u3("h2", { class: "profiler-section__header profiler-mt-6", children: "Request Headers" }),
1208
+ /* @__PURE__ */ u3("table", { children: Object.entries(profile.headers).map(([k3, v3]) => /* @__PURE__ */ u3("tr", { children: [
1209
+ /* @__PURE__ */ u3("th", { class: "profiler-text--sm", style: "width: 200px;", children: k3 }),
1210
+ /* @__PURE__ */ u3("td", { class: "profiler-text--mono profiler-text--xs", children: String(v3) })
1211
+ ] }, k3)) })
1212
+ ] }),
1213
+ profile.params && Object.keys(profile.params).length > 0 && /* @__PURE__ */ u3(k, { children: [
1214
+ /* @__PURE__ */ u3("h2", { class: "profiler-section__header profiler-mt-6", children: "Request Params" }),
1215
+ /* @__PURE__ */ u3("pre", { class: "profiler-code profiler-text--xs", children: JSON.stringify(profile.params, null, 2) })
1216
+ ] }),
1217
+ profile.request_body && /* @__PURE__ */ u3(k, { children: [
1218
+ /* @__PURE__ */ u3("h2", { class: "profiler-section__header profiler-mt-6", children: "Request Body" }),
1219
+ /* @__PURE__ */ u3(BodyBlock, { body: profile.request_body, encoding: profile.request_body_encoding })
1220
+ ] }),
1221
+ profile.response_headers && Object.keys(profile.response_headers).length > 0 && /* @__PURE__ */ u3(k, { children: [
1222
+ /* @__PURE__ */ u3("h2", { class: "profiler-section__header profiler-mt-6", children: "Response Headers" }),
1223
+ /* @__PURE__ */ u3("table", { children: Object.entries(profile.response_headers).map(([k3, v3]) => /* @__PURE__ */ u3("tr", { children: [
1224
+ /* @__PURE__ */ u3("th", { class: "profiler-text--sm", style: "width: 200px;", children: k3 }),
1225
+ /* @__PURE__ */ u3("td", { class: "profiler-text--mono profiler-text--xs", children: String(v3) })
1226
+ ] }, k3)) })
1227
+ ] }),
1228
+ profile.response_body && /* @__PURE__ */ u3(k, { children: [
1229
+ /* @__PURE__ */ u3("h2", { class: "profiler-section__header profiler-mt-6", children: "Response Body" }),
1230
+ /* @__PURE__ */ u3(BodyBlock, { body: profile.response_body, encoding: profile.response_body_encoding })
1231
+ ] }),
1232
+ /* @__PURE__ */ u3("h2", { class: "profiler-section__header profiler-mt-6", children: "Curl Command" }),
1233
+ /* @__PURE__ */ u3("div", { style: "position: relative;", children: [
1234
+ /* @__PURE__ */ u3(
1235
+ "button",
1236
+ {
1237
+ onClick: copyToClipboard,
1238
+ class: "profiler-body-download-btn",
1239
+ style: "position: absolute; top: 8px; right: 8px; z-index: 1;",
1240
+ children: copied ? "Copied!" : "Copy"
1241
+ }
1242
+ ),
1243
+ /* @__PURE__ */ u3("pre", { class: "profiler-code profiler-text--xs", style: "padding-right: 80px;", children: curl })
1244
+ ] })
1245
+ ] });
1246
+ }
1247
+
1248
+ // app/assets/typescript/profiler/components/dashboard/tabs/DatabaseTab.tsx
1249
+ function DatabaseTab({ dbData }) {
1250
+ if (!dbData?.queries) {
1251
+ return /* @__PURE__ */ u3("div", { class: "profiler-empty", children: /* @__PURE__ */ u3("p", { class: "profiler-empty__description", children: "No database queries recorded" }) });
1252
+ }
1253
+ return /* @__PURE__ */ u3(k, { children: [
1254
+ /* @__PURE__ */ u3("h2", { class: "profiler-section__header", children: [
1255
+ "Database Queries (",
1256
+ dbData.total_queries,
1257
+ ")"
1258
+ ] }),
1259
+ /* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-4 profiler-mb-4 profiler-text--sm", children: [
1260
+ /* @__PURE__ */ u3("span", { children: [
1261
+ "Total Duration: ",
1262
+ /* @__PURE__ */ u3("strong", { children: [
1263
+ dbData.total_duration,
1264
+ " ms"
1265
+ ] })
1266
+ ] }),
1267
+ /* @__PURE__ */ u3("span", { children: [
1268
+ "Slow Queries: ",
1269
+ /* @__PURE__ */ u3("strong", { class: "profiler-text--error", children: dbData.slow_queries })
1270
+ ] }),
1271
+ /* @__PURE__ */ u3("span", { children: [
1272
+ "Cached: ",
1273
+ /* @__PURE__ */ u3("strong", { children: dbData.cached_queries })
1274
+ ] })
1275
+ ] }),
1276
+ dbData.queries.map((query, index) => /* @__PURE__ */ u3("div", { class: `profiler-query-card${query.slow ? " profiler-query-card--slow" : ""}`, children: [
1277
+ /* @__PURE__ */ u3("div", { class: "profiler-query-card__header", children: [
1278
+ /* @__PURE__ */ u3("span", { class: "profiler-text--muted", children: [
1279
+ "#",
1280
+ index + 1
1281
+ ] }),
1282
+ /* @__PURE__ */ u3("span", { class: `profiler-query-card__duration ${query.slow ? "profiler-query-card__duration--slow" : "profiler-query-card__duration--fast"}`, children: [
1283
+ query.duration.toFixed(2),
1284
+ " ms"
1285
+ ] })
1286
+ ] }),
1287
+ /* @__PURE__ */ u3("code", { class: "profiler-query-card__code", children: query.sql })
1288
+ ] }, index))
1289
+ ] });
1290
+ }
1291
+
1292
+ // app/assets/typescript/profiler/components/dashboard/tabs/DumpsTab.tsx
1293
+ function DumpsTab({ dumpData }) {
1294
+ if (!dumpData?.dumps?.length) {
1295
+ return /* @__PURE__ */ u3("div", { class: "profiler-empty", children: [
1296
+ /* @__PURE__ */ u3("div", { class: "profiler-empty__icon", children: "\u{1F50D}" }),
1297
+ /* @__PURE__ */ u3("h3", { class: "profiler-empty__title", children: "No dumps recorded" }),
1298
+ /* @__PURE__ */ u3("div", { class: "profiler-empty__description", children: [
1299
+ /* @__PURE__ */ u3("p", { children: [
1300
+ "Use ",
1301
+ /* @__PURE__ */ u3("code", { children: "Profiler.dump(variable)" }),
1302
+ " in your code to dump variables here."
1303
+ ] }),
1304
+ /* @__PURE__ */ u3("p", { class: "profiler-mt-3", children: [
1305
+ "Example: ",
1306
+ /* @__PURE__ */ u3("code", { children: 'Profiler.dump(@user, "Current user")' })
1307
+ ] })
1308
+ ] })
1309
+ ] });
1310
+ }
1311
+ return /* @__PURE__ */ u3(k, { children: [
1312
+ /* @__PURE__ */ u3("h2", { class: "profiler-section__header", children: [
1313
+ "Dumped Variables (",
1314
+ dumpData.count,
1315
+ ")"
1316
+ ] }),
1317
+ dumpData.dumps.map((dump, index) => /* @__PURE__ */ u3("div", { class: "profiler-dump-card", children: [
1318
+ /* @__PURE__ */ u3("div", { class: "profiler-dump-card__header", children: [
1319
+ /* @__PURE__ */ u3("div", { children: [
1320
+ /* @__PURE__ */ u3("span", { class: "profiler-text--xs profiler-text--muted", children: [
1321
+ "#",
1322
+ index + 1
1323
+ ] }),
1324
+ dump.label && /* @__PURE__ */ u3("span", { class: "profiler-dump-card__label", children: dump.label })
1325
+ ] }),
1326
+ /* @__PURE__ */ u3("div", { children: [
1327
+ /* @__PURE__ */ u3("div", { class: "profiler-dump-card__location", children: [
1328
+ dump.file,
1329
+ ":",
1330
+ dump.line
1331
+ ] }),
1332
+ /* @__PURE__ */ u3("div", { class: "profiler-text--xs profiler-text--muted profiler-mt-1", children: new Date(dump.timestamp).toLocaleTimeString("en", { hour12: false }) })
1333
+ ] })
1334
+ ] }),
1335
+ /* @__PURE__ */ u3("div", { class: "profiler-dump-card__content", children: /* @__PURE__ */ u3("pre", { children: dump.formatted }) })
1336
+ ] }, index))
1337
+ ] });
1338
+ }
1339
+
1340
+ // app/assets/typescript/profiler/flamegraph/FlameGraphRenderer.ts
1341
+ var CATEGORY_COLORS = {
1342
+ controller: "#60a5fa",
1343
+ view: "#34d399",
1344
+ partial: "#f59e0b",
1345
+ sql: "#fb923c",
1346
+ cache: "#a78bfa",
1347
+ http: "#f87171"
1348
+ };
1349
+ var FRAME_HEIGHT = 24;
1350
+ var FRAME_GAP = 1;
1351
+ var ROW_HEIGHT = FRAME_HEIGHT + FRAME_GAP;
1352
+ var MIN_TEXT_WIDTH = 60;
1353
+ var ZOOM_ANIM_MS = 300;
1354
+ var FlameGraphRenderer = class {
1355
+ constructor(canvas, rootEvents, callbacks) {
1356
+ this.frames = [];
1357
+ this.maxDepth = 0;
1358
+ this.globalStart = 0;
1359
+ this.globalEnd = 0;
1360
+ this.viewport = { start: 0, end: 0 };
1361
+ this.targetViewport = { start: 0, end: 0 };
1362
+ this.animating = false;
1363
+ this.animStart = 0;
1364
+ this.prevViewport = { start: 0, end: 0 };
1365
+ this.dpr = 1;
1366
+ this.hoveredFrame = null;
1367
+ this.zoomStack = [];
1368
+ this.isPanning = false;
1369
+ this.panStartX = 0;
1370
+ this.panStartViewport = { start: 0, end: 0 };
1371
+ this.boundHandlers = {};
1372
+ this.canvas = canvas;
1373
+ this.ctx = canvas.getContext("2d");
1374
+ this.callbacks = callbacks;
1375
+ this.flatten(rootEvents);
1376
+ this.computeGlobalBounds();
1377
+ this.viewport = { start: this.globalStart, end: this.globalEnd };
1378
+ this.targetViewport = { ...this.viewport };
1379
+ this.setupCanvas();
1380
+ this.bindEvents();
1381
+ this.render();
1382
+ }
1383
+ flatten(rootEvents) {
1384
+ this.frames = [];
1385
+ this.maxDepth = 0;
1386
+ const walk = (nodes, depth) => {
1387
+ for (const node of nodes) {
1388
+ this.frames.push({
1389
+ node,
1390
+ depth,
1391
+ absStart: node.started_at,
1392
+ absEnd: node.finished_at
1393
+ });
1394
+ if (depth > this.maxDepth) this.maxDepth = depth;
1395
+ if (node.children?.length) walk(node.children, depth + 1);
1396
+ }
1397
+ };
1398
+ walk(rootEvents, 0);
1399
+ }
1400
+ computeGlobalBounds() {
1401
+ if (this.frames.length === 0) {
1402
+ this.globalStart = 0;
1403
+ this.globalEnd = 1;
1404
+ return;
1405
+ }
1406
+ this.globalStart = Math.min(...this.frames.map((f4) => f4.absStart));
1407
+ this.globalEnd = Math.max(...this.frames.map((f4) => f4.absEnd));
1408
+ if (this.globalEnd <= this.globalStart) this.globalEnd = this.globalStart + 1;
1409
+ }
1410
+ setupCanvas() {
1411
+ this.dpr = window.devicePixelRatio || 1;
1412
+ this.resizeCanvas();
1413
+ }
1414
+ resizeCanvas() {
1415
+ const rect = this.canvas.getBoundingClientRect();
1416
+ const w3 = rect.width;
1417
+ const h3 = (this.maxDepth + 1) * ROW_HEIGHT + 8;
1418
+ this.canvas.width = w3 * this.dpr;
1419
+ this.canvas.height = h3 * this.dpr;
1420
+ this.canvas.style.height = `${h3}px`;
1421
+ this.ctx.setTransform(this.dpr, 0, 0, this.dpr, 0, 0);
1422
+ this.render();
1423
+ }
1424
+ bindEvents() {
1425
+ this.boundHandlers.mousemove = (e3) => this.onMouseMove(e3);
1426
+ this.boundHandlers.mouseleave = () => this.onMouseLeave();
1427
+ this.boundHandlers.click = (e3) => this.onClickEvent(e3);
1428
+ this.boundHandlers.wheel = (e3) => this.onWheel(e3);
1429
+ this.boundHandlers.mousedown = (e3) => this.onMouseDown(e3);
1430
+ this.boundHandlers.mouseupmove = (e3) => this.onMouseUpOrMove(e3);
1431
+ this.boundHandlers.mouseup = () => this.onMouseUp();
1432
+ this.canvas.addEventListener("mousemove", this.boundHandlers.mousemove);
1433
+ this.canvas.addEventListener("mouseleave", this.boundHandlers.mouseleave);
1434
+ this.canvas.addEventListener("click", this.boundHandlers.click);
1435
+ this.canvas.addEventListener("wheel", this.boundHandlers.wheel, { passive: false });
1436
+ this.canvas.addEventListener("mousedown", this.boundHandlers.mousedown);
1437
+ window.addEventListener("mousemove", this.boundHandlers.mouseupmove);
1438
+ window.addEventListener("mouseup", this.boundHandlers.mouseup);
1439
+ }
1440
+ hitTest(clientX, clientY) {
1441
+ const rect = this.canvas.getBoundingClientRect();
1442
+ const mx = clientX - rect.left;
1443
+ const my = clientY - rect.top;
1444
+ const w3 = rect.width;
1445
+ const vpRange = this.viewport.end - this.viewport.start;
1446
+ for (let i3 = this.frames.length - 1; i3 >= 0; i3--) {
1447
+ const f4 = this.frames[i3];
1448
+ const x2 = (f4.absStart - this.viewport.start) / vpRange * w3;
1449
+ const fw = (f4.absEnd - f4.absStart) / vpRange * w3;
1450
+ const y3 = f4.depth * ROW_HEIGHT;
1451
+ if (mx >= x2 && mx <= x2 + fw && my >= y3 && my <= y3 + FRAME_HEIGHT) {
1452
+ return f4;
1453
+ }
1454
+ }
1455
+ return null;
1456
+ }
1457
+ onMouseMove(e3) {
1458
+ if (this.isPanning) return;
1459
+ const frame = this.hitTest(e3.clientX, e3.clientY);
1460
+ this.hoveredFrame = frame;
1461
+ this.canvas.style.cursor = frame ? "pointer" : "default";
1462
+ this.callbacks.onHover(frame, e3.clientX, e3.clientY);
1463
+ this.render();
1464
+ }
1465
+ onMouseLeave() {
1466
+ this.hoveredFrame = null;
1467
+ this.canvas.style.cursor = "default";
1468
+ this.callbacks.onHover(null, 0, 0);
1469
+ this.render();
1470
+ }
1471
+ onClickEvent(e3) {
1472
+ if (this.isPanning) return;
1473
+ const frame = this.hitTest(e3.clientX, e3.clientY);
1474
+ if (frame) {
1475
+ this.callbacks.onClick(frame);
1476
+ this.zoomTo(frame.node);
1477
+ }
1478
+ }
1479
+ onWheel(e3) {
1480
+ e3.preventDefault();
1481
+ const rect = this.canvas.getBoundingClientRect();
1482
+ const mx = e3.clientX - rect.left;
1483
+ const ratio = mx / rect.width;
1484
+ const vpRange = this.viewport.end - this.viewport.start;
1485
+ const zoomFactor = e3.deltaY > 0 ? 1.3 : 0.7;
1486
+ const newRange = Math.min(this.globalEnd - this.globalStart, vpRange * zoomFactor);
1487
+ const center = this.viewport.start + ratio * vpRange;
1488
+ let newStart = center - ratio * newRange;
1489
+ let newEnd = center + (1 - ratio) * newRange;
1490
+ if (newStart < this.globalStart) {
1491
+ newStart = this.globalStart;
1492
+ newEnd = newStart + newRange;
1493
+ }
1494
+ if (newEnd > this.globalEnd) {
1495
+ newEnd = this.globalEnd;
1496
+ newStart = newEnd - newRange;
1497
+ }
1498
+ this.animateViewport({ start: newStart, end: newEnd });
1499
+ this.updateZoomStack(newStart, newEnd);
1500
+ }
1501
+ onMouseDown(e3) {
1502
+ if (e3.button !== 0) return;
1503
+ this.isPanning = true;
1504
+ this.panStartX = e3.clientX;
1505
+ this.panStartViewport = { ...this.viewport };
1506
+ this.canvas.style.cursor = "grabbing";
1507
+ }
1508
+ onMouseUpOrMove(e3) {
1509
+ if (!this.isPanning) return;
1510
+ const dx = e3.clientX - this.panStartX;
1511
+ const rect = this.canvas.getBoundingClientRect();
1512
+ const vpRange = this.panStartViewport.end - this.panStartViewport.start;
1513
+ const shift = -(dx / rect.width) * vpRange;
1514
+ let newStart = this.panStartViewport.start + shift;
1515
+ let newEnd = this.panStartViewport.end + shift;
1516
+ if (newStart < this.globalStart) {
1517
+ newEnd += this.globalStart - newStart;
1518
+ newStart = this.globalStart;
1519
+ }
1520
+ if (newEnd > this.globalEnd) {
1521
+ newStart -= newEnd - this.globalEnd;
1522
+ newEnd = this.globalEnd;
1523
+ }
1524
+ this.viewport = { start: newStart, end: newEnd };
1525
+ this.targetViewport = { ...this.viewport };
1526
+ this.render();
1527
+ }
1528
+ onMouseUp() {
1529
+ if (this.isPanning) {
1530
+ this.isPanning = false;
1531
+ this.canvas.style.cursor = "default";
1532
+ }
1533
+ }
1534
+ zoomTo(node) {
1535
+ const ancestors = [];
1536
+ const findPath = (nodes, target, path) => {
1537
+ for (const n2 of nodes) {
1538
+ if (n2 === target) {
1539
+ ancestors.push(...path, n2);
1540
+ return true;
1541
+ }
1542
+ if (n2.children?.length && findPath(n2.children, target, [...path, n2])) return true;
1543
+ }
1544
+ return false;
1545
+ };
1546
+ const rootNodes = this.frames.filter((f4) => f4.depth === 0).map((f4) => f4.node);
1547
+ findPath(rootNodes, node, []);
1548
+ this.zoomStack = ancestors;
1549
+ this.animateViewport({ start: node.started_at, end: node.finished_at });
1550
+ this.callbacks.onZoomChange(ancestors);
1551
+ }
1552
+ resetZoom() {
1553
+ this.zoomStack = [];
1554
+ this.animateViewport({ start: this.globalStart, end: this.globalEnd });
1555
+ this.callbacks.onZoomChange([]);
1556
+ }
1557
+ updateZoomStack(start, end) {
1558
+ const fullRange = this.globalEnd - this.globalStart;
1559
+ const newRange = end - start;
1560
+ if (newRange >= fullRange * 0.99) {
1561
+ this.zoomStack = [];
1562
+ this.callbacks.onZoomChange([]);
1563
+ }
1564
+ }
1565
+ animateViewport(target) {
1566
+ this.prevViewport = { ...this.viewport };
1567
+ this.targetViewport = target;
1568
+ this.animStart = performance.now();
1569
+ if (!this.animating) {
1570
+ this.animating = true;
1571
+ this.animLoop();
1572
+ }
1573
+ }
1574
+ animLoop() {
1575
+ const t3 = Math.min(1, (performance.now() - this.animStart) / ZOOM_ANIM_MS);
1576
+ const ease = t3 < 0.5 ? 2 * t3 * t3 : 1 - Math.pow(-2 * t3 + 2, 2) / 2;
1577
+ this.viewport = {
1578
+ start: this.prevViewport.start + (this.targetViewport.start - this.prevViewport.start) * ease,
1579
+ end: this.prevViewport.end + (this.targetViewport.end - this.prevViewport.end) * ease
1580
+ };
1581
+ this.render();
1582
+ if (t3 < 1) {
1583
+ requestAnimationFrame(() => this.animLoop());
1584
+ } else {
1585
+ this.animating = false;
1586
+ this.viewport = { ...this.targetViewport };
1587
+ this.render();
1588
+ }
1589
+ }
1590
+ render() {
1591
+ const ctx = this.ctx;
1592
+ const w3 = this.canvas.width / this.dpr;
1593
+ const h3 = this.canvas.height / this.dpr;
1594
+ const vpRange = this.viewport.end - this.viewport.start;
1595
+ ctx.clearRect(0, 0, w3, h3);
1596
+ const style = getComputedStyle(this.canvas);
1597
+ const textColor = style.getPropertyValue("--profiler-text").trim() || "#eef2f7";
1598
+ const textMuted = style.getPropertyValue("--profiler-text-muted").trim() || "#5e7080";
1599
+ for (const frame of this.frames) {
1600
+ const x2 = (frame.absStart - this.viewport.start) / vpRange * w3;
1601
+ const fw = (frame.absEnd - frame.absStart) / vpRange * w3;
1602
+ const y3 = frame.depth * ROW_HEIGHT;
1603
+ if (x2 + fw < 0 || x2 > w3 || fw < 0.5) continue;
1604
+ const color = CATEGORY_COLORS[frame.node.category] || "#a78bfa";
1605
+ const isHovered = frame === this.hoveredFrame;
1606
+ ctx.fillStyle = isHovered ? this.lightenColor(color, 0.2) : color;
1607
+ ctx.globalAlpha = isHovered ? 1 : 0.85;
1608
+ this.roundRect(ctx, x2, y3, fw, FRAME_HEIGHT, 3);
1609
+ ctx.fill();
1610
+ ctx.globalAlpha = 1;
1611
+ if (isHovered) {
1612
+ ctx.strokeStyle = "#ffffff";
1613
+ ctx.lineWidth = 1.5;
1614
+ this.roundRect(ctx, x2, y3, fw, FRAME_HEIGHT, 3);
1615
+ ctx.stroke();
1616
+ }
1617
+ if (fw > MIN_TEXT_WIDTH) {
1618
+ ctx.fillStyle = this.getTextColor(color);
1619
+ ctx.font = '11px "JetBrains Mono", monospace';
1620
+ ctx.textBaseline = "middle";
1621
+ const label = frame.node.name;
1622
+ const duration = `${frame.node.duration.toFixed(1)}ms`;
1623
+ const maxTextWidth = fw - 8;
1624
+ const durationWidth = ctx.measureText(duration).width;
1625
+ if (maxTextWidth > durationWidth + 20) {
1626
+ const nameMaxWidth = maxTextWidth - durationWidth - 8;
1627
+ ctx.fillText(this.truncateText(ctx, label, nameMaxWidth), x2 + 4, y3 + FRAME_HEIGHT / 2);
1628
+ ctx.fillStyle = isHovered ? textColor : textMuted;
1629
+ ctx.fillText(duration, x2 + fw - durationWidth - 4, y3 + FRAME_HEIGHT / 2);
1630
+ } else {
1631
+ ctx.fillText(this.truncateText(ctx, label, maxTextWidth), x2 + 4, y3 + FRAME_HEIGHT / 2);
1632
+ }
1633
+ }
1634
+ }
1635
+ }
1636
+ roundRect(ctx, x2, y3, w3, h3, r3) {
1637
+ ctx.beginPath();
1638
+ ctx.moveTo(x2 + r3, y3);
1639
+ ctx.lineTo(x2 + w3 - r3, y3);
1640
+ ctx.quadraticCurveTo(x2 + w3, y3, x2 + w3, y3 + r3);
1641
+ ctx.lineTo(x2 + w3, y3 + h3 - r3);
1642
+ ctx.quadraticCurveTo(x2 + w3, y3 + h3, x2 + w3 - r3, y3 + h3);
1643
+ ctx.lineTo(x2 + r3, y3 + h3);
1644
+ ctx.quadraticCurveTo(x2, y3 + h3, x2, y3 + h3 - r3);
1645
+ ctx.lineTo(x2, y3 + r3);
1646
+ ctx.quadraticCurveTo(x2, y3, x2 + r3, y3);
1647
+ ctx.closePath();
1648
+ }
1649
+ truncateText(ctx, text, maxWidth) {
1650
+ if (ctx.measureText(text).width <= maxWidth) return text;
1651
+ let truncated = text;
1652
+ while (truncated.length > 0 && ctx.measureText(truncated + "...").width > maxWidth) {
1653
+ truncated = truncated.slice(0, -1);
1654
+ }
1655
+ return truncated.length > 0 ? truncated + "..." : "";
1656
+ }
1657
+ lightenColor(hex, amount) {
1658
+ const num = parseInt(hex.slice(1), 16);
1659
+ const r3 = Math.min(255, (num >> 16 & 255) + Math.round(255 * amount));
1660
+ const g2 = Math.min(255, (num >> 8 & 255) + Math.round(255 * amount));
1661
+ const b = Math.min(255, (num & 255) + Math.round(255 * amount));
1662
+ return `rgb(${r3},${g2},${b})`;
1663
+ }
1664
+ getTextColor(bgHex) {
1665
+ const num = parseInt(bgHex.slice(1), 16);
1666
+ const r3 = num >> 16 & 255;
1667
+ const g2 = num >> 8 & 255;
1668
+ const b = num & 255;
1669
+ const luminance = (0.299 * r3 + 0.587 * g2 + 0.114 * b) / 255;
1670
+ return luminance > 0.5 ? "#1a1a2e" : "#f0f0f0";
1671
+ }
1672
+ destroy() {
1673
+ this.canvas.removeEventListener("mousemove", this.boundHandlers.mousemove);
1674
+ this.canvas.removeEventListener("mouseleave", this.boundHandlers.mouseleave);
1675
+ this.canvas.removeEventListener("click", this.boundHandlers.click);
1676
+ this.canvas.removeEventListener("wheel", this.boundHandlers.wheel);
1677
+ this.canvas.removeEventListener("mousedown", this.boundHandlers.mousedown);
1678
+ window.removeEventListener("mousemove", this.boundHandlers.mouseupmove);
1679
+ window.removeEventListener("mouseup", this.boundHandlers.mouseup);
1680
+ }
1681
+ };
1682
+
1683
+ // app/assets/typescript/profiler/flamegraph/FlameGraphTooltip.ts
1684
+ var CATEGORY_LABELS = {
1685
+ controller: "Controller",
1686
+ view: "View",
1687
+ partial: "Partial",
1688
+ sql: "SQL",
1689
+ cache: "Cache",
1690
+ http: "HTTP"
1691
+ };
1692
+ var CATEGORY_COLORS2 = {
1693
+ controller: "#60a5fa",
1694
+ view: "#34d399",
1695
+ partial: "#f59e0b",
1696
+ sql: "#fb923c",
1697
+ cache: "#a78bfa",
1698
+ http: "#f87171"
1699
+ };
1700
+ var FlameGraphTooltip = class {
1701
+ constructor(container, totalDuration) {
1702
+ this.totalDuration = totalDuration;
1703
+ this.el = document.createElement("div");
1704
+ this.el.className = "profiler-flamegraph__tooltip";
1705
+ container.appendChild(this.el);
1706
+ }
1707
+ show(frame, x2, y3) {
1708
+ const node = frame.node;
1709
+ const category = node.category;
1710
+ const color = CATEGORY_COLORS2[category] || "#a78bfa";
1711
+ const label = CATEGORY_LABELS[category] || category;
1712
+ const pctTotal = this.totalDuration > 0 ? (node.duration / this.totalDuration * 100).toFixed(1) : "0";
1713
+ this.el.textContent = "";
1714
+ const header = document.createElement("div");
1715
+ header.className = "tooltip-header";
1716
+ header.textContent = node.name;
1717
+ this.el.appendChild(header);
1718
+ this.addRow("Category", () => {
1719
+ const badge = document.createElement("span");
1720
+ badge.className = "tooltip-badge";
1721
+ badge.style.background = color;
1722
+ badge.textContent = label;
1723
+ return badge;
1724
+ });
1725
+ this.addRow("Duration", () => {
1726
+ const span = document.createElement("span");
1727
+ span.className = "value";
1728
+ span.textContent = `${node.duration.toFixed(2)} ms`;
1729
+ return span;
1730
+ });
1731
+ this.addRow("% of total", () => {
1732
+ const span = document.createElement("span");
1733
+ span.className = "value";
1734
+ span.textContent = `${pctTotal}%`;
1735
+ return span;
1736
+ });
1737
+ if (node.payload) {
1738
+ let payloadText = null;
1739
+ if (category === "sql" && node.payload.sql) {
1740
+ payloadText = node.payload.sql.length > 200 ? node.payload.sql.slice(0, 200) + "..." : node.payload.sql;
1741
+ } else if (category === "cache" && node.payload.key) {
1742
+ payloadText = `Key: ${node.payload.key}`;
1743
+ } else if (category === "http" && node.payload.url) {
1744
+ payloadText = node.payload.url;
1745
+ }
1746
+ if (payloadText) {
1747
+ const payloadDiv = document.createElement("div");
1748
+ payloadDiv.className = "tooltip-payload";
1749
+ payloadDiv.textContent = payloadText;
1750
+ this.el.appendChild(payloadDiv);
1751
+ }
1752
+ }
1753
+ let left = x2 + 12;
1754
+ let top = y3 - 8;
1755
+ const tipRect = this.el.getBoundingClientRect();
1756
+ if (left + tipRect.width > window.innerWidth) left = x2 - tipRect.width - 12;
1757
+ if (top + tipRect.height > window.innerHeight) top = y3 - tipRect.height - 8;
1758
+ if (top < 0) top = 4;
1759
+ this.el.style.left = `${left}px`;
1760
+ this.el.style.top = `${top}px`;
1761
+ this.el.classList.add("visible");
1762
+ }
1763
+ hide() {
1764
+ this.el.classList.remove("visible");
1765
+ }
1766
+ destroy() {
1767
+ this.el.remove();
1768
+ }
1769
+ addRow(labelText, valueFactory) {
1770
+ const row = document.createElement("div");
1771
+ row.className = "tooltip-row";
1772
+ const labelEl = document.createElement("span");
1773
+ labelEl.className = "label";
1774
+ labelEl.textContent = labelText;
1775
+ row.appendChild(labelEl);
1776
+ row.appendChild(valueFactory());
1777
+ this.el.appendChild(row);
1778
+ }
1779
+ };
1780
+
1781
+ // app/assets/typescript/profiler/flamegraph/FlameGraphBreadcrumbs.ts
1782
+ var FlameGraphBreadcrumbs = class {
1783
+ constructor(container, onNavigate) {
1784
+ this.onNavigate = onNavigate;
1785
+ this.el = document.createElement("div");
1786
+ this.el.className = "profiler-flamegraph__breadcrumbs";
1787
+ container.appendChild(this.el);
1788
+ }
1789
+ update(ancestors) {
1790
+ this.el.textContent = "";
1791
+ if (ancestors.length === 0) {
1792
+ this.el.style.display = "none";
1793
+ return;
1794
+ }
1795
+ this.el.style.display = "flex";
1796
+ const rootLink = document.createElement("button");
1797
+ rootLink.className = "breadcrumb-item";
1798
+ rootLink.textContent = "Root";
1799
+ rootLink.addEventListener("click", () => this.onNavigate(null));
1800
+ this.el.appendChild(rootLink);
1801
+ for (const node of ancestors) {
1802
+ const sep = document.createElement("span");
1803
+ sep.className = "breadcrumb-separator";
1804
+ sep.textContent = "\u203A";
1805
+ this.el.appendChild(sep);
1806
+ const link = document.createElement("button");
1807
+ link.className = "breadcrumb-item";
1808
+ const name = node.name.length > 30 ? node.name.slice(0, 30) + "..." : node.name;
1809
+ link.textContent = name;
1810
+ link.addEventListener("click", () => this.onNavigate(node));
1811
+ this.el.appendChild(link);
1812
+ }
1813
+ }
1814
+ destroy() {
1815
+ this.el.remove();
1816
+ }
1817
+ };
1818
+
1819
+ // app/assets/typescript/profiler/components/dashboard/tabs/FlameGraphTab.tsx
1820
+ var CATEGORY_COLORS3 = {
1821
+ controller: "#60a5fa",
1822
+ view: "#34d399",
1823
+ partial: "#f59e0b",
1824
+ sql: "#fb923c",
1825
+ cache: "#a78bfa",
1826
+ http: "#f87171"
1827
+ };
1828
+ var CATEGORY_LABELS2 = {
1829
+ controller: "Controller",
1830
+ view: "View",
1831
+ partial: "Partial",
1832
+ sql: "SQL",
1833
+ cache: "Cache",
1834
+ http: "HTTP"
1835
+ };
1836
+ function FlameGraphTab({ flamegraphData, perfData }) {
1837
+ const canvasRef = A2(null);
1838
+ const containerRef = A2(null);
1839
+ const rendererRef = A2(null);
1840
+ const tooltipRef = A2(null);
1841
+ const breadcrumbsRef = A2(null);
1842
+ const [isZoomed, setIsZoomed] = d2(false);
1843
+ const data = flamegraphData;
1844
+ y2(() => {
1845
+ if (!data?.root_events?.length || !canvasRef.current || !containerRef.current) return;
1846
+ const container = containerRef.current;
1847
+ const canvas = canvasRef.current;
1848
+ const tooltip = new FlameGraphTooltip(container, data.total_duration);
1849
+ tooltipRef.current = tooltip;
1850
+ const breadcrumbs = new FlameGraphBreadcrumbs(container, (node) => {
1851
+ if (!rendererRef.current) return;
1852
+ if (node) {
1853
+ rendererRef.current.zoomTo(node);
1854
+ } else {
1855
+ rendererRef.current.resetZoom();
1856
+ }
1857
+ });
1858
+ breadcrumbsRef.current = breadcrumbs;
1859
+ container.insertBefore(breadcrumbs["el"], canvas);
1860
+ const renderer = new FlameGraphRenderer(canvas, data.root_events, {
1861
+ onHover: (frame, x2, y3) => {
1862
+ if (frame) {
1863
+ tooltip.show(frame, x2, y3);
1864
+ } else {
1865
+ tooltip.hide();
1866
+ }
1867
+ },
1868
+ onClick: (_frame) => {
1869
+ setIsZoomed(true);
1870
+ },
1871
+ onZoomChange: (ancestors) => {
1872
+ breadcrumbs.update(ancestors);
1873
+ setIsZoomed(ancestors.length > 0);
1874
+ }
1875
+ });
1876
+ rendererRef.current = renderer;
1877
+ const handleResize = () => renderer.resizeCanvas();
1878
+ window.addEventListener("resize", handleResize);
1879
+ const handleTheme = () => renderer.render();
1880
+ document.addEventListener("profiler:theme-change", handleTheme);
1881
+ return () => {
1882
+ renderer.destroy();
1883
+ tooltip.destroy();
1884
+ breadcrumbs.destroy();
1885
+ window.removeEventListener("resize", handleResize);
1886
+ document.removeEventListener("profiler:theme-change", handleTheme);
1887
+ rendererRef.current = null;
1888
+ tooltipRef.current = null;
1889
+ breadcrumbsRef.current = null;
1890
+ };
1891
+ }, [data]);
1892
+ if (!data?.root_events?.length) {
1893
+ if (!perfData?.events?.length) {
1894
+ return /* @__PURE__ */ u3("div", { class: "profiler-empty", children: /* @__PURE__ */ u3("p", { class: "profiler-empty__description", children: "No performance events recorded" }) });
1895
+ }
1896
+ return /* @__PURE__ */ u3(k, { children: [
1897
+ /* @__PURE__ */ u3("h2", { class: "profiler-section__header", children: [
1898
+ "Performance Timeline (",
1899
+ perfData.total_events,
1900
+ " events)"
1901
+ ] }),
1902
+ /* @__PURE__ */ u3("p", { class: "profiler-mb-4", children: [
1903
+ "Total Duration: ",
1904
+ /* @__PURE__ */ u3("strong", { children: [
1905
+ perfData.total_duration,
1906
+ " ms"
1907
+ ] })
1908
+ ] }),
1909
+ perfData.events.map((event, index) => /* @__PURE__ */ u3("div", { class: "profiler-query-card", children: [
1910
+ /* @__PURE__ */ u3("div", { class: "profiler-query-card__header", children: [
1911
+ /* @__PURE__ */ u3("strong", { children: event.name }),
1912
+ /* @__PURE__ */ u3("span", { class: event.duration >= 500 ? "badge-error" : event.duration >= 100 ? "badge-warning" : "badge-success", children: [
1913
+ event.duration.toFixed(2),
1914
+ " ms"
1915
+ ] })
1916
+ ] }),
1917
+ event.payload && Object.keys(event.payload).length > 0 && /* @__PURE__ */ u3("pre", { class: "profiler-text--xs profiler-text--muted profiler-mt-2", children: JSON.stringify(event.payload, null, 2) })
1918
+ ] }, index))
1919
+ ] });
1920
+ }
1921
+ const categoryCounts = {};
1922
+ const countNode = (node) => {
1923
+ const cat = node.category;
1924
+ if (!categoryCounts[cat]) categoryCounts[cat] = { count: 0, duration: 0 };
1925
+ categoryCounts[cat].count++;
1926
+ categoryCounts[cat].duration += node.duration;
1927
+ node.children?.forEach(countNode);
1928
+ };
1929
+ data.root_events.forEach(countNode);
1930
+ const handleReset = () => {
1931
+ rendererRef.current?.resetZoom();
1932
+ setIsZoomed(false);
1933
+ };
1934
+ return /* @__PURE__ */ u3("div", { class: "profiler-flamegraph", children: [
1935
+ /* @__PURE__ */ u3("div", { class: "profiler-flamegraph__stats", children: [
1936
+ /* @__PURE__ */ u3("div", { class: "stat-item", children: [
1937
+ /* @__PURE__ */ u3("span", { class: "stat-label", children: "Total Events" }),
1938
+ /* @__PURE__ */ u3("span", { class: "stat-value", children: data.total_events })
1939
+ ] }),
1940
+ /* @__PURE__ */ u3("div", { class: "stat-item", children: [
1941
+ /* @__PURE__ */ u3("span", { class: "stat-label", children: "Total Duration" }),
1942
+ /* @__PURE__ */ u3("span", { class: "stat-value", children: [
1943
+ data.total_duration.toFixed(2),
1944
+ " ",
1945
+ /* @__PURE__ */ u3("small", { children: "ms" })
1946
+ ] })
1947
+ ] }),
1948
+ Object.keys(categoryCounts).map((cat) => /* @__PURE__ */ u3("div", { class: "stat-item", children: [
1949
+ /* @__PURE__ */ u3("span", { class: "stat-label", children: [
1950
+ /* @__PURE__ */ u3("span", { class: "stat-dot", style: { background: CATEGORY_COLORS3[cat] } }),
1951
+ CATEGORY_LABELS2[cat]
1952
+ ] }),
1953
+ /* @__PURE__ */ u3("span", { class: "stat-value", children: categoryCounts[cat].count })
1954
+ ] }, cat))
1955
+ ] }),
1956
+ /* @__PURE__ */ u3("div", { class: "profiler-flamegraph__legend", children: Object.keys(CATEGORY_COLORS3).map((cat) => /* @__PURE__ */ u3("div", { class: "legend-item", children: [
1957
+ /* @__PURE__ */ u3("span", { class: "legend-color", style: { background: CATEGORY_COLORS3[cat] } }),
1958
+ /* @__PURE__ */ u3("span", { children: CATEGORY_LABELS2[cat] })
1959
+ ] }, cat)) }),
1960
+ /* @__PURE__ */ u3("div", { class: "profiler-flamegraph__controls", children: [
1961
+ isZoomed && /* @__PURE__ */ u3("button", { class: "profiler-flamegraph__reset", onClick: handleReset, children: "Reset Zoom" }),
1962
+ /* @__PURE__ */ u3("span", { class: "profiler-flamegraph__hint", children: "Click to zoom, scroll to zoom in/out, drag to pan" })
1963
+ ] }),
1964
+ /* @__PURE__ */ u3("div", { class: "profiler-flamegraph__canvas-container", ref: containerRef, children: /* @__PURE__ */ u3(
1965
+ "canvas",
1966
+ {
1967
+ ref: canvasRef,
1968
+ class: "profiler-flamegraph__canvas",
1969
+ style: { width: "100%" }
1970
+ }
1971
+ ) })
1972
+ ] });
1973
+ }
1974
+
1975
+ // app/assets/typescript/profiler/components/dashboard/tabs/ViewsTab.tsx
1976
+ function ViewsTab({ viewData }) {
1977
+ if (!viewData) {
1978
+ return /* @__PURE__ */ u3("div", { class: "profiler-empty", children: /* @__PURE__ */ u3("p", { class: "profiler-empty__description", children: "No view data recorded" }) });
1979
+ }
1980
+ return /* @__PURE__ */ u3(k, { children: [
1981
+ /* @__PURE__ */ u3("h2", { class: "profiler-section__header", children: "View Rendering" }),
1982
+ /* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-4 profiler-mb-4 profiler-text--sm", children: [
1983
+ /* @__PURE__ */ u3("span", { children: [
1984
+ "Views: ",
1985
+ /* @__PURE__ */ u3("strong", { children: viewData.total_views })
1986
+ ] }),
1987
+ /* @__PURE__ */ u3("span", { children: [
1988
+ "Partials: ",
1989
+ /* @__PURE__ */ u3("strong", { children: viewData.total_partials })
1990
+ ] }),
1991
+ /* @__PURE__ */ u3("span", { children: [
1992
+ "Total Duration: ",
1993
+ /* @__PURE__ */ u3("strong", { children: [
1994
+ viewData.total_duration,
1995
+ " ms"
1996
+ ] })
1997
+ ] })
1998
+ ] }),
1999
+ viewData.views && viewData.views.length > 0 && /* @__PURE__ */ u3(k, { children: [
2000
+ /* @__PURE__ */ u3("h3", { class: "profiler-text--lg profiler-mt-6 profiler-mb-3", children: "Templates" }),
2001
+ viewData.views.map((view, i3) => /* @__PURE__ */ u3("div", { class: "profiler-query-card profiler-query-card--success", children: /* @__PURE__ */ u3("div", { class: "profiler-query-card__header", children: [
2002
+ /* @__PURE__ */ u3("span", { children: view.identifier }),
2003
+ /* @__PURE__ */ u3("span", { class: "badge-success", children: [
2004
+ view.duration.toFixed(2),
2005
+ " ms"
2006
+ ] })
2007
+ ] }) }, i3))
2008
+ ] }),
2009
+ viewData.partials && viewData.partials.length > 0 && /* @__PURE__ */ u3(k, { children: [
2010
+ /* @__PURE__ */ u3("h3", { class: "profiler-text--lg profiler-mt-6 profiler-mb-3", children: "Partials" }),
2011
+ viewData.partials.map((partial, i3) => /* @__PURE__ */ u3("div", { class: "profiler-query-card profiler-query-card--success", children: /* @__PURE__ */ u3("div", { class: "profiler-query-card__header", children: [
2012
+ /* @__PURE__ */ u3("span", { children: partial.identifier }),
2013
+ /* @__PURE__ */ u3("span", { class: "badge-success", children: [
2014
+ partial.duration.toFixed(2),
2015
+ " ms"
2016
+ ] })
2017
+ ] }) }, i3))
2018
+ ] })
2019
+ ] });
2020
+ }
2021
+
2022
+ // app/assets/typescript/profiler/components/dashboard/tabs/AjaxTab.tsx
2023
+ function methodBadge2(method) {
2024
+ const map = { GET: "info", POST: "success", PUT: "warning", PATCH: "warning", DELETE: "error" };
2025
+ return map[method] || "default";
2026
+ }
2027
+ function statusBadge2(status) {
2028
+ if (status >= 200 && status < 300) return "success";
2029
+ if (status >= 400) return "error";
2030
+ return "warning";
2031
+ }
2032
+ function AjaxTab({ ajaxData }) {
2033
+ if (!ajaxData?.requests?.length) {
2034
+ return /* @__PURE__ */ u3("div", { class: "profiler-empty", children: [
2035
+ /* @__PURE__ */ u3("div", { class: "profiler-empty__icon", children: "\u{1F310}" }),
2036
+ /* @__PURE__ */ u3("h3", { class: "profiler-empty__title", children: "No AJAX requests tracked" }),
2037
+ /* @__PURE__ */ u3("p", { class: "profiler-empty__description", children: "AJAX requests made from this page will appear here." })
2038
+ ] });
2039
+ }
2040
+ const avgDuration = ajaxData.total_duration / ajaxData.total_requests;
2041
+ return /* @__PURE__ */ u3(k, { children: [
2042
+ /* @__PURE__ */ u3("h2", { class: "profiler-section__header", children: [
2043
+ "AJAX Requests (",
2044
+ ajaxData.total_requests,
2045
+ ")"
2046
+ ] }),
2047
+ /* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-4 profiler-mb-4 profiler-text--sm", children: [
2048
+ /* @__PURE__ */ u3("span", { children: [
2049
+ "Total Duration: ",
2050
+ /* @__PURE__ */ u3("strong", { children: [
2051
+ ajaxData.total_duration.toFixed(2),
2052
+ " ms"
2053
+ ] })
2054
+ ] }),
2055
+ /* @__PURE__ */ u3("span", { children: [
2056
+ "Average: ",
2057
+ /* @__PURE__ */ u3("strong", { children: [
2058
+ avgDuration.toFixed(2),
2059
+ " ms"
2060
+ ] })
2061
+ ] })
2062
+ ] }),
2063
+ /* @__PURE__ */ u3("div", { class: "profiler-grid profiler-grid--2 profiler-mb-6", children: [
2064
+ /* @__PURE__ */ u3("div", { class: "profiler-panel profiler-panel--sm", children: [
2065
+ /* @__PURE__ */ u3("h3", { class: "profiler-text--sm profiler-text--muted profiler-text--uppercase profiler-mb-3", children: "By Method" }),
2066
+ Object.entries(ajaxData.by_method).map(([method, count]) => /* @__PURE__ */ u3("div", { class: "profiler-kv-row", children: [
2067
+ /* @__PURE__ */ u3("span", { class: `badge-${methodBadge2(method)}`, children: method }),
2068
+ /* @__PURE__ */ u3("strong", { children: count })
2069
+ ] }, method))
2070
+ ] }),
2071
+ /* @__PURE__ */ u3("div", { class: "profiler-panel profiler-panel--sm", children: [
2072
+ /* @__PURE__ */ u3("h3", { class: "profiler-text--sm profiler-text--muted profiler-text--uppercase profiler-mb-3", children: "By Status" }),
2073
+ Object.entries(ajaxData.by_status).map(([status, count]) => /* @__PURE__ */ u3("div", { class: "profiler-kv-row", children: [
2074
+ /* @__PURE__ */ u3("span", { class: `badge-${status.startsWith("2") ? "success" : status.startsWith("4") || status.startsWith("5") ? "error" : "warning"}`, children: status }),
2075
+ /* @__PURE__ */ u3("strong", { children: count })
2076
+ ] }, status))
2077
+ ] })
2078
+ ] }),
2079
+ /* @__PURE__ */ u3("h3", { class: "profiler-text--lg profiler-mb-3", children: "Requests" }),
2080
+ ajaxData.requests.map((req, index) => /* @__PURE__ */ u3("div", { class: `profiler-ajax-card profiler-ajax-card--${req.status >= 200 && req.status < 300 ? "success" : "error"}`, children: [
2081
+ /* @__PURE__ */ u3("div", { class: "profiler-ajax-card__row", children: [
2082
+ /* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-3", children: [
2083
+ /* @__PURE__ */ u3("span", { class: `profiler-ajax-card__method badge-${methodBadge2(req.method)}`, children: req.method }),
2084
+ /* @__PURE__ */ u3("strong", { class: "profiler-ajax-card__path", children: req.path })
2085
+ ] }),
2086
+ /* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-2", children: [
2087
+ /* @__PURE__ */ u3("span", { class: `badge-${statusBadge2(req.status)}`, children: req.status }),
2088
+ /* @__PURE__ */ u3("span", { class: req.duration >= 500 ? "badge-error" : req.duration >= 100 ? "badge-warning" : "badge-success", children: [
2089
+ req.duration.toFixed(2),
2090
+ " ms"
2091
+ ] })
2092
+ ] })
2093
+ ] }),
2094
+ /* @__PURE__ */ u3("div", { class: "profiler-ajax-card__row", children: [
2095
+ /* @__PURE__ */ u3("span", { class: "profiler-ajax-card__time", children: new Date(req.started_at).toLocaleTimeString("en", { hour12: false }) }),
2096
+ /* @__PURE__ */ u3("a", { href: `/_profiler/profiles/${req.token}`, class: "profiler-text--sm", style: "color: var(--profiler-accent);", children: "View Profile \u2192" })
2097
+ ] })
2098
+ ] }, index))
2099
+ ] });
2100
+ }
2101
+
2102
+ // app/assets/typescript/profiler/components/dashboard/tabs/CacheTab.tsx
2103
+ function CacheTab({ cacheData }) {
2104
+ if (!cacheData) {
2105
+ return /* @__PURE__ */ u3("div", { class: "profiler-empty", children: /* @__PURE__ */ u3("p", { class: "profiler-empty__description", children: "No cache operations recorded" }) });
2106
+ }
2107
+ return /* @__PURE__ */ u3(k, { children: [
2108
+ /* @__PURE__ */ u3("h2", { class: "profiler-section__header", children: "Cache Operations" }),
2109
+ /* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-4 profiler-mb-4 profiler-text--sm", children: [
2110
+ /* @__PURE__ */ u3("span", { children: [
2111
+ "Reads: ",
2112
+ /* @__PURE__ */ u3("strong", { children: cacheData.total_reads })
2113
+ ] }),
2114
+ /* @__PURE__ */ u3("span", { children: [
2115
+ "Writes: ",
2116
+ /* @__PURE__ */ u3("strong", { children: cacheData.total_writes })
2117
+ ] }),
2118
+ /* @__PURE__ */ u3("span", { children: [
2119
+ "Deletes: ",
2120
+ /* @__PURE__ */ u3("strong", { children: cacheData.total_deletes })
2121
+ ] }),
2122
+ /* @__PURE__ */ u3("span", { children: [
2123
+ "Hit Rate:",
2124
+ " ",
2125
+ /* @__PURE__ */ u3("strong", { class: `badge-${cacheData.hit_rate > 80 ? "success" : "warning"}`, children: [
2126
+ cacheData.hit_rate,
2127
+ "%"
2128
+ ] })
2129
+ ] })
2130
+ ] }),
2131
+ cacheData.reads && cacheData.reads.length > 0 && /* @__PURE__ */ u3(k, { children: [
2132
+ /* @__PURE__ */ u3("h3", { class: "profiler-text--lg profiler-mt-6 profiler-mb-3", children: "Cache Reads" }),
2133
+ cacheData.reads.map((read, i3) => /* @__PURE__ */ u3("div", { class: "profiler-query-card", children: /* @__PURE__ */ u3("div", { class: "profiler-query-card__header", children: [
2134
+ /* @__PURE__ */ u3("span", { class: "profiler-text--mono profiler-text--sm", children: read.key }),
2135
+ /* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-2", children: [
2136
+ /* @__PURE__ */ u3("span", { class: read.hit ? "badge-success" : "badge-error", children: read.hit ? "HIT" : "MISS" }),
2137
+ /* @__PURE__ */ u3("span", { class: read.duration >= 500 ? "badge-error" : read.duration >= 100 ? "badge-warning" : "badge-success", children: [
2138
+ read.duration.toFixed(2),
2139
+ " ms"
2140
+ ] })
2141
+ ] })
2142
+ ] }) }, i3))
2143
+ ] })
2144
+ ] });
2145
+ }
2146
+
2147
+ // app/assets/typescript/profiler/components/dashboard/tabs/LogsTab.tsx
2148
+ function levelClass(level) {
2149
+ switch (level) {
2150
+ case "ERROR":
2151
+ case "FATAL":
2152
+ return "profiler-text--error";
2153
+ case "WARN":
2154
+ return "profiler-text--warning";
2155
+ case "INFO":
2156
+ return "profiler-text--success";
2157
+ default:
2158
+ return "profiler-text--muted";
2159
+ }
2160
+ }
2161
+ function levelBadgeStyle(level) {
2162
+ switch (level) {
2163
+ case "ERROR":
2164
+ case "FATAL":
2165
+ return "background: var(--profiler-error, #ef4444); color: #fff;";
2166
+ case "WARN":
2167
+ return "background: var(--profiler-warning, #f59e0b); color: #fff;";
2168
+ case "INFO":
2169
+ return "background: var(--profiler-success, #22c55e); color: #fff;";
2170
+ default:
2171
+ return "background: var(--profiler-muted, #6b7280); color: #fff;";
2172
+ }
2173
+ }
2174
+ function LogsTab({ logData }) {
2175
+ const [filter, setFilter] = d2("ALL");
2176
+ if (!logData?.logs?.length) {
2177
+ return /* @__PURE__ */ u3("div", { class: "profiler-empty", children: [
2178
+ /* @__PURE__ */ u3("div", { class: "profiler-empty__icon", children: "\u{1F4CB}" }),
2179
+ /* @__PURE__ */ u3("h3", { class: "profiler-empty__title", children: "No log messages captured" }),
2180
+ /* @__PURE__ */ u3("div", { class: "profiler-empty__description", children: /* @__PURE__ */ u3("p", { children: [
2181
+ "Log messages emitted via ",
2182
+ /* @__PURE__ */ u3("code", { children: "Rails.logger" }),
2183
+ " during this request will appear here."
2184
+ ] }) })
2185
+ ] });
2186
+ }
2187
+ const levels = ["ALL", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"];
2188
+ const filtered = filter === "ALL" ? logData.logs : logData.logs.filter((l3) => l3.level === filter);
2189
+ return /* @__PURE__ */ u3(k, { children: [
2190
+ /* @__PURE__ */ u3("h2", { class: "profiler-section__header", children: [
2191
+ "Log Messages (",
2192
+ logData.count,
2193
+ ")"
2194
+ ] }),
2195
+ /* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-4 profiler-mb-4 profiler-text--sm", children: [
2196
+ logData.errors > 0 && /* @__PURE__ */ u3("span", { children: [
2197
+ "Errors: ",
2198
+ /* @__PURE__ */ u3("strong", { class: "profiler-text--error", children: logData.errors })
2199
+ ] }),
2200
+ logData.warnings > 0 && /* @__PURE__ */ u3("span", { children: [
2201
+ "Warnings: ",
2202
+ /* @__PURE__ */ u3("strong", { class: "profiler-text--warning", children: logData.warnings })
2203
+ ] })
2204
+ ] }),
2205
+ /* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-2 profiler-mb-4", children: levels.map((level) => /* @__PURE__ */ u3(
2206
+ "button",
2207
+ {
2208
+ onClick: () => setFilter(level),
2209
+ class: `btn btn-sm ${filter === level ? "btn-primary" : "btn-secondary"}`,
2210
+ children: level
2211
+ },
2212
+ level
2213
+ )) }),
2214
+ filtered.length === 0 ? /* @__PURE__ */ u3("div", { class: "profiler-text--muted profiler-text--sm", children: [
2215
+ "No ",
2216
+ filter,
2217
+ " messages."
2218
+ ] }) : filtered.map((entry, index) => /* @__PURE__ */ u3("div", { class: "profiler-query-card profiler-mb-2", children: [
2219
+ /* @__PURE__ */ u3("div", { class: "profiler-query-card__header", children: [
2220
+ /* @__PURE__ */ u3(
2221
+ "span",
2222
+ {
2223
+ class: "profiler-text--xs",
2224
+ style: `display:inline-block;padding:1px 6px;border-radius:3px;font-weight:600;${levelBadgeStyle(entry.level)}`,
2225
+ children: entry.level
2226
+ }
2227
+ ),
2228
+ /* @__PURE__ */ u3("span", { class: "profiler-text--xs profiler-text--muted", children: new Date(entry.timestamp).toLocaleTimeString("en", { hour12: false }) })
2229
+ ] }),
2230
+ /* @__PURE__ */ u3("pre", { class: `profiler-query-card__code ${levelClass(entry.level)}`, style: "white-space:pre-wrap;word-break:break-all;", children: entry.message })
2231
+ ] }, index))
2232
+ ] });
2233
+ }
2234
+
2235
+ // app/assets/typescript/profiler/components/dashboard/tabs/ExceptionTab.tsx
2236
+ function ExceptionTab({ exceptionData }) {
2237
+ const [showAllFrames, setShowAllFrames] = d2(false);
2238
+ if (!exceptionData?.exception_class) {
2239
+ return /* @__PURE__ */ u3("div", { class: "profiler-empty", children: [
2240
+ /* @__PURE__ */ u3("div", { class: "profiler-empty__icon", children: "\u{1F4A5}" }),
2241
+ /* @__PURE__ */ u3("h3", { class: "profiler-empty__title", children: "No exception recorded" }),
2242
+ /* @__PURE__ */ u3("div", { class: "profiler-empty__description", children: /* @__PURE__ */ u3("p", { children: "Exceptions raised during this request will appear here." }) })
2243
+ ] });
2244
+ }
2245
+ const backtrace = exceptionData.backtrace || [];
2246
+ const appFrames = backtrace.filter((f4) => f4.app_frame);
2247
+ const displayFrames = showAllFrames ? backtrace : backtrace.slice(0, 30);
2248
+ return /* @__PURE__ */ u3(k, { children: [
2249
+ /* @__PURE__ */ u3("div", { class: "profiler-mb-4", style: "padding: 12px 16px; background: color-mix(in srgb, var(--profiler-error, #ef4444) 10%, transparent); border-left: 4px solid var(--profiler-error, #ef4444); border-radius: 4px;", children: [
2250
+ /* @__PURE__ */ u3("div", { class: "profiler-text--error", style: "font-size:1.1em;font-weight:700;margin-bottom:4px;", children: exceptionData.exception_class }),
2251
+ /* @__PURE__ */ u3("div", { style: "font-family:monospace;font-size:0.9em;word-break:break-word;", children: exceptionData.message })
2252
+ ] }),
2253
+ appFrames.length > 0 && /* @__PURE__ */ u3("div", { class: "profiler-mb-4", children: [
2254
+ /* @__PURE__ */ u3("h3", { class: "profiler-section__header", children: [
2255
+ "Application frames (",
2256
+ appFrames.length,
2257
+ ")"
2258
+ ] }),
2259
+ appFrames.map((frame, i3) => /* @__PURE__ */ u3("div", { class: "profiler-query-card profiler-mb-1", children: /* @__PURE__ */ u3("code", { class: "profiler-text--sm", children: frame.location }) }, i3))
2260
+ ] }),
2261
+ /* @__PURE__ */ u3("div", { children: [
2262
+ /* @__PURE__ */ u3("h3", { class: "profiler-section__header", children: [
2263
+ "Full backtrace (",
2264
+ backtrace.length,
2265
+ " frames)"
2266
+ ] }),
2267
+ displayFrames.map((frame, i3) => /* @__PURE__ */ u3(
2268
+ "div",
2269
+ {
2270
+ class: "profiler-query-card profiler-mb-1",
2271
+ style: frame.app_frame ? "" : "opacity:0.45;",
2272
+ children: /* @__PURE__ */ u3("code", { class: `profiler-text--sm${frame.app_frame ? " profiler-text--success" : " profiler-text--muted"}`, children: frame.location })
2273
+ },
2274
+ i3
2275
+ )),
2276
+ !showAllFrames && backtrace.length > 30 && /* @__PURE__ */ u3(
2277
+ "button",
2278
+ {
2279
+ class: "btn btn-secondary btn-sm profiler-mt-2",
2280
+ onClick: () => setShowAllFrames(true),
2281
+ children: [
2282
+ "Show all ",
2283
+ backtrace.length,
2284
+ " frames"
2285
+ ]
2286
+ }
2287
+ )
2288
+ ] })
2289
+ ] });
2290
+ }
2291
+
2292
+ // app/assets/typescript/profiler/components/dashboard/ProfileDashboard.tsx
2293
+ function ProfileDashboard({ profile, initialTab, embedded }) {
2294
+ const cd = profile.collectors_data || {};
2295
+ const hasAjax = cd["ajax"]?.total_requests > 0;
2296
+ const hasHttp = cd["http"]?.total_requests > 0;
2297
+ const hasException = !!cd["exception"]?.exception_class;
2298
+ const hasLogs = (cd["logs"]?.count ?? 0) > 0;
2299
+ const [activeTab, setActiveTab] = d2(hasException ? "exception" : initialTab);
2300
+ const handleTabClick = (tab) => (e3) => {
2301
+ e3.preventDefault();
2302
+ setActiveTab(tab);
2303
+ const url = new URL(window.location.href);
2304
+ url.searchParams.set("tab", tab);
2305
+ history.pushState(null, "", url.toString());
2306
+ };
2307
+ const tabClass = (key) => `tab${activeTab === key ? " active" : ""}`;
2308
+ return /* @__PURE__ */ u3("div", { class: "container", children: [
2309
+ /* @__PURE__ */ u3("div", { class: "header", children: [
2310
+ /* @__PURE__ */ u3("h1", { children: /* @__PURE__ */ u3("a", { href: "/_profiler?section=http", children: [
2311
+ /* @__PURE__ */ u3("span", { class: "h1-emoji", children: "\u{1F4CA}" }),
2312
+ " Profile Details"
2313
+ ] }) }),
2314
+ /* @__PURE__ */ u3("p", { children: [
2315
+ profile.method,
2316
+ " ",
2317
+ profile.path
2318
+ ] }),
2319
+ /* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-4 profiler-mt-2", children: [
2320
+ /* @__PURE__ */ u3("span", { children: [
2321
+ "Duration: ",
2322
+ /* @__PURE__ */ u3("strong", { children: [
2323
+ profile.duration.toFixed(2),
2324
+ " ms"
2325
+ ] })
2326
+ ] }),
2327
+ /* @__PURE__ */ u3("span", { children: [
2328
+ "Status: ",
2329
+ /* @__PURE__ */ u3("strong", { children: profile.status })
2330
+ ] }),
2331
+ profile.memory && /* @__PURE__ */ u3("span", { children: [
2332
+ "Memory: ",
2333
+ /* @__PURE__ */ u3("strong", { children: [
2334
+ (profile.memory / 1024 / 1024).toFixed(2),
2335
+ " MB"
2336
+ ] })
2337
+ ] })
2338
+ ] })
2339
+ ] }),
2340
+ /* @__PURE__ */ u3("div", { class: "profiler-panel profiler-mb-6", children: [
2341
+ /* @__PURE__ */ u3("div", { class: "tabs", children: [
2342
+ hasException && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("exception"), onClick: handleTabClick("exception"), style: "color:var(--profiler-error,#ef4444);", children: "\u{1F4A5} Exception" }),
2343
+ /* @__PURE__ */ u3("a", { href: "#", class: tabClass("request"), onClick: handleTabClick("request"), children: "Request" }),
2344
+ /* @__PURE__ */ u3("a", { href: "#", class: tabClass("dump"), onClick: handleTabClick("dump"), children: "Dump" }),
2345
+ /* @__PURE__ */ u3("a", { href: "#", class: tabClass("database"), onClick: handleTabClick("database"), children: "Database" }),
2346
+ hasAjax && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("ajax"), onClick: handleTabClick("ajax"), children: "AJAX" }),
2347
+ hasHttp && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("http"), onClick: handleTabClick("http"), children: "Outbound HTTP" }),
2348
+ /* @__PURE__ */ u3("a", { href: "#", class: tabClass("timeline"), onClick: handleTabClick("timeline"), children: "Timeline" }),
2349
+ /* @__PURE__ */ u3("a", { href: "#", class: tabClass("views"), onClick: handleTabClick("views"), children: "Views" }),
2350
+ /* @__PURE__ */ u3("a", { href: "#", class: tabClass("cache"), onClick: handleTabClick("cache"), children: "Cache" }),
2351
+ hasLogs && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("logs"), onClick: handleTabClick("logs"), children: "Logs" })
2352
+ ] }),
2353
+ /* @__PURE__ */ u3("div", { class: "profiler-p-4 tab-content active", children: [
2354
+ activeTab === "exception" && /* @__PURE__ */ u3(ExceptionTab, { exceptionData: cd["exception"] }),
2355
+ activeTab === "request" && /* @__PURE__ */ u3(RequestTab, { profile }),
2356
+ activeTab === "dump" && /* @__PURE__ */ u3(DumpsTab, { dumpData: cd["dump"] }),
2357
+ activeTab === "database" && /* @__PURE__ */ u3(DatabaseTab, { dbData: cd["database"] }),
2358
+ activeTab === "ajax" && /* @__PURE__ */ u3(AjaxTab, { ajaxData: cd["ajax"] }),
2359
+ activeTab === "http" && /* @__PURE__ */ u3(HttpTab, { httpData: cd["http"] }),
2360
+ activeTab === "timeline" && /* @__PURE__ */ u3(FlameGraphTab, { flamegraphData: cd["flamegraph"], perfData: cd["performance"] }),
2361
+ activeTab === "views" && /* @__PURE__ */ u3(ViewsTab, { viewData: cd["view"] }),
2362
+ activeTab === "cache" && /* @__PURE__ */ u3(CacheTab, { cacheData: cd["cache"] }),
2363
+ activeTab === "logs" && /* @__PURE__ */ u3(LogsTab, { logData: cd["logs"] })
2364
+ ] })
2365
+ ] }),
2366
+ !embedded && /* @__PURE__ */ u3("div", { class: "profiler-mt-6", children: /* @__PURE__ */ u3("a", { href: "/_profiler", style: "color: var(--profiler-accent);", children: "\u2190 Back to profiles" }) })
2367
+ ] });
2368
+ }
2369
+
2370
+ // app/assets/typescript/profiler/components/dashboard/tabs/JobTab.tsx
2371
+ function JobTab({ jobData }) {
2372
+ if (!jobData) {
2373
+ return /* @__PURE__ */ u3("div", { class: "profiler-empty", children: [
2374
+ /* @__PURE__ */ u3("div", { class: "profiler-empty__icon", children: "\u2699\uFE0F" }),
2375
+ /* @__PURE__ */ u3("h3", { class: "profiler-empty__title", children: "No job data" })
2376
+ ] });
2377
+ }
2378
+ const isSuccess = jobData.status === "completed";
2379
+ return /* @__PURE__ */ u3(k, { children: [
2380
+ /* @__PURE__ */ u3("h2", { class: "profiler-section__header", children: "Job Details" }),
2381
+ /* @__PURE__ */ u3("div", { class: "profiler-grid profiler-grid--2 profiler-mb-6", children: [
2382
+ /* @__PURE__ */ u3("div", { class: "profiler-panel profiler-panel--sm", children: [
2383
+ /* @__PURE__ */ u3("h3", { class: "profiler-text--sm profiler-text--muted profiler-text--uppercase profiler-mb-3", children: "Info" }),
2384
+ /* @__PURE__ */ u3("div", { class: "profiler-kv-row", children: [
2385
+ /* @__PURE__ */ u3("span", { class: "profiler-text--sm", children: "Class" }),
2386
+ /* @__PURE__ */ u3("strong", { class: "profiler-text--mono", children: jobData.job_class })
2387
+ ] }),
2388
+ /* @__PURE__ */ u3("div", { class: "profiler-kv-row", children: [
2389
+ /* @__PURE__ */ u3("span", { class: "profiler-text--sm", children: "Job ID" }),
2390
+ /* @__PURE__ */ u3("span", { class: "profiler-text--xs profiler-text--mono profiler-text--muted", children: jobData.job_id })
2391
+ ] }),
2392
+ /* @__PURE__ */ u3("div", { class: "profiler-kv-row", children: [
2393
+ /* @__PURE__ */ u3("span", { class: "profiler-text--sm", children: "Queue" }),
2394
+ /* @__PURE__ */ u3("span", { class: "profiler-text--mono", children: jobData.queue })
2395
+ ] }),
2396
+ /* @__PURE__ */ u3("div", { class: "profiler-kv-row", children: [
2397
+ /* @__PURE__ */ u3("span", { class: "profiler-text--sm", children: "Executions" }),
2398
+ /* @__PURE__ */ u3("span", { children: jobData.executions })
2399
+ ] }),
2400
+ /* @__PURE__ */ u3("div", { class: "profiler-kv-row", children: [
2401
+ /* @__PURE__ */ u3("span", { class: "profiler-text--sm", children: "Status" }),
2402
+ /* @__PURE__ */ u3("span", { class: `badge-${isSuccess ? "success" : "error"}`, children: isSuccess ? "\u2713 Completed" : "\u2717 Failed" })
2403
+ ] })
2404
+ ] }),
2405
+ jobData.arguments && jobData.arguments.length > 0 && /* @__PURE__ */ u3("div", { class: "profiler-panel profiler-panel--sm", children: [
2406
+ /* @__PURE__ */ u3("h3", { class: "profiler-text--sm profiler-text--muted profiler-text--uppercase profiler-mb-3", children: "Arguments" }),
2407
+ jobData.arguments.map((arg, i3) => /* @__PURE__ */ u3("div", { class: "profiler-kv-row", children: [
2408
+ /* @__PURE__ */ u3("span", { class: "profiler-text--xs profiler-text--muted", children: [
2409
+ "[",
2410
+ i3,
2411
+ "]"
2412
+ ] }),
2413
+ /* @__PURE__ */ u3("span", { class: "profiler-text--xs profiler-text--mono", style: "word-break:break-all", children: String(arg) })
2414
+ ] }, i3))
2415
+ ] })
2416
+ ] }),
2417
+ jobData.error && /* @__PURE__ */ u3("div", { class: "profiler-panel profiler-panel--sm profiler-mb-4", style: "border-left: 3px solid var(--profiler-error, #ef4444)", children: [
2418
+ /* @__PURE__ */ u3("h3", { class: "profiler-text--sm profiler-text--muted profiler-text--uppercase profiler-mb-2", children: "Error" }),
2419
+ /* @__PURE__ */ u3("pre", { class: "profiler-code profiler-text--xs", style: "white-space:pre-wrap;word-break:break-all;margin:0", children: jobData.error })
2420
+ ] })
2421
+ ] });
2422
+ }
2423
+
2424
+ // app/assets/typescript/profiler/components/dashboard/JobProfileDashboard.tsx
2425
+ function JobProfileDashboard({ profile, initialTab, embedded }) {
2426
+ const validTabs = ["job", "database", "cache", "http"];
2427
+ const defaultTab = validTabs.includes(initialTab) ? initialTab : "job";
2428
+ const [activeTab, setActiveTab] = d2(defaultTab);
2429
+ const cd = profile.collectors_data || {};
2430
+ const hasHttp = cd["http"]?.total_requests > 0;
2431
+ const jobData = cd["job"];
2432
+ const isFailed = jobData?.status === "failed";
2433
+ const handleTabClick = (tab) => (e3) => {
2434
+ e3.preventDefault();
2435
+ setActiveTab(tab);
2436
+ const url = new URL(window.location.href);
2437
+ url.searchParams.set("tab", tab);
2438
+ history.pushState(null, "", url.toString());
2439
+ };
2440
+ const tabClass = (key) => `tab${activeTab === key ? " active" : ""}`;
2441
+ return /* @__PURE__ */ u3("div", { class: "container", children: [
2442
+ /* @__PURE__ */ u3("div", { class: "header", children: [
2443
+ /* @__PURE__ */ u3("h1", { children: /* @__PURE__ */ u3("a", { href: "/_profiler?section=jobs", children: [
2444
+ /* @__PURE__ */ u3("span", { class: "h1-emoji", children: "\u2699\uFE0F" }),
2445
+ " Job Profile"
2446
+ ] }) }),
2447
+ /* @__PURE__ */ u3("p", { children: profile.path }),
2448
+ /* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-4 profiler-mt-2", children: [
2449
+ /* @__PURE__ */ u3("span", { children: [
2450
+ "Duration: ",
2451
+ /* @__PURE__ */ u3("strong", { children: [
2452
+ profile.duration.toFixed(2),
2453
+ " ms"
2454
+ ] })
2455
+ ] }),
2456
+ /* @__PURE__ */ u3("span", { children: [
2457
+ "Status: ",
2458
+ /* @__PURE__ */ u3("strong", { children: /* @__PURE__ */ u3("span", { class: `badge-${isFailed ? "error" : "success"}`, children: isFailed ? "Failed" : "Completed" }) })
2459
+ ] }),
2460
+ profile.memory != null && /* @__PURE__ */ u3("span", { children: [
2461
+ "Memory: ",
2462
+ /* @__PURE__ */ u3("strong", { children: [
2463
+ (profile.memory / 1024 / 1024).toFixed(2),
2464
+ " MB"
2465
+ ] })
2466
+ ] })
2467
+ ] })
2468
+ ] }),
2469
+ /* @__PURE__ */ u3("div", { class: "profiler-panel profiler-mb-6", children: [
2470
+ /* @__PURE__ */ u3("div", { class: "tabs", children: [
2471
+ /* @__PURE__ */ u3("a", { href: "#", class: tabClass("job"), onClick: handleTabClick("job"), children: "Job" }),
2472
+ /* @__PURE__ */ u3("a", { href: "#", class: tabClass("database"), onClick: handleTabClick("database"), children: "Database" }),
2473
+ /* @__PURE__ */ u3("a", { href: "#", class: tabClass("cache"), onClick: handleTabClick("cache"), children: "Cache" }),
2474
+ hasHttp && /* @__PURE__ */ u3("a", { href: "#", class: tabClass("http"), onClick: handleTabClick("http"), children: "Outbound HTTP" })
2475
+ ] }),
2476
+ /* @__PURE__ */ u3("div", { class: "profiler-p-4 tab-content active", children: [
2477
+ activeTab === "job" && /* @__PURE__ */ u3(JobTab, { jobData: cd["job"] }),
2478
+ activeTab === "database" && /* @__PURE__ */ u3(DatabaseTab, { dbData: cd["database"] }),
2479
+ activeTab === "cache" && /* @__PURE__ */ u3(CacheTab, { cacheData: cd["cache"] }),
2480
+ activeTab === "http" && /* @__PURE__ */ u3(HttpTab, { httpData: cd["http"] })
2481
+ ] })
2482
+ ] }),
2483
+ !embedded && /* @__PURE__ */ u3("div", { class: "profiler-mt-6", children: /* @__PURE__ */ u3("a", { href: "/_profiler", style: "color: var(--profiler-accent);", children: "\u2190 Back to profiles" }) })
2484
+ ] });
2485
+ }
2486
+
2487
+ // app/assets/typescript/profiler/timeline.ts
2488
+ function initTimeline(container) {
2489
+ console.log("Initializing profiler timeline");
2490
+ const token = container.dataset.token;
2491
+ if (!token) {
2492
+ console.error("No profile token found");
2493
+ return;
2494
+ }
2495
+ loadTimelineData(token).then((events) => {
2496
+ renderTimeline(container, events);
2497
+ });
2498
+ }
2499
+ async function loadTimelineData(token) {
2500
+ const response = await fetch(`/_profiler/profiles/${token}/timeline`);
2501
+ const data = await response.json();
2502
+ return data.events || [];
2503
+ }
2504
+ function renderTimeline(container, events) {
2505
+ if (!events.length) {
2506
+ container.innerHTML = "<p>No timeline events recorded</p>";
2507
+ return;
2508
+ }
2509
+ const svg = createTimelineSVG(events);
2510
+ container.appendChild(svg);
2511
+ }
2512
+ function createTimelineSVG(events) {
2513
+ const width = 1200;
2514
+ const height = events.length * 40 + 40;
2515
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
2516
+ svg.setAttribute("width", width.toString());
2517
+ svg.setAttribute("height", height.toString());
2518
+ svg.style.cssText = "background: #1e1e1e; border-radius: 4px;";
2519
+ const minTime = Math.min(...events.map((e3) => e3.started_at));
2520
+ const maxTime = Math.max(...events.map((e3) => e3.finished_at));
2521
+ const timeRange = maxTime - minTime;
2522
+ const scale = (width - 100) / timeRange;
2523
+ events.forEach((event, index) => {
2524
+ const y3 = index * 40 + 20;
2525
+ const x2 = (event.started_at - minTime) * scale + 50;
2526
+ const eventWidth = event.duration * scale;
2527
+ const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
2528
+ rect.setAttribute("x", x2.toString());
2529
+ rect.setAttribute("y", y3.toString());
2530
+ rect.setAttribute("width", Math.max(eventWidth, 2).toString());
2531
+ rect.setAttribute("height", "20");
2532
+ rect.setAttribute("fill", getEventColor(event.name));
2533
+ rect.setAttribute("rx", "2");
2534
+ const title = document.createElementNS("http://www.w3.org/2000/svg", "title");
2535
+ title.textContent = `${event.name}
2536
+ Duration: ${event.duration.toFixed(2)}ms`;
2537
+ rect.appendChild(title);
2538
+ svg.appendChild(rect);
2539
+ const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
2540
+ text.setAttribute("x", "5");
2541
+ text.setAttribute("y", (y3 + 15).toString());
2542
+ text.setAttribute("fill", "#d4d4d4");
2543
+ text.setAttribute("font-size", "11");
2544
+ text.textContent = event.name.substring(0, 40);
2545
+ svg.appendChild(text);
2546
+ });
2547
+ return svg;
2548
+ }
2549
+ function getEventColor(eventName) {
2550
+ if (eventName.includes("Controller")) return "#569cd6";
2551
+ if (eventName.includes("Render")) return "#4ec9b0";
2552
+ if (eventName.includes("Partial")) return "#ce9178";
2553
+ return "#dcdcaa";
2554
+ }
2555
+
2556
+ // app/assets/typescript/profiler/sql-formatter.ts
2557
+ var SQL_KEYWORDS = [
2558
+ "SELECT",
2559
+ "FROM",
2560
+ "WHERE",
2561
+ "INSERT",
2562
+ "UPDATE",
2563
+ "DELETE",
2564
+ "JOIN",
2565
+ "LEFT",
2566
+ "RIGHT",
2567
+ "INNER",
2568
+ "OUTER",
2569
+ "ON",
2570
+ "AS",
2571
+ "AND",
2572
+ "OR",
2573
+ "NOT",
2574
+ "IN",
2575
+ "EXISTS",
2576
+ "LIKE",
2577
+ "ORDER",
2578
+ "BY",
2579
+ "GROUP",
2580
+ "HAVING",
2581
+ "LIMIT",
2582
+ "OFFSET",
2583
+ "DISTINCT",
2584
+ "COUNT",
2585
+ "SUM",
2586
+ "AVG",
2587
+ "MAX",
2588
+ "MIN",
2589
+ "CASE",
2590
+ "WHEN",
2591
+ "THEN",
2592
+ "ELSE",
2593
+ "END"
2594
+ ];
2595
+ function formatSQL(element) {
2596
+ const sql = element.textContent || "";
2597
+ const formatted = highlightSQL(sql);
2598
+ element.innerHTML = formatted;
2599
+ }
2600
+ function highlightSQL(sql) {
2601
+ let highlighted = sql;
2602
+ SQL_KEYWORDS.forEach((keyword) => {
2603
+ const regex = new RegExp(`\\b${keyword}\\b`, "gi");
2604
+ highlighted = highlighted.replace(regex, `<span class="sql-keyword">${keyword}</span>`);
2605
+ });
2606
+ highlighted = highlighted.replace(/'([^']*)'/g, `<span class="sql-string">'$1'</span>`);
2607
+ highlighted = highlighted.replace(/\b(\d+)\b/g, '<span class="sql-number">$1</span>');
2608
+ highlighted = highlighted.replace(/--([^\n]*)/g, '<span class="sql-comment">--$1</span>');
2609
+ return highlighted;
2610
+ }
2611
+
2612
+ // app/assets/typescript/profiler/theme.ts
2613
+ var STORAGE_KEY = "profiler-theme";
2614
+ var THEME_ATTRIBUTE = "data-theme";
2615
+ var ThemeManager = class {
2616
+ constructor() {
2617
+ this.systemPreference = window.matchMedia("(prefers-color-scheme: dark)");
2618
+ this.currentTheme = this.loadTheme();
2619
+ this.init();
2620
+ }
2621
+ init() {
2622
+ this.applyTheme(this.currentTheme);
2623
+ this.systemPreference.addEventListener("change", (e3) => {
2624
+ if (this.currentTheme === "auto") {
2625
+ this.applyTheme("auto");
2626
+ }
2627
+ });
2628
+ window.addEventListener("storage", (e3) => {
2629
+ if (e3.key === STORAGE_KEY && e3.newValue) {
2630
+ this.currentTheme = e3.newValue;
2631
+ this.applyTheme(this.currentTheme);
2632
+ }
2633
+ });
2634
+ }
2635
+ loadTheme() {
2636
+ const stored = localStorage.getItem(STORAGE_KEY);
2637
+ return stored || "auto";
2638
+ }
2639
+ saveTheme(theme) {
2640
+ localStorage.setItem(STORAGE_KEY, theme);
2641
+ }
2642
+ applyTheme(theme) {
2643
+ const resolvedTheme = this.resolveTheme(theme);
2644
+ document.documentElement.setAttribute(THEME_ATTRIBUTE, resolvedTheme);
2645
+ window.dispatchEvent(new CustomEvent("profiler:theme-change", {
2646
+ detail: { theme: resolvedTheme }
2647
+ }));
2648
+ }
2649
+ resolveTheme(theme) {
2650
+ if (theme === "auto") {
2651
+ return this.systemPreference.matches ? "dark" : "light";
2652
+ }
2653
+ return theme;
2654
+ }
2655
+ getTheme() {
2656
+ return this.currentTheme;
2657
+ }
2658
+ getResolvedTheme() {
2659
+ return this.resolveTheme(this.currentTheme);
2660
+ }
2661
+ setTheme(theme) {
2662
+ this.currentTheme = theme;
2663
+ this.saveTheme(theme);
2664
+ this.applyTheme(theme);
2665
+ }
2666
+ toggle() {
2667
+ const resolved = this.getResolvedTheme();
2668
+ const newTheme = resolved === "dark" ? "light" : "dark";
2669
+ this.setTheme(newTheme);
2670
+ }
2671
+ };
2672
+ var themeManager = new ThemeManager();
2673
+ function toggleTheme() {
2674
+ themeManager.toggle();
2675
+ }
2676
+ function getResolvedTheme() {
2677
+ return themeManager.getResolvedTheme();
2678
+ }
2679
+ function createSunIcon() {
2680
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
2681
+ svg.setAttribute("viewBox", "0 0 24 24");
2682
+ svg.setAttribute("fill", "none");
2683
+ const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
2684
+ path.setAttribute("d", "M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z");
2685
+ path.setAttribute("stroke", "currentColor");
2686
+ path.setAttribute("stroke-width", "2");
2687
+ path.setAttribute("stroke-linecap", "round");
2688
+ path.setAttribute("stroke-linejoin", "round");
2689
+ svg.appendChild(path);
2690
+ return svg;
2691
+ }
2692
+ function createMoonIcon() {
2693
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
2694
+ svg.setAttribute("viewBox", "0 0 24 24");
2695
+ svg.setAttribute("fill", "none");
2696
+ const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
2697
+ path.setAttribute("d", "M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z");
2698
+ path.setAttribute("stroke", "currentColor");
2699
+ path.setAttribute("stroke-width", "2");
2700
+ path.setAttribute("stroke-linecap", "round");
2701
+ path.setAttribute("stroke-linejoin", "round");
2702
+ svg.appendChild(path);
2703
+ return svg;
2704
+ }
2705
+ function createThemeToggle() {
2706
+ const button = document.createElement("button");
2707
+ button.className = "profiler-theme-toggle";
2708
+ button.setAttribute("aria-label", "Toggle theme");
2709
+ button.title = "Toggle light/dark mode";
2710
+ const updateIcon = () => {
2711
+ const theme = getResolvedTheme();
2712
+ while (button.firstChild) {
2713
+ button.removeChild(button.firstChild);
2714
+ }
2715
+ const icon = theme === "dark" ? createSunIcon() : createMoonIcon();
2716
+ button.appendChild(icon);
2717
+ };
2718
+ updateIcon();
2719
+ window.addEventListener("profiler:theme-change", updateIcon);
2720
+ button.addEventListener("click", () => {
2721
+ toggleTheme();
2722
+ });
2723
+ return button;
2724
+ }
2725
+ if (document.readyState === "loading") {
2726
+ document.addEventListener("DOMContentLoaded", () => {
2727
+ console.log("\u{1F3A8} Theme system initialized:", getResolvedTheme());
2728
+ });
2729
+ } else {
2730
+ console.log("\u{1F3A8} Theme system initialized:", getResolvedTheme());
2731
+ }
2732
+
2733
+ // app/assets/typescript/profiler/main.tsx
2734
+ document.addEventListener("DOMContentLoaded", () => {
2735
+ const indexEl = document.getElementById("profiler-index");
2736
+ if (indexEl) {
2737
+ J(/* @__PURE__ */ u3(ProfileList, {}), indexEl);
2738
+ }
2739
+ const showEl = document.getElementById("profiler-show");
2740
+ if (showEl) {
2741
+ const dataEl = document.getElementById("profiler-show-data");
2742
+ if (dataEl) {
2743
+ const profile = JSON.parse(dataEl.textContent);
2744
+ const tab = new URLSearchParams(location.search).get("tab") || "request";
2745
+ const embedded = showEl.dataset.embedded === "true";
2746
+ if (profile.profile_type === "job") {
2747
+ J(/* @__PURE__ */ u3(JobProfileDashboard, { profile, initialTab: tab, embedded }), showEl);
2748
+ } else {
2749
+ J(/* @__PURE__ */ u3(ProfileDashboard, { profile, initialTab: tab, embedded }), showEl);
2750
+ }
2751
+ }
2752
+ }
2753
+ if (!indexEl && !showEl) {
2754
+ const header = document.querySelector(".header, .profiler-detail");
2755
+ if (header) {
2756
+ const toggle = createThemeToggle();
2757
+ toggle.style.position = "absolute";
2758
+ toggle.style.top = "20px";
2759
+ toggle.style.right = "20px";
2760
+ header.style.position = "relative";
2761
+ header.appendChild(toggle);
2762
+ }
2763
+ }
2764
+ const timeline = document.getElementById("profiler-timeline");
2765
+ if (timeline) {
2766
+ initTimeline(timeline);
2767
+ }
2768
+ document.querySelectorAll('pre[data-language="sql"]').forEach((block) => {
2769
+ formatSQL(block);
2770
+ });
2771
+ });
2772
+ })();