orez 0.0.45 → 0.0.47

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 (60) hide show
  1. package/README.md +8 -12
  2. package/dist/admin/http-proxy.d.ts +31 -0
  3. package/dist/admin/http-proxy.d.ts.map +1 -0
  4. package/dist/admin/http-proxy.js +140 -0
  5. package/dist/admin/http-proxy.js.map +1 -0
  6. package/dist/admin/log-store.d.ts +22 -0
  7. package/dist/admin/log-store.d.ts.map +1 -0
  8. package/dist/admin/log-store.js +86 -0
  9. package/dist/admin/log-store.js.map +1 -0
  10. package/dist/admin/server.d.ts +19 -0
  11. package/dist/admin/server.d.ts.map +1 -0
  12. package/dist/admin/server.js +110 -0
  13. package/dist/admin/server.js.map +1 -0
  14. package/dist/admin/ui.d.ts +2 -0
  15. package/dist/admin/ui.d.ts.map +1 -0
  16. package/dist/admin/ui.js +683 -0
  17. package/dist/admin/ui.js.map +1 -0
  18. package/dist/cli.js +48 -1
  19. package/dist/cli.js.map +1 -1
  20. package/dist/config.d.ts +4 -0
  21. package/dist/config.d.ts.map +1 -1
  22. package/dist/config.js +4 -0
  23. package/dist/config.js.map +1 -1
  24. package/dist/index.d.ts +9 -0
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +191 -33
  27. package/dist/index.js.map +1 -1
  28. package/dist/log.d.ts +9 -0
  29. package/dist/log.d.ts.map +1 -1
  30. package/dist/log.js +24 -1
  31. package/dist/log.js.map +1 -1
  32. package/dist/pg-proxy.d.ts.map +1 -1
  33. package/dist/pg-proxy.js +19 -4
  34. package/dist/pg-proxy.js.map +1 -1
  35. package/dist/pglite-manager.d.ts +1 -0
  36. package/dist/pglite-manager.d.ts.map +1 -1
  37. package/dist/pglite-manager.js +1 -1
  38. package/dist/pglite-manager.js.map +1 -1
  39. package/dist/replication/handler.d.ts.map +1 -1
  40. package/dist/replication/handler.js +20 -2
  41. package/dist/replication/handler.js.map +1 -1
  42. package/dist/vite-plugin.d.ts +3 -0
  43. package/dist/vite-plugin.d.ts.map +1 -1
  44. package/dist/vite-plugin.js +24 -0
  45. package/dist/vite-plugin.js.map +1 -1
  46. package/package.json +4 -2
  47. package/src/admin/http-proxy.ts +186 -0
  48. package/src/admin/log-store.ts +111 -0
  49. package/src/admin/server.ts +148 -0
  50. package/src/admin/ui.ts +682 -0
  51. package/src/cli.ts +49 -1
  52. package/src/config.ts +8 -0
  53. package/src/index.ts +207 -32
  54. package/src/log.ts +25 -1
  55. package/src/pg-proxy.ts +26 -6
  56. package/src/pglite-manager.ts +1 -1
  57. package/src/replication/handler.ts +21 -2
  58. package/src/shim/hooks.mjs +11 -8
  59. package/src/shim/register.mjs +2 -2
  60. package/src/vite-plugin.ts +28 -0
