@adonis-agora/telescope 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +26 -0
  3. package/dist/configure.d.ts +16 -0
  4. package/dist/configure.d.ts.map +1 -0
  5. package/dist/configure.js +75 -0
  6. package/dist/configure.js.map +1 -0
  7. package/dist/providers/telescope_ai_provider.d.ts +20 -0
  8. package/dist/providers/telescope_ai_provider.d.ts.map +1 -0
  9. package/dist/providers/telescope_ai_provider.js +45 -0
  10. package/dist/providers/telescope_ai_provider.js.map +1 -0
  11. package/dist/providers/telescope_alerts_provider.d.ts +23 -0
  12. package/dist/providers/telescope_alerts_provider.d.ts.map +1 -0
  13. package/dist/providers/telescope_alerts_provider.js +72 -0
  14. package/dist/providers/telescope_alerts_provider.js.map +1 -0
  15. package/dist/providers/telescope_provider.d.ts +43 -0
  16. package/dist/providers/telescope_provider.d.ts.map +1 -0
  17. package/dist/providers/telescope_provider.js +103 -0
  18. package/dist/providers/telescope_provider.js.map +1 -0
  19. package/dist/providers/telescope_ui_provider.d.ts +21 -0
  20. package/dist/providers/telescope_ui_provider.d.ts.map +1 -0
  21. package/dist/providers/telescope_ui_provider.js +119 -0
  22. package/dist/providers/telescope_ui_provider.js.map +1 -0
  23. package/dist/providers/telescope_watchers_provider.d.ts +31 -0
  24. package/dist/providers/telescope_watchers_provider.d.ts.map +1 -0
  25. package/dist/providers/telescope_watchers_provider.js +116 -0
  26. package/dist/providers/telescope_watchers_provider.js.map +1 -0
  27. package/dist/src/ai/define_config.d.ts +56 -0
  28. package/dist/src/ai/define_config.d.ts.map +1 -0
  29. package/dist/src/ai/define_config.js +39 -0
  30. package/dist/src/ai/define_config.js.map +1 -0
  31. package/dist/src/ai/diagnoser.d.ts +34 -0
  32. package/dist/src/ai/diagnoser.d.ts.map +1 -0
  33. package/dist/src/ai/diagnoser.js +74 -0
  34. package/dist/src/ai/diagnoser.js.map +1 -0
  35. package/dist/src/ai/diagnosis_cache.d.ts +43 -0
  36. package/dist/src/ai/diagnosis_cache.d.ts.map +1 -0
  37. package/dist/src/ai/diagnosis_cache.js +56 -0
  38. package/dist/src/ai/diagnosis_cache.js.map +1 -0
  39. package/dist/src/ai/factory.d.ts +15 -0
  40. package/dist/src/ai/factory.d.ts.map +1 -0
  41. package/dist/src/ai/factory.js +24 -0
  42. package/dist/src/ai/factory.js.map +1 -0
  43. package/dist/src/ai/index.d.ts +14 -0
  44. package/dist/src/ai/index.d.ts.map +1 -0
  45. package/dist/src/ai/index.js +15 -0
  46. package/dist/src/ai/index.js.map +1 -0
  47. package/dist/src/ai/prompt.d.ts +31 -0
  48. package/dist/src/ai/prompt.d.ts.map +1 -0
  49. package/dist/src/ai/prompt.js +66 -0
  50. package/dist/src/ai/prompt.js.map +1 -0
  51. package/dist/src/ai/telescope_ai_diagnoser.d.ts +79 -0
  52. package/dist/src/ai/telescope_ai_diagnoser.d.ts.map +1 -0
  53. package/dist/src/ai/telescope_ai_diagnoser.js +111 -0
  54. package/dist/src/ai/telescope_ai_diagnoser.js.map +1 -0
  55. package/dist/src/alerts/alert_channel.d.ts +69 -0
  56. package/dist/src/alerts/alert_channel.d.ts.map +1 -0
  57. package/dist/src/alerts/alert_channel.js +114 -0
  58. package/dist/src/alerts/alert_channel.js.map +1 -0
  59. package/dist/src/alerts/alert_rule.d.ts +86 -0
  60. package/dist/src/alerts/alert_rule.d.ts.map +1 -0
  61. package/dist/src/alerts/alert_rule.js +2 -0
  62. package/dist/src/alerts/alert_rule.js.map +1 -0
  63. package/dist/src/alerts/alerter.d.ts +72 -0
  64. package/dist/src/alerts/alerter.d.ts.map +1 -0
  65. package/dist/src/alerts/alerter.js +248 -0
  66. package/dist/src/alerts/alerter.js.map +1 -0
  67. package/dist/src/alerts/define_config.d.ts +68 -0
  68. package/dist/src/alerts/define_config.d.ts.map +1 -0
  69. package/dist/src/alerts/define_config.js +57 -0
  70. package/dist/src/alerts/define_config.js.map +1 -0
  71. package/dist/src/alerts/exception_source.d.ts +44 -0
  72. package/dist/src/alerts/exception_source.d.ts.map +1 -0
  73. package/dist/src/alerts/exception_source.js +79 -0
  74. package/dist/src/alerts/exception_source.js.map +1 -0
  75. package/dist/src/alerts/index.d.ts +16 -0
  76. package/dist/src/alerts/index.d.ts.map +1 -0
  77. package/dist/src/alerts/index.js +17 -0
  78. package/dist/src/alerts/index.js.map +1 -0
  79. package/dist/src/alerts/new_exception_tracker.d.ts +50 -0
  80. package/dist/src/alerts/new_exception_tracker.d.ts.map +1 -0
  81. package/dist/src/alerts/new_exception_tracker.js +74 -0
  82. package/dist/src/alerts/new_exception_tracker.js.map +1 -0
  83. package/dist/src/alerts/parse_duration.d.ts +10 -0
  84. package/dist/src/alerts/parse_duration.d.ts.map +1 -0
  85. package/dist/src/alerts/parse_duration.js +27 -0
  86. package/dist/src/alerts/parse_duration.js.map +1 -0
  87. package/dist/src/alerts/slack_format.d.ts +60 -0
  88. package/dist/src/alerts/slack_format.d.ts.map +1 -0
  89. package/dist/src/alerts/slack_format.js +122 -0
  90. package/dist/src/alerts/slack_format.js.map +1 -0
  91. package/dist/src/context_accessor.d.ts +30 -0
  92. package/dist/src/context_accessor.d.ts.map +1 -0
  93. package/dist/src/context_accessor.js +20 -0
  94. package/dist/src/context_accessor.js.map +1 -0
  95. package/dist/src/define_config.d.ts +109 -0
  96. package/dist/src/define_config.d.ts.map +1 -0
  97. package/dist/src/define_config.js +38 -0
  98. package/dist/src/define_config.js.map +1 -0
  99. package/dist/src/diagnostics_registry.d.ts +46 -0
  100. package/dist/src/diagnostics_registry.d.ts.map +1 -0
  101. package/dist/src/diagnostics_registry.js +34 -0
  102. package/dist/src/diagnostics_registry.js.map +1 -0
  103. package/dist/src/diagnostics_watcher.d.ts +72 -0
  104. package/dist/src/diagnostics_watcher.d.ts.map +1 -0
  105. package/dist/src/diagnostics_watcher.js +119 -0
  106. package/dist/src/diagnostics_watcher.js.map +1 -0
  107. package/dist/src/entry.d.ts +81 -0
  108. package/dist/src/entry.d.ts.map +1 -0
  109. package/dist/src/entry.js +34 -0
  110. package/dist/src/entry.js.map +1 -0
  111. package/dist/src/exception_family_hash.d.ts +29 -0
  112. package/dist/src/exception_family_hash.d.ts.map +1 -0
  113. package/dist/src/exception_family_hash.js +30 -0
  114. package/dist/src/exception_family_hash.js.map +1 -0
  115. package/dist/src/exception_watcher.d.ts +66 -0
  116. package/dist/src/exception_watcher.d.ts.map +1 -0
  117. package/dist/src/exception_watcher.js +94 -0
  118. package/dist/src/exception_watcher.js.map +1 -0
  119. package/dist/src/extension/registry.d.ts +17 -0
  120. package/dist/src/extension/registry.d.ts.map +1 -0
  121. package/dist/src/extension/registry.js +56 -0
  122. package/dist/src/extension/registry.js.map +1 -0
  123. package/dist/src/extension/types.d.ts +158 -0
  124. package/dist/src/extension/types.d.ts.map +1 -0
  125. package/dist/src/extension/types.js +5 -0
  126. package/dist/src/extension/types.js.map +1 -0
  127. package/dist/src/index.d.ts +36 -0
  128. package/dist/src/index.d.ts.map +1 -0
  129. package/dist/src/index.js +28 -0
  130. package/dist/src/index.js.map +1 -0
  131. package/dist/src/redaction/redact.d.ts +93 -0
  132. package/dist/src/redaction/redact.d.ts.map +1 -0
  133. package/dist/src/redaction/redact.js +184 -0
  134. package/dist/src/redaction/redact.js.map +1 -0
  135. package/dist/src/redaction/redacting_store.d.ts +28 -0
  136. package/dist/src/redaction/redacting_store.d.ts.map +1 -0
  137. package/dist/src/redaction/redacting_store.js +49 -0
  138. package/dist/src/redaction/redacting_store.js.map +1 -0
  139. package/dist/src/registry.d.ts +26 -0
  140. package/dist/src/registry.d.ts.map +1 -0
  141. package/dist/src/registry.js +28 -0
  142. package/dist/src/registry.js.map +1 -0
  143. package/dist/src/request_watcher.d.ts +44 -0
  144. package/dist/src/request_watcher.d.ts.map +1 -0
  145. package/dist/src/request_watcher.js +37 -0
  146. package/dist/src/request_watcher.js.map +1 -0
  147. package/dist/src/service.d.ts +36 -0
  148. package/dist/src/service.d.ts.map +1 -0
  149. package/dist/src/service.js +65 -0
  150. package/dist/src/service.js.map +1 -0
  151. package/dist/src/store.d.ts +56 -0
  152. package/dist/src/store.d.ts.map +1 -0
  153. package/dist/src/store.js +2 -0
  154. package/dist/src/store.js.map +1 -0
  155. package/dist/src/stores/factory.d.ts +61 -0
  156. package/dist/src/stores/factory.d.ts.map +1 -0
  157. package/dist/src/stores/factory.js +42 -0
  158. package/dist/src/stores/factory.js.map +1 -0
  159. package/dist/src/stores/lucid.d.ts +138 -0
  160. package/dist/src/stores/lucid.d.ts.map +1 -0
  161. package/dist/src/stores/lucid.js +257 -0
  162. package/dist/src/stores/lucid.js.map +1 -0
  163. package/dist/src/stores/memory.d.ts +31 -0
  164. package/dist/src/stores/memory.d.ts.map +1 -0
  165. package/dist/src/stores/memory.js +117 -0
  166. package/dist/src/stores/memory.js.map +1 -0
  167. package/dist/src/telescope_middleware.d.ts +19 -0
  168. package/dist/src/telescope_middleware.d.ts.map +1 -0
  169. package/dist/src/telescope_middleware.js +56 -0
  170. package/dist/src/telescope_middleware.js.map +1 -0
  171. package/dist/src/ui/api.d.ts +49 -0
  172. package/dist/src/ui/api.d.ts.map +1 -0
  173. package/dist/src/ui/api.js +155 -0
  174. package/dist/src/ui/api.js.map +1 -0
  175. package/dist/src/ui/dashboard.d.ts +8 -0
  176. package/dist/src/ui/dashboard.d.ts.map +1 -0
  177. package/dist/src/ui/dashboard.html +626 -0
  178. package/dist/src/ui/dashboard.js +29 -0
  179. package/dist/src/ui/dashboard.js.map +1 -0
  180. package/dist/src/ui/define_config.d.ts +87 -0
  181. package/dist/src/ui/define_config.d.ts.map +1 -0
  182. package/dist/src/ui/define_config.js +104 -0
  183. package/dist/src/ui/define_config.js.map +1 -0
  184. package/dist/src/ui/extension_api.d.ts +23 -0
  185. package/dist/src/ui/extension_api.d.ts.map +1 -0
  186. package/dist/src/ui/extension_api.js +50 -0
  187. package/dist/src/ui/extension_api.js.map +1 -0
  188. package/dist/src/ui/guard.d.ts +33 -0
  189. package/dist/src/ui/guard.d.ts.map +1 -0
  190. package/dist/src/ui/guard.js +47 -0
  191. package/dist/src/ui/guard.js.map +1 -0
  192. package/dist/src/ui/http.d.ts +47 -0
  193. package/dist/src/ui/http.d.ts.map +1 -0
  194. package/dist/src/ui/http.js +43 -0
  195. package/dist/src/ui/http.js.map +1 -0
  196. package/dist/src/ui/index.d.ts +12 -0
  197. package/dist/src/ui/index.d.ts.map +1 -0
  198. package/dist/src/ui/index.js +13 -0
  199. package/dist/src/ui/index.js.map +1 -0
  200. package/dist/src/watchers/cache_watcher.d.ts +60 -0
  201. package/dist/src/watchers/cache_watcher.d.ts.map +1 -0
  202. package/dist/src/watchers/cache_watcher.js +72 -0
  203. package/dist/src/watchers/cache_watcher.js.map +1 -0
  204. package/dist/src/watchers/define_config.d.ts +38 -0
  205. package/dist/src/watchers/define_config.d.ts.map +1 -0
  206. package/dist/src/watchers/define_config.js +17 -0
  207. package/dist/src/watchers/define_config.js.map +1 -0
  208. package/dist/src/watchers/emitter.d.ts +32 -0
  209. package/dist/src/watchers/emitter.d.ts.map +1 -0
  210. package/dist/src/watchers/emitter.js +2 -0
  211. package/dist/src/watchers/emitter.js.map +1 -0
  212. package/dist/src/watchers/http_client_watcher.d.ts +74 -0
  213. package/dist/src/watchers/http_client_watcher.d.ts.map +1 -0
  214. package/dist/src/watchers/http_client_watcher.js +168 -0
  215. package/dist/src/watchers/http_client_watcher.js.map +1 -0
  216. package/dist/src/watchers/index.d.ts +19 -0
  217. package/dist/src/watchers/index.d.ts.map +1 -0
  218. package/dist/src/watchers/index.js +19 -0
  219. package/dist/src/watchers/index.js.map +1 -0
  220. package/dist/src/watchers/logs_watcher.d.ts +82 -0
  221. package/dist/src/watchers/logs_watcher.d.ts.map +1 -0
  222. package/dist/src/watchers/logs_watcher.js +145 -0
  223. package/dist/src/watchers/logs_watcher.js.map +1 -0
  224. package/dist/src/watchers/lucid_query_watcher.d.ts +64 -0
  225. package/dist/src/watchers/lucid_query_watcher.d.ts.map +1 -0
  226. package/dist/src/watchers/lucid_query_watcher.js +84 -0
  227. package/dist/src/watchers/lucid_query_watcher.js.map +1 -0
  228. package/dist/src/watchers/mail_watcher.d.ts +51 -0
  229. package/dist/src/watchers/mail_watcher.d.ts.map +1 -0
  230. package/dist/src/watchers/mail_watcher.js +93 -0
  231. package/dist/src/watchers/mail_watcher.js.map +1 -0
  232. package/dist/src/watchers/normalize_http_target.d.ts +17 -0
  233. package/dist/src/watchers/normalize_http_target.d.ts.map +1 -0
  234. package/dist/src/watchers/normalize_http_target.js +41 -0
  235. package/dist/src/watchers/normalize_http_target.js.map +1 -0
  236. package/dist/src/watchers/query_family_hash.d.ts +8 -0
  237. package/dist/src/watchers/query_family_hash.d.ts.map +1 -0
  238. package/dist/src/watchers/query_family_hash.js +31 -0
  239. package/dist/src/watchers/query_family_hash.js.map +1 -0
  240. package/dist/src/watchers/record.d.ts +22 -0
  241. package/dist/src/watchers/record.d.ts.map +1 -0
  242. package/dist/src/watchers/record.js +48 -0
  243. package/dist/src/watchers/record.js.map +1 -0
  244. package/dist/stubs/config/telescope.stub +56 -0
  245. package/dist/stubs/config/telescope_ai.stub +36 -0
  246. package/dist/stubs/config/telescope_alerts.stub +47 -0
  247. package/dist/stubs/config/telescope_ui.stub +40 -0
  248. package/dist/stubs/config/telescope_watchers.stub +30 -0
  249. package/dist/stubs/database/migrations/create_telescope_entries_table.stub +39 -0
  250. package/dist/stubs/main.d.ts +6 -0
  251. package/dist/stubs/main.d.ts.map +1 -0
  252. package/dist/stubs/main.js +7 -0
  253. package/dist/stubs/main.js.map +1 -0
  254. package/package.json +140 -0