@@ -0,0 +1,683 @@
1
+ export function getAdminHtml() {
2
+ return '<!DOCTYPE html>\n' +
3
+ '<html lang="en">\n' +
4
+ '<head>\n' +
5
+ '<meta charset="utf-8">\n' +
6
+ '<meta name="viewport" content="width=device-width, initial-scale=1">\n' +
7
+ '<title>orez admin</title>\n' +
8
+ '<style>\n' +
9
+ ':root {\n' +
10
+ ' --bg: #0d1117;\n' +
11
+ ' --surface: #161b22;\n' +
12
+ ' --border: #30363d;\n' +
13
+ ' --text: #e6edf3;\n' +
14
+ ' --text-dim: #8b949e;\n' +
15
+ ' --accent: #58a6ff;\n' +
16
+ ' --green: #3fb950;\n' +
17
+ ' --yellow: #d29922;\n' +
18
+ ' --red: #f85149;\n' +
19
+ ' --purple: #bc8cff;\n' +
20
+ '}\n' +
21
+ '* { margin: 0; padding: 0; box-sizing: border-box; }\n' +
22
+ 'body {\n' +
23
+ ' font-family: "SF Mono", "Fira Code", "JetBrains Mono", "Cascadia Code", monospace;\n' +
24
+ ' background: var(--bg);\n' +
25
+ ' color: var(--text);\n' +
26
+ ' height: 100vh;\n' +
27
+ ' display: flex;\n' +
28
+ ' flex-direction: column;\n' +
29
+ ' overflow: hidden;\n' +
30
+ '}\n' +
31
+ '.header {\n' +
32
+ ' display: flex;\n' +
33
+ ' align-items: center;\n' +
34
+ ' padding: 12px 16px;\n' +
35
+ ' background: var(--surface);\n' +
36
+ ' border-bottom: 1px solid var(--border);\n' +
37
+ ' gap: 12px;\n' +
38
+ ' flex-shrink: 0;\n' +
39
+ '}\n' +
40
+ '.header .logo {\n' +
41
+ ' font-size: 15px;\n' +
42
+ ' font-weight: 700;\n' +
43
+ ' color: var(--accent);\n' +
44
+ ' letter-spacing: -0.5px;\n' +
45
+ '}\n' +
46
+ '.badge {\n' +
47
+ ' display: inline-flex;\n' +
48
+ ' align-items: center;\n' +
49
+ ' padding: 2px 8px;\n' +
50
+ ' border-radius: 12px;\n' +
51
+ ' font-size: 11px;\n' +
52
+ ' border: 1px solid var(--border);\n' +
53
+ ' color: var(--text-dim);\n' +
54
+ ' gap: 4px;\n' +
55
+ '}\n' +
56
+ '.badge .dot {\n' +
57
+ ' width: 6px;\n' +
58
+ ' height: 6px;\n' +
59
+ ' border-radius: 50%;\n' +
60
+ ' background: var(--green);\n' +
61
+ '}\n' +
62
+ '.spacer { flex: 1; }\n' +
63
+ '.tabs {\n' +
64
+ ' display: flex;\n' +
65
+ ' padding: 0 16px;\n' +
66
+ ' background: var(--surface);\n' +
67
+ ' border-bottom: 1px solid var(--border);\n' +
68
+ ' gap: 2px;\n' +
69
+ ' flex-shrink: 0;\n' +
70
+ '}\n' +
71
+ '.tab {\n' +
72
+ ' padding: 8px 14px;\n' +
73
+ ' font-size: 12px;\n' +
74
+ ' color: var(--text-dim);\n' +
75
+ ' cursor: pointer;\n' +
76
+ ' border-bottom: 2px solid transparent;\n' +
77
+ ' transition: all 0.15s;\n' +
78
+ ' background: none;\n' +
79
+ ' border-top: none;\n' +
80
+ ' border-left: none;\n' +
81
+ ' border-right: none;\n' +
82
+ ' font-family: inherit;\n' +
83
+ '}\n' +
84
+ '.tab:hover { color: var(--text); }\n' +
85
+ '.tab.active {\n' +
86
+ ' color: var(--accent);\n' +
87
+ ' border-bottom-color: var(--accent);\n' +
88
+ '}\n' +
89
+ '.toolbar {\n' +
90
+ ' display: flex;\n' +
91
+ ' align-items: center;\n' +
92
+ ' padding: 8px 16px;\n' +
93
+ ' gap: 10px;\n' +
94
+ ' border-bottom: 1px solid var(--border);\n' +
95
+ ' flex-shrink: 0;\n' +
96
+ '}\n' +
97
+ '.toolbar label {\n' +
98
+ ' font-size: 11px;\n' +
99
+ ' color: var(--text-dim);\n' +
100
+ ' text-transform: uppercase;\n' +
101
+ ' letter-spacing: 0.5px;\n' +
102
+ '}\n' +
103
+ '.toolbar select {\n' +
104
+ ' background: var(--surface);\n' +
105
+ ' color: var(--text);\n' +
106
+ ' border: 1px solid var(--border);\n' +
107
+ ' border-radius: 6px;\n' +
108
+ ' padding: 4px 8px;\n' +
109
+ ' font-size: 12px;\n' +
110
+ ' font-family: inherit;\n' +
111
+ ' cursor: pointer;\n' +
112
+ '}\n' +
113
+ '.toolbar select:focus { outline: none; border-color: var(--accent); }\n' +
114
+ '.toolbar input[type="text"] {\n' +
115
+ ' background: var(--surface);\n' +
116
+ ' color: var(--text);\n' +
117
+ ' border: 1px solid var(--border);\n' +
118
+ ' border-radius: 6px;\n' +
119
+ ' padding: 4px 8px;\n' +
120
+ ' font-size: 12px;\n' +
121
+ ' font-family: inherit;\n' +
122
+ ' width: 200px;\n' +
123
+ '}\n' +
124
+ '.toolbar input[type="text"]:focus { outline: none; border-color: var(--accent); }\n' +
125
+ '.toolbar input[type="text"]::placeholder { color: var(--text-dim); }\n' +
126
+ '.sep { width: 1px; height: 20px; background: var(--border); }\n' +
127
+ '.action-btn {\n' +
128
+ ' padding: 5px 12px;\n' +
129
+ ' border-radius: 6px;\n' +
130
+ ' border: 1px solid;\n' +
131
+ ' background: transparent;\n' +
132
+ ' cursor: pointer;\n' +
133
+ ' font-family: inherit;\n' +
134
+ ' font-size: 11px;\n' +
135
+ ' transition: all 0.15s ease;\n' +
136
+ ' white-space: nowrap;\n' +
137
+ '}\n' +
138
+ '.action-btn:disabled { opacity: 0.4; cursor: not-allowed; }\n' +
139
+ '.action-btn.blue { color: var(--accent); border-color: #1f6feb44; }\n' +
140
+ '.action-btn.blue:hover:not(:disabled) { background: #1f6feb22; border-color: var(--accent); }\n' +
141
+ '.action-btn.orange { color: var(--yellow); border-color: #d2992244; }\n' +
142
+ '.action-btn.orange:hover:not(:disabled) { background: #d2992222; border-color: var(--yellow); }\n' +
143
+ '.action-btn.red { color: var(--red); border-color: #f8514944; }\n' +
144
+ '.action-btn.red:hover:not(:disabled) { background: #f8514922; border-color: var(--red); }\n' +
145
+ '.action-btn.gray { color: var(--text-dim); border-color: #8b949e44; }\n' +
146
+ '.action-btn.gray:hover:not(:disabled) { background: #8b949e22; border-color: var(--text-dim); }\n' +
147
+ '.content-area {\n' +
148
+ ' flex: 1;\n' +
149
+ ' overflow: hidden;\n' +
150
+ ' position: relative;\n' +
151
+ ' display: flex;\n' +
152
+ ' flex-direction: column;\n' +
153
+ '}\n' +
154
+ '.log-wrap {\n' +
155
+ ' flex: 1;\n' +
156
+ ' overflow: hidden;\n' +
157
+ ' position: relative;\n' +
158
+ '}\n' +
159
+ '.log-view {\n' +
160
+ ' height: 100%;\n' +
161
+ ' overflow-y: auto;\n' +
162
+ ' padding: 8px 16px;\n' +
163
+ ' font-size: 12px;\n' +
164
+ ' line-height: 1.5;\n' +
165
+ '}\n' +
166
+ '.log-line { white-space: pre-wrap; word-break: break-all; }\n' +
167
+ '.log-line .ts { color: var(--text-dim); }\n' +
168
+ '.log-line .src { display: inline-block; width: 7ch; }\n' +
169
+ '.log-line .src.zero { color: var(--purple); }\n' +
170
+ '.log-line .src.pglite { color: var(--green); }\n' +
171
+ '.log-line .src.proxy { color: var(--yellow); }\n' +
172
+ '.log-line .src.orez { color: var(--accent); }\n' +
173
+ '.log-line .src.s3 { color: #79c0ff; }\n' +
174
+ '.log-line.level-error .msg { color: var(--red); }\n' +
175
+ '.log-line.level-warn .msg { color: var(--yellow); }\n' +
176
+ '.log-line.level-info .msg { color: var(--text); }\n' +
177
+ '.log-line.level-debug .msg { color: var(--text-dim); }\n' +
178
+ '.jump-btn {\n' +
179
+ ' position: absolute;\n' +
180
+ ' bottom: 16px;\n' +
181
+ ' left: 50%;\n' +
182
+ ' transform: translateX(-50%);\n' +
183
+ ' padding: 6px 16px;\n' +
184
+ ' border-radius: 20px;\n' +
185
+ ' background: var(--accent);\n' +
186
+ ' color: #fff;\n' +
187
+ ' border: none;\n' +
188
+ ' font-size: 12px;\n' +
189
+ ' font-family: inherit;\n' +
190
+ ' cursor: pointer;\n' +
191
+ ' opacity: 0;\n' +
192
+ ' transition: opacity 0.2s;\n' +
193
+ ' pointer-events: none;\n' +
194
+ ' z-index: 10;\n' +
195
+ '}\n' +
196
+ '.jump-btn.visible { opacity: 1; pointer-events: auto; }\n' +
197
+ '.env-view {\n' +
198
+ ' height: 100%;\n' +
199
+ ' overflow-y: auto;\n' +
200
+ ' padding: 16px;\n' +
201
+ ' display: none;\n' +
202
+ '}\n' +
203
+ '.env-table { width: 100%; border-collapse: collapse; font-size: 12px; }\n' +
204
+ '.env-table th {\n' +
205
+ ' text-align: left;\n' +
206
+ ' padding: 6px 12px;\n' +
207
+ ' color: var(--text-dim);\n' +
208
+ ' border-bottom: 1px solid var(--border);\n' +
209
+ ' font-weight: 500;\n' +
210
+ ' text-transform: uppercase;\n' +
211
+ ' font-size: 10px;\n' +
212
+ ' letter-spacing: 0.5px;\n' +
213
+ '}\n' +
214
+ '.env-table td {\n' +
215
+ ' padding: 6px 12px;\n' +
216
+ ' border-bottom: 1px solid #21262d;\n' +
217
+ '}\n' +
218
+ '.env-table td:first-child { color: var(--accent); white-space: nowrap; }\n' +
219
+ '.env-table td:last-child { color: var(--text); word-break: break-all; }\n' +
220
+ '.env-table tr:hover td { background: #161b22; }\n' +
221
+ // http view
222
+ '.http-view {\n' +
223
+ ' height: 100%;\n' +
224
+ ' overflow-y: auto;\n' +
225
+ ' padding: 0;\n' +
226
+ ' display: none;\n' +
227
+ '}\n' +
228
+ '.http-table { width: 100%; border-collapse: collapse; font-size: 12px; }\n' +
229
+ '.http-table th {\n' +
230
+ ' text-align: left;\n' +
231
+ ' padding: 6px 12px;\n' +
232
+ ' color: var(--text-dim);\n' +
233
+ ' border-bottom: 1px solid var(--border);\n' +
234
+ ' font-weight: 500;\n' +
235
+ ' text-transform: uppercase;\n' +
236
+ ' font-size: 10px;\n' +
237
+ ' letter-spacing: 0.5px;\n' +
238
+ ' position: sticky;\n' +
239
+ ' top: 0;\n' +
240
+ ' background: var(--bg);\n' +
241
+ ' z-index: 1;\n' +
242
+ '}\n' +
243
+ '.http-table td {\n' +
244
+ ' padding: 5px 12px;\n' +
245
+ ' border-bottom: 1px solid #21262d;\n' +
246
+ ' white-space: nowrap;\n' +
247
+ '}\n' +
248
+ '.http-table tr.http-row { cursor: pointer; }\n' +
249
+ '.http-table tr.http-row:hover td { background: #161b22; }\n' +
250
+ '.http-table .method { font-weight: 600; }\n' +
251
+ '.http-table .method.get { color: var(--green); }\n' +
252
+ '.http-table .method.post { color: var(--yellow); }\n' +
253
+ '.http-table .method.put { color: var(--accent); }\n' +
254
+ '.http-table .method.delete { color: var(--red); }\n' +
255
+ '.http-table .method.patch { color: #79c0ff; }\n' +
256
+ '.http-table .method.ws { color: var(--purple); }\n' +
257
+ '.http-table .status.s2 { color: var(--green); }\n' +
258
+ '.http-table .status.s3 { color: var(--yellow); }\n' +
259
+ '.http-table .status.s4 { color: var(--red); }\n' +
260
+ '.http-table .status.s5 { color: var(--red); font-weight: 600; }\n' +
261
+ '.http-table .path { color: var(--text); max-width: 500px; overflow: hidden; text-overflow: ellipsis; }\n' +
262
+ '.http-table .dur { color: var(--text-dim); }\n' +
263
+ '.http-table .sz { color: var(--text-dim); }\n' +
264
+ '.http-detail {\n' +
265
+ ' display: none;\n' +
266
+ '}\n' +
267
+ '.http-detail.open { display: table-row; }\n' +
268
+ '.http-detail td {\n' +
269
+ ' padding: 8px 12px 12px 24px;\n' +
270
+ ' background: #0c0e14;\n' +
271
+ ' border-bottom: 1px solid var(--border);\n' +
272
+ '}\n' +
273
+ '.http-detail .hdr-section { margin-bottom: 8px; }\n' +
274
+ '.http-detail .hdr-title {\n' +
275
+ ' font-size: 10px;\n' +
276
+ ' text-transform: uppercase;\n' +
277
+ ' color: var(--text-dim);\n' +
278
+ ' letter-spacing: 0.5px;\n' +
279
+ ' margin-bottom: 4px;\n' +
280
+ '}\n' +
281
+ '.http-detail .hdr-line {\n' +
282
+ ' font-size: 11px;\n' +
283
+ ' line-height: 1.6;\n' +
284
+ ' white-space: pre-wrap;\n' +
285
+ ' word-break: break-all;\n' +
286
+ '}\n' +
287
+ '.http-detail .hdr-key { color: var(--accent); }\n' +
288
+ '.http-detail .hdr-val { color: var(--text-dim); }\n' +
289
+ // actions panel
290
+ '.actions-panel {\n' +
291
+ ' flex-shrink: 0;\n' +
292
+ ' border-top: 1px solid var(--border);\n' +
293
+ ' background: var(--surface);\n' +
294
+ ' padding: 8px 16px;\n' +
295
+ '}\n' +
296
+ '.action-row {\n' +
297
+ ' display: flex;\n' +
298
+ ' align-items: center;\n' +
299
+ ' gap: 8px;\n' +
300
+ ' padding: 4px 0;\n' +
301
+ '}\n' +
302
+ '.action-label {\n' +
303
+ ' font-size: 11px;\n' +
304
+ ' font-weight: 600;\n' +
305
+ ' width: 7ch;\n' +
306
+ ' flex-shrink: 0;\n' +
307
+ '}\n' +
308
+ '.action-label.zero { color: var(--purple); }\n' +
309
+ '.action-label.logs { color: var(--text-dim); }\n' +
310
+ // toast
311
+ '.toast {\n' +
312
+ ' position: fixed;\n' +
313
+ ' bottom: 20px;\n' +
314
+ ' right: 20px;\n' +
315
+ ' padding: 10px 16px;\n' +
316
+ ' border-radius: 8px;\n' +
317
+ ' background: var(--surface);\n' +
318
+ ' border: 1px solid var(--border);\n' +
319
+ ' color: var(--text);\n' +
320
+ ' font-size: 12px;\n' +
321
+ ' font-family: inherit;\n' +
322
+ ' opacity: 0;\n' +
323
+ ' transform: translateY(10px);\n' +
324
+ ' transition: all 0.3s ease;\n' +
325
+ ' pointer-events: none;\n' +
326
+ ' z-index: 100;\n' +
327
+ '}\n' +
328
+ '.toast.show { opacity: 1; transform: translateY(0); }\n' +
329
+ '.toast.error { border-color: var(--red); color: var(--red); }\n' +
330
+ '.toast.success { border-color: var(--green); color: var(--green); }\n' +
331
+ '</style>\n' +
332
+ '</head>\n' +
333
+ '<body>\n' +
334
+ ' <div class="header">\n' +
335
+ ' <span class="logo">&#9670; orez admin</span>\n' +
336
+ ' <div class="spacer"></div>\n' +
337
+ ' <span class="badge"><span class="dot"></span> pg <span id="pg-port">-</span></span>\n' +
338
+ ' <span class="badge"><span class="dot"></span> zero <span id="zero-port">-</span></span>\n' +
339
+ ' <span class="badge" id="uptime-badge">&#9201; --</span>\n' +
340
+ ' </div>\n' +
341
+ '\n' +
342
+ ' <div class="tabs" id="tab-bar">\n' +
343
+ ' <button class="tab active" data-source="">All</button>\n' +
344
+ ' <button class="tab" data-source="zero">Zero</button>\n' +
345
+ ' <button class="tab" data-source="pglite">PGlite</button>\n' +
346
+ ' <button class="tab" data-source="proxy">Proxy</button>\n' +
347
+ ' <button class="tab" data-source="orez">Orez</button>\n' +
348
+ ' <button class="tab" data-source="s3">S3</button>\n' +
349
+ ' <button class="tab" data-source="http">HTTP</button>\n' +
350
+ ' <button class="tab" data-source="env">Env</button>\n' +
351
+ ' </div>\n' +
352
+ '\n' +
353
+ ' <div class="toolbar" id="toolbar">\n' +
354
+ ' <label>Level</label>\n' +
355
+ ' <select id="level-filter">\n' +
356
+ ' <option value="" selected>all levels</option>\n' +
357
+ ' <option value="error">error only</option>\n' +
358
+ ' <option value="warn">warn+</option>\n' +
359
+ ' <option value="info">info+</option>\n' +
360
+ ' </select>\n' +
361
+ ' </div>\n' +
362
+ '\n' +
363
+ ' <div class="toolbar" id="http-toolbar" style="display:none">\n' +
364
+ ' <label>Filter</label>\n' +
365
+ ' <input type="text" id="http-path-filter" placeholder="filter by path...">\n' +
366
+ ' </div>\n' +
367
+ '\n' +
368
+ ' <div class="content-area">\n' +
369
+ ' <div class="log-wrap">\n' +
370
+ ' <div class="log-view" id="log-view"></div>\n' +
371
+ ' <div class="env-view" id="env-view">\n' +
372
+ ' <table class="env-table">\n' +
373
+ ' <thead><tr><th>Variable</th><th>Value</th></tr></thead>\n' +
374
+ ' <tbody id="env-body"></tbody>\n' +
375
+ ' </table>\n' +
376
+ ' </div>\n' +
377
+ ' <div class="http-view" id="http-view">\n' +
378
+ ' <table class="http-table">\n' +
379
+ ' <thead><tr>\n' +
380
+ ' <th>Time</th>\n' +
381
+ ' <th>Method</th>\n' +
382
+ ' <th>Path</th>\n' +
383
+ ' <th>Status</th>\n' +
384
+ ' <th>Duration</th>\n' +
385
+ ' <th>Size</th>\n' +
386
+ ' </tr></thead>\n' +
387
+ ' <tbody id="http-body"></tbody>\n' +
388
+ ' </table>\n' +
389
+ ' </div>\n' +
390
+ ' <button class="jump-btn" id="jump-btn" onclick="jumpToBottom()">&#x2193; Jump to bottom</button>\n' +
391
+ ' </div>\n' +
392
+ '\n' +
393
+ ' <div class="actions-panel" id="actions-panel">\n' +
394
+ ' <div class="action-row">\n' +
395
+ ' <span class="action-label zero">zero</span>\n' +
396
+ ' <button class="action-btn blue" data-zero-action onclick="doAction(\'restart-zero\', this)">&#x21bb; Restart</button>\n' +
397
+ ' <button class="action-btn orange" data-zero-action onclick="doAction(\'reset-zero\', this)">&#x21ba; Reset</button>\n' +
398
+ ' </div>\n' +
399
+ ' <div class="action-row">\n' +
400
+ ' <span class="action-label logs">logs</span>\n' +
401
+ ' <button class="action-btn gray" onclick="doAction(\'clear-logs\', this)">&#x2715; Clear Logs</button>\n' +
402
+ ' <button class="action-btn gray" onclick="doAction(\'clear-http\', this)">&#x2715; Clear HTTP</button>\n' +
403
+ ' </div>\n' +
404
+ ' </div>\n' +
405
+ ' </div>\n' +
406
+ '\n' +
407
+ ' <div class="toast" id="toast"></div>\n' +
408
+ '\n' +
409
+ '<script>\n' +
410
+ 'var activeSource = "";\n' +
411
+ 'var activeLevel = "";\n' +
412
+ 'var lastCursor = 0;\n' +
413
+ 'var autoScroll = true;\n' +
414
+ 'var envLoaded = false;\n' +
415
+ 'var isEnvTab = false;\n' +
416
+ 'var isHttpTab = false;\n' +
417
+ 'var httpCursor = 0;\n' +
418
+ 'var httpAutoScroll = true;\n' +
419
+ '\n' +
420
+ 'var logView = document.getElementById("log-view");\n' +
421
+ 'var envView = document.getElementById("env-view");\n' +
422
+ 'var httpView = document.getElementById("http-view");\n' +
423
+ 'var jumpBtn = document.getElementById("jump-btn");\n' +
424
+ 'var toastEl = document.getElementById("toast");\n' +
425
+ 'var toolbar = document.getElementById("toolbar");\n' +
426
+ 'var httpToolbar = document.getElementById("http-toolbar");\n' +
427
+ '\n' +
428
+ 'document.getElementById("tab-bar").addEventListener("click", function(e) {\n' +
429
+ ' var tab = e.target.closest(".tab");\n' +
430
+ ' if (!tab) return;\n' +
431
+ ' document.querySelectorAll(".tab").forEach(function(t) { t.classList.remove("active"); });\n' +
432
+ ' tab.classList.add("active");\n' +
433
+ ' var source = tab.dataset.source;\n' +
434
+ ' isEnvTab = source === "env";\n' +
435
+ ' isHttpTab = source === "http";\n' +
436
+ ' logView.style.display = "none";\n' +
437
+ ' envView.style.display = "none";\n' +
438
+ ' httpView.style.display = "none";\n' +
439
+ ' toolbar.style.display = "none";\n' +
440
+ ' httpToolbar.style.display = "none";\n' +
441
+ ' if (isEnvTab) {\n' +
442
+ ' envView.style.display = "block";\n' +
443
+ ' if (!envLoaded) loadEnv();\n' +
444
+ ' } else if (isHttpTab) {\n' +
445
+ ' httpView.style.display = "block";\n' +
446
+ ' httpToolbar.style.display = "flex";\n' +
447
+ ' httpCursor = 0;\n' +
448
+ ' document.getElementById("http-body").innerHTML = "";\n' +
449
+ ' fetchHttp();\n' +
450
+ ' } else {\n' +
451
+ ' logView.style.display = "block";\n' +
452
+ ' toolbar.style.display = "flex";\n' +
453
+ ' activeSource = source;\n' +
454
+ ' lastCursor = 0;\n' +
455
+ ' logView.innerHTML = "";\n' +
456
+ ' fetchLogs();\n' +
457
+ ' }\n' +
458
+ '});\n' +
459
+ '\n' +
460
+ 'document.getElementById("level-filter").addEventListener("change", function(e) {\n' +
461
+ ' activeLevel = e.target.value;\n' +
462
+ ' lastCursor = 0;\n' +
463
+ ' logView.innerHTML = "";\n' +
464
+ ' fetchLogs();\n' +
465
+ '});\n' +
466
+ '\n' +
467
+ 'var httpFilterTimeout = null;\n' +
468
+ 'document.getElementById("http-path-filter").addEventListener("input", function() {\n' +
469
+ ' clearTimeout(httpFilterTimeout);\n' +
470
+ ' httpFilterTimeout = setTimeout(function() {\n' +
471
+ ' httpCursor = 0;\n' +
472
+ ' document.getElementById("http-body").innerHTML = "";\n' +
473
+ ' fetchHttp();\n' +
474
+ ' }, 300);\n' +
475
+ '});\n' +
476
+ '\n' +
477
+ 'logView.addEventListener("scroll", function() {\n' +
478
+ ' var atBottom = logView.scrollHeight - logView.scrollTop - logView.clientHeight < 40;\n' +
479
+ ' autoScroll = atBottom;\n' +
480
+ ' jumpBtn.classList.toggle("visible", !atBottom);\n' +
481
+ '});\n' +
482
+ '\n' +
483
+ 'httpView.addEventListener("scroll", function() {\n' +
484
+ ' var atBottom = httpView.scrollHeight - httpView.scrollTop - httpView.clientHeight < 40;\n' +
485
+ ' httpAutoScroll = atBottom;\n' +
486
+ '});\n' +
487
+ '\n' +
488
+ 'function jumpToBottom() {\n' +
489
+ ' var el = isHttpTab ? httpView : logView;\n' +
490
+ ' el.scrollTop = el.scrollHeight;\n' +
491
+ ' autoScroll = true;\n' +
492
+ ' httpAutoScroll = true;\n' +
493
+ ' jumpBtn.classList.remove("visible");\n' +
494
+ '}\n' +
495
+ '\n' +
496
+ 'function fmtTime(ts) {\n' +
497
+ ' var d = new Date(ts);\n' +
498
+ ' return d.toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit" })\n' +
499
+ ' + "." + String(d.getMilliseconds()).padStart(3, "0");\n' +
500
+ '}\n' +
501
+ '\n' +
502
+ 'function escHtml(s) {\n' +
503
+ ' return s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");\n' +
504
+ '}\n' +
505
+ '\n' +
506
+ 'function fmtSize(bytes) {\n' +
507
+ ' if (bytes === 0) return "-";\n' +
508
+ ' if (bytes < 1024) return bytes + "B";\n' +
509
+ ' if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + "kb";\n' +
510
+ ' return (bytes / (1024 * 1024)).toFixed(1) + "MB";\n' +
511
+ '}\n' +
512
+ '\n' +
513
+ 'function renderEntries(entries) {\n' +
514
+ ' var frag = document.createDocumentFragment();\n' +
515
+ ' for (var i = 0; i < entries.length; i++) {\n' +
516
+ ' var e = entries[i];\n' +
517
+ ' var div = document.createElement("div");\n' +
518
+ ' div.className = "log-line level-" + e.level;\n' +
519
+ ' div.innerHTML = \'<span class="ts">\' + fmtTime(e.ts) + "</span> "\n' +
520
+ ' + \'<span class="src \' + e.source + \'">\' + e.source.padEnd(6) + "</span> "\n' +
521
+ ' + \'<span class="msg">\' + escHtml(e.msg) + "</span>";\n' +
522
+ ' frag.appendChild(div);\n' +
523
+ ' }\n' +
524
+ ' logView.appendChild(frag);\n' +
525
+ ' if (autoScroll) logView.scrollTop = logView.scrollHeight;\n' +
526
+ '}\n' +
527
+ '\n' +
528
+ 'function renderHttpEntries(entries) {\n' +
529
+ ' var tbody = document.getElementById("http-body");\n' +
530
+ ' var frag = document.createDocumentFragment();\n' +
531
+ ' for (var i = 0; i < entries.length; i++) {\n' +
532
+ ' var e = entries[i];\n' +
533
+ ' var tr = document.createElement("tr");\n' +
534
+ ' tr.className = "http-row";\n' +
535
+ ' tr.dataset.id = e.id;\n' +
536
+ ' var mc = e.method.toLowerCase();\n' +
537
+ ' var sc = "s" + String(e.status).charAt(0);\n' +
538
+ ' tr.innerHTML = "<td>" + fmtTime(e.ts) + "</td>"\n' +
539
+ ' + \'<td><span class="method \' + mc + \'">\' + e.method + "</span></td>"\n' +
540
+ ' + \'<td class="path">\' + escHtml(e.path) + "</td>"\n' +
541
+ ' + \'<td><span class="status \' + sc + \'">\' + e.status + "</span></td>"\n' +
542
+ ' + \'<td class="dur">\' + e.duration + "ms</td>"\n' +
543
+ ' + \'<td class="sz">\' + fmtSize(e.resSize) + "</td>";\n' +
544
+ ' tr.addEventListener("click", (function(entry) {\n' +
545
+ ' return function() { toggleHttpDetail(this, entry); };\n' +
546
+ ' })(e));\n' +
547
+ ' frag.appendChild(tr);\n' +
548
+ ' }\n' +
549
+ ' tbody.appendChild(frag);\n' +
550
+ ' if (httpAutoScroll) httpView.scrollTop = httpView.scrollHeight;\n' +
551
+ '}\n' +
552
+ '\n' +
553
+ 'function toggleHttpDetail(row, entry) {\n' +
554
+ ' var next = row.nextElementSibling;\n' +
555
+ ' if (next && next.classList.contains("http-detail")) {\n' +
556
+ ' next.classList.toggle("open");\n' +
557
+ ' return;\n' +
558
+ ' }\n' +
559
+ ' var detail = document.createElement("tr");\n' +
560
+ ' detail.className = "http-detail open";\n' +
561
+ ' var html = \'<td colspan="6">\';\n' +
562
+ ' html += \'<div class="hdr-section"><div class="hdr-title">request headers</div>\';\n' +
563
+ ' var rk = Object.keys(entry.reqHeaders || {}).sort();\n' +
564
+ ' for (var i = 0; i < rk.length; i++) {\n' +
565
+ ' html += \'<div class="hdr-line"><span class="hdr-key">\' + escHtml(rk[i]) + \'</span>: <span class="hdr-val">\' + escHtml(entry.reqHeaders[rk[i]]) + "</span></div>";\n' +
566
+ ' }\n' +
567
+ ' html += "</div>";\n' +
568
+ ' html += \'<div class="hdr-section"><div class="hdr-title">response headers</div>\';\n' +
569
+ ' var sk = Object.keys(entry.resHeaders || {}).sort();\n' +
570
+ ' for (var j = 0; j < sk.length; j++) {\n' +
571
+ ' html += \'<div class="hdr-line"><span class="hdr-key">\' + escHtml(sk[j]) + \'</span>: <span class="hdr-val">\' + escHtml(entry.resHeaders[sk[j]]) + "</span></div>";\n' +
572
+ ' }\n' +
573
+ ' html += "</div>";\n' +
574
+ ' if (entry.reqSize > 0) html += \'<div class="hdr-line"><span class="hdr-key">request body size</span>: <span class="hdr-val">\' + fmtSize(entry.reqSize) + "</span></div>";\n' +
575
+ ' html += "</td>";\n' +
576
+ ' detail.innerHTML = html;\n' +
577
+ ' row.parentNode.insertBefore(detail, row.nextSibling);\n' +
578
+ '}\n' +
579
+ '\n' +
580
+ 'function fetchLogs() {\n' +
581
+ ' var params = new URLSearchParams();\n' +
582
+ ' if (activeSource) params.set("source", activeSource);\n' +
583
+ ' if (activeLevel) params.set("level", activeLevel);\n' +
584
+ ' if (lastCursor) params.set("since", String(lastCursor));\n' +
585
+ ' fetch("/api/logs?" + params).then(function(res) { return res.json(); }).then(function(data) {\n' +
586
+ ' if (data.entries && data.entries.length > 0) renderEntries(data.entries);\n' +
587
+ ' if (data.cursor) lastCursor = data.cursor;\n' +
588
+ ' }).catch(function() {});\n' +
589
+ '}\n' +
590
+ '\n' +
591
+ 'function fetchHttp() {\n' +
592
+ ' var params = new URLSearchParams();\n' +
593
+ ' if (httpCursor) params.set("since", String(httpCursor));\n' +
594
+ ' var pathFilter = document.getElementById("http-path-filter").value;\n' +
595
+ ' if (pathFilter) params.set("path", pathFilter);\n' +
596
+ ' fetch("/api/http-log?" + params).then(function(res) { return res.json(); }).then(function(data) {\n' +
597
+ ' if (data.entries && data.entries.length > 0) renderHttpEntries(data.entries);\n' +
598
+ ' if (data.cursor) httpCursor = data.cursor;\n' +
599
+ ' }).catch(function() {});\n' +
600
+ '}\n' +
601
+ '\n' +
602
+ 'function loadEnv() {\n' +
603
+ ' fetch("/api/env").then(function(res) { return res.json(); }).then(function(data) {\n' +
604
+ ' var tbody = document.getElementById("env-body");\n' +
605
+ ' tbody.innerHTML = "";\n' +
606
+ ' var keys = Object.keys(data.env).sort();\n' +
607
+ ' for (var i = 0; i < keys.length; i++) {\n' +
608
+ ' var tr = document.createElement("tr");\n' +
609
+ ' tr.innerHTML = "<td>" + escHtml(keys[i]) + "</td><td>" + escHtml(String(data.env[keys[i]])) + "</td>";\n' +
610
+ ' tbody.appendChild(tr);\n' +
611
+ ' }\n' +
612
+ ' envLoaded = true;\n' +
613
+ ' }).catch(function() {});\n' +
614
+ '}\n' +
615
+ '\n' +
616
+ 'function fetchStatus() {\n' +
617
+ ' fetch("/api/status").then(function(res) { return res.json(); }).then(function(data) {\n' +
618
+ ' document.getElementById("pg-port").textContent = ":" + data.pgPort;\n' +
619
+ ' document.getElementById("zero-port").textContent = ":" + data.zeroPort;\n' +
620
+ ' var m = Math.floor(data.uptime / 60);\n' +
621
+ ' var s = data.uptime % 60;\n' +
622
+ ' document.getElementById("uptime-badge").textContent = "\\u23F1 " + (m > 0 ? m + "m " : "") + s + "s";\n' +
623
+ ' var zeroDisabled = data.skipZeroCache;\n' +
624
+ ' document.querySelectorAll("[data-zero-action]").forEach(function(btn) {\n' +
625
+ ' btn.disabled = zeroDisabled;\n' +
626
+ ' });\n' +
627
+ ' }).catch(function() {});\n' +
628
+ '}\n' +
629
+ '\n' +
630
+ 'function doAction(action, btn) {\n' +
631
+ ' if (action === "reset-zero") {\n' +
632
+ ' if (!confirm("Reset zero-cache? This deletes the replica and resyncs from scratch.")) return;\n' +
633
+ ' }\n' +
634
+ ' btn.disabled = true;\n' +
635
+ ' var origText = btn.textContent;\n' +
636
+ ' btn.textContent = "...";\n' +
637
+ ' fetch("/api/actions/" + action, { method: "POST" })\n' +
638
+ ' .then(function(res) { return res.json(); })\n' +
639
+ ' .then(function(data) {\n' +
640
+ ' showToast(data.message || "done", data.ok ? "success" : "error");\n' +
641
+ ' if (action === "clear-logs") {\n' +
642
+ ' logView.innerHTML = "";\n' +
643
+ ' lastCursor = 0;\n' +
644
+ ' }\n' +
645
+ ' if (action === "clear-http") {\n' +
646
+ ' document.getElementById("http-body").innerHTML = "";\n' +
647
+ ' httpCursor = 0;\n' +
648
+ ' }\n' +
649
+ ' })\n' +
650
+ ' .catch(function(err) {\n' +
651
+ ' showToast("failed: " + err.message, "error");\n' +
652
+ ' })\n' +
653
+ ' .finally(function() {\n' +
654
+ ' btn.disabled = false;\n' +
655
+ ' btn.textContent = origText;\n' +
656
+ ' });\n' +
657
+ '}\n' +
658
+ '\n' +
659
+ 'function showToast(msg, type) {\n' +
660
+ ' toastEl.textContent = msg;\n' +
661
+ ' toastEl.className = "toast " + type + " show";\n' +
662
+ ' setTimeout(function() { toastEl.className = "toast"; }, 2500);\n' +
663
+ '}\n' +
664
+ '\n' +
665
+ 'fetchLogs();\n' +
666
+ 'fetchStatus();\n' +
667
+ 'setInterval(function() {\n' +
668
+ ' if (document.hidden) return;\n' +
669
+ ' if (isHttpTab) fetchHttp();\n' +
670
+ ' else if (!isEnvTab) fetchLogs();\n' +
671
+ '}, 1000);\n' +
672
+ 'setInterval(function() { if (!document.hidden) fetchStatus(); }, 5000);\n' +
673
+ 'document.addEventListener("visibilitychange", function() {\n' +
674
+ ' if (document.hidden) return;\n' +
675
+ ' if (isHttpTab) fetchHttp();\n' +
676
+ ' else if (!isEnvTab) fetchLogs();\n' +
677
+ ' fetchStatus();\n' +
678
+ '});\n' +
679
+ '</script>\n' +
680
+ '</body>\n' +
681
+ '</html>';
682
+ }
683
+ //# sourceMappingURL=ui.js.map