@@ -0,0 +1,626 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Telescope</title>
7
+ <style>
8
+ :root {
9
+ --bg: #0d1117;
10
+ --panel: #161b22;
11
+ --panel-2: #1c232c;
12
+ --border: #2b333d;
13
+ --text: #e6edf3;
14
+ --muted: #8b949e;
15
+ --accent: #58a6ff;
16
+ --accent-dim: #1f6feb;
17
+ --green: #3fb950;
18
+ --yellow: #d29922;
19
+ --red: #f85149;
20
+ --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
21
+ }
22
+ * { box-sizing: border-box; }
23
+ html, body { height: 100%; margin: 0; }
24
+ body {
25
+ background: var(--bg);
26
+ color: var(--text);
27
+ font: 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
28
+ }
29
+ header {
30
+ display: flex;
31
+ align-items: center;
32
+ gap: 16px;
33
+ padding: 12px 20px;
34
+ border-bottom: 1px solid var(--border);
35
+ background: var(--panel);
36
+ position: sticky;
37
+ top: 0;
38
+ z-index: 5;
39
+ }
40
+ header h1 {
41
+ font-size: 16px;
42
+ margin: 0;
43
+ font-weight: 600;
44
+ letter-spacing: 0.2px;
45
+ }
46
+ header h1 .dot { color: var(--accent); }
47
+ .controls { display: flex; align-items: center; gap: 10px; margin-left: auto; flex-wrap: wrap; }
48
+ select, input[type="text"] {
49
+ background: var(--panel-2);
50
+ color: var(--text);
51
+ border: 1px solid var(--border);
52
+ border-radius: 6px;
53
+ padding: 6px 10px;
54
+ font-size: 13px;
55
+ outline: none;
56
+ }
57
+ input[type="text"] { min-width: 200px; }
58
+ select:focus, input[type="text"]:focus { border-color: var(--accent-dim); }
59
+ label.toggle { display: inline-flex; align-items: center; gap: 6px; color: var(--muted); font-size: 13px; user-select: none; cursor: pointer; }
60
+ button {
61
+ background: var(--panel-2);
62
+ color: var(--text);
63
+ border: 1px solid var(--border);
64
+ border-radius: 6px;
65
+ padding: 6px 12px;
66
+ font-size: 13px;
67
+ cursor: pointer;
68
+ }
69
+ button:hover { border-color: var(--accent-dim); }
70
+ .layout { display: grid; grid-template-columns: 1fr; height: calc(100% - 53px); }
71
+ .layout.detail-open { grid-template-columns: 1fr 1fr; }
72
+ main { overflow: auto; }
73
+ aside {
74
+ overflow: auto;
75
+ border-left: 1px solid var(--border);
76
+ background: var(--panel);
77
+ display: none;
78
+ }
79
+ .layout.detail-open aside { display: block; }
80
+ .stats {
81
+ display: flex;
82
+ gap: 18px;
83
+ padding: 10px 20px;
84
+ border-bottom: 1px solid var(--border);
85
+ color: var(--muted);
86
+ font-size: 12px;
87
+ flex-wrap: wrap;
88
+ }
89
+ .stats b { color: var(--text); font-variant-numeric: tabular-nums; }
90
+ table { width: 100%; border-collapse: collapse; }
91
+ th, td { text-align: left; padding: 8px 12px; border-bottom: 1px solid var(--border); vertical-align: top; }
92
+ th { color: var(--muted); font-weight: 500; font-size: 12px; text-transform: uppercase; letter-spacing: 0.4px; position: sticky; top: 0; background: var(--bg); }
93
+ tbody tr { cursor: pointer; }
94
+ tbody tr:hover { background: var(--panel-2); }
95
+ tbody tr.selected { background: rgba(31, 111, 235, 0.18); }
96
+ .badge {
97
+ display: inline-block;
98
+ padding: 1px 7px;
99
+ border-radius: 999px;
100
+ font-size: 11px;
101
+ font-family: var(--mono);
102
+ border: 1px solid var(--border);
103
+ background: var(--panel-2);
104
+ color: var(--muted);
105
+ }
106
+ .badge.request { color: var(--accent); border-color: var(--accent-dim); }
107
+ .badge.diagnostic { color: var(--green); border-color: var(--green); }
108
+ .badge.exception { color: var(--red); border-color: var(--red); }
109
+ .summary { font-family: var(--mono); font-size: 13px; }
110
+ .time, .trace { color: var(--muted); font-size: 12px; font-family: var(--mono); white-space: nowrap; }
111
+ .trace { color: var(--accent); }
112
+ .detail-head { display: flex; align-items: center; gap: 10px; padding: 12px 16px; border-bottom: 1px solid var(--border); position: sticky; top: 0; background: var(--panel); }
113
+ .detail-head h2 { font-size: 14px; margin: 0; }
114
+ .detail-head .close { margin-left: auto; }
115
+ pre {
116
+ margin: 0;
117
+ padding: 16px;
118
+ font-family: var(--mono);
119
+ font-size: 12.5px;
120
+ white-space: pre-wrap;
121
+ word-break: break-word;
122
+ color: var(--text);
123
+ }
124
+ .kv { padding: 8px 16px; border-bottom: 1px solid var(--border); display: grid; grid-template-columns: 110px 1fr; gap: 6px 12px; font-size: 12.5px; }
125
+ .kv dt { color: var(--muted); }
126
+ .kv dd { margin: 0; font-family: var(--mono); word-break: break-word; }
127
+ .empty, .error { padding: 40px 20px; text-align: center; color: var(--muted); }
128
+ .error { color: var(--red); }
129
+ .tagline .badge { margin-right: 4px; margin-bottom: 4px; }
130
+
131
+ /* — extension dashboards — */
132
+ nav.tabs { display: flex; gap: 4px; }
133
+ nav.tabs button {
134
+ background: transparent;
135
+ border: 1px solid transparent;
136
+ border-bottom: 2px solid transparent;
137
+ border-radius: 6px 6px 0 0;
138
+ color: var(--muted);
139
+ padding: 6px 12px;
140
+ }
141
+ nav.tabs button:hover { color: var(--text); }
142
+ nav.tabs button.active { color: var(--text); border-bottom-color: var(--accent); }
143
+ .dash { padding: 16px 20px; overflow: auto; height: calc(100% - 53px); }
144
+ .dash-section { margin-bottom: 24px; }
145
+ .dash-section h3 {
146
+ margin: 0 0 10px;
147
+ font-size: 12px;
148
+ text-transform: uppercase;
149
+ letter-spacing: 0.5px;
150
+ color: var(--muted);
151
+ font-weight: 600;
152
+ }
153
+ .grid { display: grid; gap: 12px; }
154
+ .grid.cols-2 { grid-template-columns: repeat(2, 1fr); }
155
+ .grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
156
+ .grid.cols-4 { grid-template-columns: repeat(4, 1fr); }
157
+ @media (max-width: 900px) { .grid { grid-template-columns: 1fr !important; } }
158
+ .card {
159
+ background: var(--panel);
160
+ border: 1px solid var(--border);
161
+ border-radius: 8px;
162
+ padding: 14px;
163
+ min-height: 96px;
164
+ display: flex;
165
+ flex-direction: column;
166
+ }
167
+ .card .card-title { color: var(--muted); font-size: 12px; margin-bottom: 8px; }
168
+ .card .big {
169
+ font-size: 30px;
170
+ font-weight: 600;
171
+ font-variant-numeric: tabular-nums;
172
+ line-height: 1.1;
173
+ }
174
+ .card .big.ok { color: var(--green); }
175
+ .card .big.warn { color: var(--yellow); }
176
+ .card .big.bad { color: var(--red); }
177
+ .card .delta { font-size: 12px; color: var(--muted); margin-top: 4px; }
178
+ .card .delta.up { color: var(--green); }
179
+ .card .delta.down { color: var(--red); }
180
+ .card table { font-size: 12.5px; }
181
+ .card th, .card td { padding: 5px 8px; }
182
+ .card td a { color: var(--accent); text-decoration: none; }
183
+ .card td a:hover { text-decoration: underline; }
184
+ .barlist { display: flex; flex-direction: column; gap: 6px; }
185
+ .barlist .row { display: grid; grid-template-columns: 1fr auto; gap: 8px; align-items: center; font-size: 12.5px; }
186
+ .barlist .track { grid-column: 1 / -1; height: 5px; border-radius: 3px; background: var(--panel-2); overflow: hidden; }
187
+ .barlist .track > span { display: block; height: 100%; background: var(--accent); }
188
+ .legend { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 8px; font-size: 12px; color: var(--muted); }
189
+ .legend .swatch { display: inline-block; width: 9px; height: 9px; border-radius: 2px; margin-right: 5px; vertical-align: middle; }
190
+ .markers { display: flex; gap: 14px; margin-top: 8px; font-size: 12px; color: var(--muted); }
191
+ .markers b { color: var(--text); font-variant-numeric: tabular-nums; }
192
+ .card svg { display: block; width: 100%; }
193
+ .muted-pill { color: var(--muted); font-size: 12px; }
194
+ </style>
195
+ </head>
196
+ <body>
197
+ <header>
198
+ <h1><span class="dot">â—Ź</span> Telescope</h1>
199
+ <nav class="tabs" id="tabs"></nav>
200
+ <div class="controls" id="entries-controls">
201
+ <select id="type-filter" title="Filter by entry type">
202
+ <option value="">all types</option>
203
+ <option value="request">request</option>
204
+ <option value="diagnostic">diagnostic</option>
205
+ <option value="query">query</option>
206
+ <option value="job">job</option>
207
+ <option value="exception">exception</option>
208
+ <option value="log">log</option>
209
+ </select>
210
+ <input type="text" id="search" placeholder="search content / tags…" />
211
+ <label class="toggle"><input type="checkbox" id="autorefresh" /> auto-refresh</label>
212
+ <button id="refresh">Refresh</button>
213
+ </div>
214
+ </header>
215
+
216
+ <div class="stats" id="stats"><span>Loading…</span></div>
217
+
218
+ <div class="layout" id="layout">
219
+ <main>
220
+ <table>
221
+ <thead>
222
+ <tr>
223
+ <th style="width: 110px">Type</th>
224
+ <th>Summary</th>
225
+ <th style="width: 150px">Trace</th>
226
+ <th style="width: 170px">Time</th>
227
+ </tr>
228
+ </thead>
229
+ <tbody id="rows"></tbody>
230
+ </table>
231
+ <div class="empty" id="empty" style="display: none">No entries yet.</div>
232
+ </main>
233
+ <aside id="detail"></aside>
234
+ </div>
235
+
236
+ <div class="dash" id="dashboard-view" style="display: none"></div>
237
+
238
+ <script>
239
+ (function () {
240
+ var API_BASE = '__TELESCOPE_API_BASE__';
241
+ var els = {
242
+ rows: document.getElementById('rows'),
243
+ empty: document.getElementById('empty'),
244
+ stats: document.getElementById('stats'),
245
+ detail: document.getElementById('detail'),
246
+ layout: document.getElementById('layout'),
247
+ type: document.getElementById('type-filter'),
248
+ search: document.getElementById('search'),
249
+ autorefresh: document.getElementById('autorefresh'),
250
+ refresh: document.getElementById('refresh'),
251
+ tabs: document.getElementById('tabs'),
252
+ entriesControls: document.getElementById('entries-controls'),
253
+ dashboardView: document.getElementById('dashboard-view'),
254
+ };
255
+ var selectedId = null;
256
+ var timer = null;
257
+ var activeTab = 'entries';
258
+
259
+ function esc(s) {
260
+ return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
261
+ return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
262
+ });
263
+ }
264
+ function qs(params) {
265
+ var parts = [];
266
+ Object.keys(params).forEach(function (k) {
267
+ if (params[k] !== '' && params[k] != null)
268
+ parts.push(encodeURIComponent(k) + '=' + encodeURIComponent(params[k]));
269
+ });
270
+ return parts.length ? '?' + parts.join('&') : '';
271
+ }
272
+ function fetchJson(url) {
273
+ return fetch(url, { headers: { accept: 'application/json' }, credentials: 'same-origin' }).then(function (r) {
274
+ if (!r.ok) throw new Error('HTTP ' + r.status);
275
+ return r.json();
276
+ });
277
+ }
278
+ function fmtTime(iso) {
279
+ try {
280
+ return new Date(iso).toLocaleTimeString() + ' ' + new Date(iso).toLocaleDateString();
281
+ } catch (e) {
282
+ return iso;
283
+ }
284
+ }
285
+
286
+ function renderRows(entries) {
287
+ els.empty.style.display = entries.length ? 'none' : 'block';
288
+ els.rows.innerHTML = entries
289
+ .map(function (e) {
290
+ var sel = e.id === selectedId ? ' class="selected"' : '';
291
+ return (
292
+ '<tr data-id="' + esc(e.id) + '"' + sel + '>' +
293
+ '<td><span class="badge ' + esc(e.type) + '">' + esc(e.type) + '</span></td>' +
294
+ '<td class="summary">' + esc(e.summary) +
295
+ (e.durationMs != null ? ' <span class="time">(' + esc(e.durationMs) + 'ms)</span>' : '') +
296
+ '</td>' +
297
+ '<td class="trace">' + (e.traceId ? esc(e.traceId).slice(0, 12) : '—') + '</td>' +
298
+ '<td class="time">' + esc(fmtTime(e.createdAt)) + '</td>' +
299
+ '</tr>'
300
+ );
301
+ })
302
+ .join('');
303
+ Array.prototype.forEach.call(els.rows.querySelectorAll('tr'), function (tr) {
304
+ tr.addEventListener('click', function () {
305
+ openDetail(tr.getAttribute('data-id'));
306
+ });
307
+ });
308
+ }
309
+
310
+ function renderStats(s) {
311
+ var fam = (s.topFamilies || [])
312
+ .slice(0, 5)
313
+ .map(function (f) { return esc(f.key) + ' (' + f.count + ')'; })
314
+ .join(', ') || '—';
315
+ var tags = (s.topTags || [])
316
+ .slice(0, 5)
317
+ .map(function (t) { return esc(t.key) + ' (' + t.count + ')'; })
318
+ .join(', ') || '—';
319
+ els.stats.innerHTML =
320
+ '<span>Total entries: <b>' + esc(s.count) + '</b></span>' +
321
+ '<span>Top families: <b>' + fam + '</b></span>' +
322
+ '<span>Top tags: <b>' + tags + '</b></span>';
323
+ }
324
+
325
+ function openDetail(id) {
326
+ selectedId = id;
327
+ els.layout.classList.add('detail-open');
328
+ highlight();
329
+ els.detail.innerHTML = '<div class="empty">Loading…</div>';
330
+ fetchJson(API_BASE + '/entries/' + encodeURIComponent(id))
331
+ .then(function (res) {
332
+ var e = res.data;
333
+ var tags = (e.tags || [])
334
+ .map(function (t) { return '<span class="badge">' + esc(t) + '</span>'; })
335
+ .join('');
336
+ els.detail.innerHTML =
337
+ '<div class="detail-head">' +
338
+ '<span class="badge ' + esc(e.type) + '">' + esc(e.type) + '</span>' +
339
+ '<h2>' + esc(e.id) + '</h2>' +
340
+ '<button class="close" id="close-detail">close</button>' +
341
+ '</div>' +
342
+ '<dl class="kv">' +
343
+ '<dt>type</dt><dd>' + esc(e.type) + '</dd>' +
344
+ '<dt>family</dt><dd>' + esc(e.familyHash || '—') + '</dd>' +
345
+ '<dt>trace</dt><dd>' + esc(e.traceId || '—') + '</dd>' +
346
+ '<dt>origin</dt><dd>' + esc(e.origin) + '</dd>' +
347
+ '<dt>duration</dt><dd>' + esc(e.durationMs == null ? '—' : e.durationMs + 'ms') + '</dd>' +
348
+ '<dt>created</dt><dd>' + esc(fmtTime(e.createdAt)) + '</dd>' +
349
+ '</dl>' +
350
+ (tags ? '<div class="kv tagline"><dt>tags</dt><dd>' + tags + '</dd></div>' : '') +
351
+ '<pre>' + esc(JSON.stringify(e.content, null, 2)) + '</pre>';
352
+ document.getElementById('close-detail').addEventListener('click', closeDetail);
353
+ })
354
+ .catch(function (err) {
355
+ els.detail.innerHTML = '<div class="error">' + esc(err.message) + '</div>';
356
+ });
357
+ }
358
+ function closeDetail() {
359
+ selectedId = null;
360
+ els.layout.classList.remove('detail-open');
361
+ highlight();
362
+ }
363
+ function highlight() {
364
+ Array.prototype.forEach.call(els.rows.querySelectorAll('tr'), function (tr) {
365
+ tr.classList.toggle('selected', tr.getAttribute('data-id') === selectedId);
366
+ });
367
+ }
368
+
369
+ // ---- extension dashboards (panel renderer) ----
370
+ var SERIES_COLORS = ['#3fb950', '#f85149', '#58a6ff', '#d29922', '#a78bfa'];
371
+
372
+ function fmtValue(v, format) {
373
+ if (v == null || (typeof v === 'number' && isNaN(v))) return '—';
374
+ if (format === 'percent') return (v * 100).toFixed(1) + '%';
375
+ if (format === 'duration') return v >= 1000 ? (v / 1000).toFixed(2) + 's' : Math.round(v) + 'ms';
376
+ if (format === 'rate') return Math.round(v * 10) / 10 + '/h';
377
+ if (typeof v === 'number') return String(Math.round(v * 100) / 100);
378
+ return String(v);
379
+ }
380
+ function thresholdClass(v, t) {
381
+ if (!t || v == null) return '';
382
+ if (t.direction === 'up-bad') return v >= t.bad ? 'bad' : v >= t.warn ? 'warn' : 'ok';
383
+ return v <= t.bad ? 'bad' : v <= t.warn ? 'warn' : 'ok';
384
+ }
385
+ function clsColor(cls) {
386
+ return cls === 'ok' ? 'var(--green)' : cls === 'warn' ? 'var(--yellow)' : cls === 'bad' ? 'var(--red)' : 'var(--accent)';
387
+ }
388
+ function fillLink(href, row) {
389
+ return href.replace(/\{(\w+)\}/g, function (_, k) {
390
+ return encodeURIComponent(row[k] == null ? '' : row[k]);
391
+ });
392
+ }
393
+ function sparkline(points) {
394
+ if (!points || !points.length) return '';
395
+ var w = 100, h = 24, max = Math.max.apply(null, points), min = Math.min.apply(null, points);
396
+ var span = max - min || 1, step = points.length > 1 ? w / (points.length - 1) : w;
397
+ var d = points
398
+ .map(function (p, i) {
399
+ return (i ? 'L' : 'M') + (i * step).toFixed(1) + ' ' + (h - ((p - min) / span) * (h - 4) - 2).toFixed(1);
400
+ })
401
+ .join(' ');
402
+ return '<svg viewBox="0 0 ' + w + ' ' + h + '" height="24" preserveAspectRatio="none"><path d="' + d + '" fill="none" stroke="var(--accent)" stroke-width="1.5"/></svg>';
403
+ }
404
+ function donut(segments) {
405
+ var total = segments.reduce(function (s, x) { return s + (x.value || 0); }, 0) || 1;
406
+ var r = 30, c = 2 * Math.PI * r, off = 0;
407
+ var rings = segments
408
+ .map(function (seg) {
409
+ var frac = (seg.value || 0) / total;
410
+ var dash = (frac * c).toFixed(2) + ' ' + (c - frac * c).toFixed(2);
411
+ var ring = '<circle r="' + r + '" cx="40" cy="40" fill="none" stroke="' + (seg.color || 'var(--accent)') + '" stroke-width="12" stroke-dasharray="' + dash + '" stroke-dashoffset="' + (-off * c).toFixed(2) + '" transform="rotate(-90 40 40)"/>';
412
+ off += frac;
413
+ return ring;
414
+ })
415
+ .join('');
416
+ return '<svg viewBox="0 0 80 80" height="90">' + rings + '</svg>';
417
+ }
418
+ function columns(rows, series, stacked) {
419
+ if (!rows.length) return '<div class="muted-pill">no data</div>';
420
+ var w = 100, h = 60, n = rows.length, bw = w / n, max = 1;
421
+ rows.forEach(function (r) {
422
+ var sum = stacked ? series.reduce(function (s, k) { return s + (r[k] || 0); }, 0) : Math.max.apply(null, series.map(function (k) { return r[k] || 0; }));
423
+ if (sum > max) max = sum;
424
+ });
425
+ var bars = rows
426
+ .map(function (r, i) {
427
+ var x = i * bw, y = h, out = '';
428
+ series.forEach(function (k, si) {
429
+ var bh = ((r[k] || 0) / max) * (h - 2);
430
+ y -= bh;
431
+ out += '<rect x="' + (x + 1).toFixed(1) + '" y="' + y.toFixed(1) + '" width="' + (bw - 2).toFixed(1) + '" height="' + bh.toFixed(1) + '" fill="' + SERIES_COLORS[si % SERIES_COLORS.length] + '"/>';
432
+ });
433
+ return out;
434
+ })
435
+ .join('');
436
+ return '<svg viewBox="0 0 ' + w + ' ' + h + '" height="70" preserveAspectRatio="none">' + bars + '</svg>';
437
+ }
438
+ function histogram(buckets) {
439
+ if (!buckets.length) return '<div class="muted-pill">no data</div>';
440
+ var w = 100, h = 60, n = buckets.length, bw = w / n;
441
+ var max = Math.max.apply(null, buckets.map(function (b) { return b.count || 0; })) || 1;
442
+ var bars = buckets
443
+ .map(function (b, i) {
444
+ var bh = ((b.count || 0) / max) * (h - 2);
445
+ return '<rect x="' + (i * bw + 1).toFixed(1) + '" y="' + (h - bh).toFixed(1) + '" width="' + (bw - 2).toFixed(1) + '" height="' + bh.toFixed(1) + '" fill="var(--accent)"/>';
446
+ })
447
+ .join('');
448
+ return '<svg viewBox="0 0 ' + w + ' ' + h + '" height="70" preserveAspectRatio="none">' + bars + '</svg>';
449
+ }
450
+ function barList(items) {
451
+ if (!items.length) return '<div class="muted-pill">no data</div>';
452
+ var max = Math.max.apply(null, items.map(function (i) { return i.value || 0; })) || 1;
453
+ return '<div class="barlist">' + items
454
+ .map(function (it) {
455
+ var pct = (((it.value || 0) / max) * 100).toFixed(0);
456
+ return '<div class="row"><span>' + esc(it.label) + '</span><b>' + esc(it.value) + '</b></div><div class="track"><span style="width:' + pct + '%"></span></div>';
457
+ })
458
+ .join('') + '</div>';
459
+ }
460
+ function panelTable(panel, data) {
461
+ var cols = panel.columns || [], rows = data.rows || [];
462
+ if (!rows.length) return '<div class="muted-pill">no data</div>';
463
+ var head = '<tr>' + cols.map(function (c) { return '<th>' + esc(c.label) + '</th>'; }).join('') + '</tr>';
464
+ var body = rows
465
+ .map(function (row) {
466
+ return '<tr>' + cols
467
+ .map(function (c) {
468
+ var cell = esc(row[c.key] == null ? '' : row[c.key]);
469
+ if (c.link && c.link.href) {
470
+ var tgt = c.link.external ? ' target="_blank" rel="noopener"' : '';
471
+ cell = '<a href="' + esc(fillLink(c.link.href, row)) + '"' + tgt + '>' + cell + '</a>';
472
+ }
473
+ return '<td>' + cell + '</td>';
474
+ })
475
+ .join('') + '</tr>';
476
+ })
477
+ .join('');
478
+ return '<table><thead>' + head + '</thead><tbody>' + body + '</tbody></table>';
479
+ }
480
+ function renderPanelBody(panel, data) {
481
+ if (panel.kind === 'stat') {
482
+ var cls = thresholdClass(data.value, panel.thresholds);
483
+ var delta = data.delta != null
484
+ ? '<div class="delta ' + (data.delta >= 0 ? 'up' : 'down') + '">' + (data.delta >= 0 ? 'â–˛' : 'â–Ľ') + ' ' + esc(fmtValue(Math.abs(data.delta), panel.format)) + '</div>'
485
+ : '';
486
+ var spark = panel.spark && data.spark ? sparkline(data.spark) : '';
487
+ return '<div class="big ' + cls + '">' + esc(fmtValue(data.value, panel.format)) + '</div>' + delta + spark;
488
+ }
489
+ if (panel.kind === 'gauge') {
490
+ var gcls = thresholdClass(data.value, panel.thresholds);
491
+ var max = panel.max != null ? panel.max : data.max != null ? data.max : 1;
492
+ var pct = max ? Math.max(0, Math.min(100, (data.value / max) * 100)) : 0;
493
+ return '<div class="big ' + gcls + '">' + esc(fmtValue(data.value, panel.format)) + '</div>' +
494
+ '<div style="height:6px;border-radius:3px;background:var(--panel-2);margin-top:10px;overflow:hidden"><span style="display:block;height:100%;width:' + pct.toFixed(0) + '%;background:' + clsColor(gcls) + '"></span></div>';
495
+ }
496
+ if (panel.kind === 'table') return panelTable(panel, data);
497
+ if (panel.kind === 'topN') return barList((data.items || []).slice(0, panel.limit || 8));
498
+ if (panel.kind === 'breakdown') {
499
+ var segs = data.segments || [];
500
+ var legend = '<div class="legend">' + segs
501
+ .map(function (s) { return '<span><span class="swatch" style="background:' + (s.color || 'var(--accent)') + '"></span>' + esc(s.label) + ' ' + esc(s.value) + '</span>'; })
502
+ .join('') + '</div>';
503
+ return donut(segs) + legend;
504
+ }
505
+ if (panel.kind === 'timeseries') return columns(data.rows || [], panel.series || [], panel.style === 'stacked');
506
+ if (panel.kind === 'distribution') {
507
+ var markers = (panel.markers || [])
508
+ .map(function (m) { return '<span>' + m + ' <b>' + esc(fmtValue(data[m], panel.format)) + '</b></span>'; })
509
+ .join('');
510
+ return histogram(data.buckets || []) + (markers ? '<div class="markers">' + markers + '</div>' : '');
511
+ }
512
+ return '<div class="muted-pill">unsupported panel: ' + esc(panel.kind) + '</div>';
513
+ }
514
+ function panelCard(panel) {
515
+ var card = document.createElement('div');
516
+ card.className = 'card';
517
+ card.innerHTML = '<div class="card-title">' + esc(panel.title) + '</div><div class="muted-pill">Loading…</div>';
518
+ var binding = panel.data || {};
519
+ var provider = binding.provider || '';
520
+ var ext = provider.split('.')[0];
521
+ var url = API_BASE + '/ext/' + encodeURIComponent(ext) + '/data/' + encodeURIComponent(provider) + qs(binding.query || {});
522
+ fetchJson(url)
523
+ .then(function (res) {
524
+ card.innerHTML = '<div class="card-title">' + esc(panel.title) + '</div>' + renderPanelBody(panel, res.data || {});
525
+ })
526
+ .catch(function (err) {
527
+ card.innerHTML = '<div class="card-title">' + esc(panel.title) + '</div><div class="error">' + esc(err.message) + '</div>';
528
+ });
529
+ return card;
530
+ }
531
+ function renderDashboardSpec(spec) {
532
+ els.dashboardView.innerHTML = '';
533
+ var sections = spec.sections && spec.sections.length ? spec.sections : [{ panels: spec.panels || [], cols: 3 }];
534
+ sections.forEach(function (section) {
535
+ var secEl = document.createElement('div');
536
+ secEl.className = 'dash-section';
537
+ if (section.title) secEl.innerHTML = '<h3>' + esc(section.title) + '</h3>';
538
+ var grid = document.createElement('div');
539
+ grid.className = 'grid cols-' + (section.cols || 3);
540
+ (section.panels || []).forEach(function (panel) { grid.appendChild(panelCard(panel)); });
541
+ secEl.appendChild(grid);
542
+ els.dashboardView.appendChild(secEl);
543
+ });
544
+ }
545
+
546
+ function updateTabs() {
547
+ Array.prototype.forEach.call(els.tabs.querySelectorAll('button'), function (b) {
548
+ b.classList.toggle('active', b.getAttribute('data-tab') === activeTab);
549
+ });
550
+ }
551
+ function showEntries() {
552
+ activeTab = 'entries';
553
+ els.layout.style.display = '';
554
+ els.stats.style.display = '';
555
+ els.entriesControls.style.display = '';
556
+ els.dashboardView.style.display = 'none';
557
+ updateTabs();
558
+ load();
559
+ }
560
+ function showDashboard(spec) {
561
+ activeTab = spec.id;
562
+ els.layout.style.display = 'none';
563
+ els.stats.style.display = 'none';
564
+ els.entriesControls.style.display = 'none';
565
+ els.dashboardView.style.display = '';
566
+ updateTabs();
567
+ renderDashboardSpec(spec);
568
+ }
569
+ function buildTabs(dashboards) {
570
+ els.tabs.innerHTML = '';
571
+ var entriesBtn = document.createElement('button');
572
+ entriesBtn.setAttribute('data-tab', 'entries');
573
+ entriesBtn.textContent = 'Entries';
574
+ entriesBtn.addEventListener('click', showEntries);
575
+ els.tabs.appendChild(entriesBtn);
576
+ dashboards.forEach(function (d) {
577
+ var b = document.createElement('button');
578
+ b.setAttribute('data-tab', d.id);
579
+ b.textContent = d.label;
580
+ b.addEventListener('click', function () { showDashboard(d); });
581
+ els.tabs.appendChild(b);
582
+ });
583
+ updateTabs();
584
+ }
585
+
586
+ function load() {
587
+ var query = qs({ type: els.type.value, search: els.search.value.trim(), limit: 100 });
588
+ fetchJson(API_BASE + '/entries' + query)
589
+ .then(function (res) { renderRows(res.data || []); })
590
+ .catch(function (err) {
591
+ els.rows.innerHTML = '';
592
+ els.empty.style.display = 'block';
593
+ els.empty.textContent = 'Error: ' + err.message;
594
+ });
595
+ fetchJson(API_BASE + '/stats')
596
+ .then(function (res) { renderStats(res.data || {}); })
597
+ .catch(function () {});
598
+ }
599
+
600
+ function setAutoRefresh(on) {
601
+ if (timer) { clearInterval(timer); timer = null; }
602
+ if (on) timer = setInterval(load, 3000);
603
+ }
604
+
605
+ var searchDebounce;
606
+ els.search.addEventListener('input', function () {
607
+ clearTimeout(searchDebounce);
608
+ searchDebounce = setTimeout(load, 250);
609
+ });
610
+ els.type.addEventListener('change', load);
611
+ els.refresh.addEventListener('click', load);
612
+ els.autorefresh.addEventListener('change', function () { setAutoRefresh(els.autorefresh.checked); });
613
+
614
+ // Discover extension dashboards (entries-only when no extensions / the route is absent).
615
+ fetchJson(API_BASE + '/meta')
616
+ .then(function (res) {
617
+ var dashboards = (res.data && res.data.dashboards) || [];
618
+ if (dashboards.length) buildTabs(dashboards);
619
+ })
620
+ .catch(function () {});
621
+
622
+ load();
623
+ })();
624
+ </script>
625
+ </body>
626
+ </html>
@@ -0,0 +1,29 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { fileURLToPath } from 'node:url';
3
+ /**
4
+ * The raw dashboard HTML, read once from the sibling `dashboard.html` (copied into
5
+ * `dist/src/` by the build's `copy:assets` step). Lazily loaded + cached so the
6
+ * file read happens at most once per process and only when the dashboard is served.
7
+ */
8
+ let cachedHtml = null;
9
+ function loadHtml() {
10
+ if (cachedHtml === null) {
11
+ const htmlPath = fileURLToPath(new URL('./dashboard.html', import.meta.url));
12
+ cachedHtml = readFileSync(htmlPath, 'utf8');
13
+ }
14
+ return cachedHtml;
15
+ }
16
+ /**
17
+ * Render the self-contained dashboard page, injecting the JSON API base path so
18
+ * the inline script knows where to fetch from. The template contains a
19
+ * `__TELESCOPE_API_BASE__` placeholder which is replaced with `apiBase` (e.g.
20
+ * `/telescope/api`).
21
+ */
22
+ export function renderDashboard(apiBase) {
23
+ return loadHtml().replaceAll('__TELESCOPE_API_BASE__', escapeForScript(apiBase));
24
+ }
25
+ /** Escape characters that would break out of the single-quoted JS string literal. */
26
+ function escapeForScript(value) {
27
+ return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/</g, '\\u003c');
28
+ }
29
+ //# sourceMappingURL=dashboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../../../src/ui/dashboard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC;;;;GAIG;AACH,IAAI,UAAU,GAAkB,IAAI,CAAC;AAErC,SAAS,QAAQ;IACf,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;QACxB,MAAM,QAAQ,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,kBAAkB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7E,UAAU,GAAG,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC9C,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,OAAO,QAAQ,EAAE,CAAC,UAAU,CAAC,wBAAwB,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;AACnF,CAAC;AAED,qFAAqF;AACrF,SAAS,eAAe,CAAC,KAAa;IACpC,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;AACpF,CAAC"}