@float.js/core 2.0.1

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.
@@ -0,0 +1,839 @@
1
+ // src/devtools/index.ts
2
+ var DevDashboardState = class {
3
+ routes = [];
4
+ builds = [];
5
+ requestLogs = [];
6
+ startTime = /* @__PURE__ */ new Date();
7
+ maxLogs = 100;
8
+ requestCount = 0;
9
+ totalResponseTime = 0;
10
+ errorCount = 0;
11
+ addRoute(route) {
12
+ const existing = this.routes.findIndex((r) => r.path === route.path);
13
+ if (existing >= 0) {
14
+ this.routes[existing] = route;
15
+ } else {
16
+ this.routes.push(route);
17
+ }
18
+ }
19
+ addBuild(build) {
20
+ this.builds.unshift(build);
21
+ if (this.builds.length > 20) {
22
+ this.builds.pop();
23
+ }
24
+ }
25
+ logRequest(log) {
26
+ this.requestLogs.unshift(log);
27
+ if (this.requestLogs.length > this.maxLogs) {
28
+ this.requestLogs.pop();
29
+ }
30
+ this.requestCount++;
31
+ this.totalResponseTime += log.duration;
32
+ if (log.status >= 400) {
33
+ this.errorCount++;
34
+ }
35
+ }
36
+ getMetrics() {
37
+ return {
38
+ requests: this.requestCount,
39
+ avgResponseTime: this.requestCount > 0 ? Math.round(this.totalResponseTime / this.requestCount) : 0,
40
+ errorRate: this.requestCount > 0 ? Math.round(this.errorCount / this.requestCount * 100) : 0,
41
+ activeConnections: 0,
42
+ // Updated by server
43
+ memoryUsage: process.memoryUsage(),
44
+ uptime: Date.now() - this.startTime.getTime()
45
+ };
46
+ }
47
+ clear() {
48
+ this.requestLogs = [];
49
+ this.requestCount = 0;
50
+ this.totalResponseTime = 0;
51
+ this.errorCount = 0;
52
+ }
53
+ };
54
+ var dashboardState = new DevDashboardState();
55
+ function createRequestLogger() {
56
+ return (req, res, next) => {
57
+ const startTime = Date.now();
58
+ const id = `req_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`;
59
+ const originalEnd = res.end.bind(res);
60
+ res.end = function(chunk, encoding, callback) {
61
+ const duration = Date.now() - startTime;
62
+ dashboardState.logRequest({
63
+ id,
64
+ method: req.method || "GET",
65
+ path: req.url || "/",
66
+ status: res.statusCode,
67
+ duration,
68
+ timestamp: /* @__PURE__ */ new Date()
69
+ });
70
+ return originalEnd(chunk, encoding, callback);
71
+ };
72
+ next();
73
+ };
74
+ }
75
+ function generateDashboardHTML(state) {
76
+ const metrics = state.getMetrics();
77
+ const memoryMB = Math.round(metrics.memoryUsage.heapUsed / 1024 / 1024);
78
+ const memoryTotal = Math.round(metrics.memoryUsage.heapTotal / 1024 / 1024);
79
+ const uptimeSeconds = Math.round(metrics.uptime / 1e3);
80
+ const uptimeFormatted = uptimeSeconds < 60 ? `${uptimeSeconds}s` : uptimeSeconds < 3600 ? `${Math.floor(uptimeSeconds / 60)}m ${uptimeSeconds % 60}s` : `${Math.floor(uptimeSeconds / 3600)}h ${Math.floor(uptimeSeconds % 3600 / 60)}m`;
81
+ const lastBuild = state.builds.length > 0 ? state.builds[0] : null;
82
+ const lastBuildTime = lastBuild ? new Date(lastBuild.timestamp).toLocaleTimeString() : "Never";
83
+ return `<!DOCTYPE html>
84
+ <html lang="en">
85
+ <head>
86
+ <meta charset="UTF-8">
87
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
88
+ <title>Float.js Dev Dashboard</title>
89
+ <link rel="preconnect" href="https://fonts.googleapis.com">
90
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
91
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
92
+ <style>
93
+ * { margin: 0; padding: 0; box-sizing: border-box; }
94
+
95
+ :root {
96
+ --bg-dark: #09090b;
97
+ --bg-card: #18181b;
98
+ --bg-card-hover: #1f1f23;
99
+ --bg-sidebar: #0f0f12;
100
+ --text-primary: #fafafa;
101
+ --text-secondary: #71717a;
102
+ --text-muted: #52525b;
103
+ --accent: #a855f7;
104
+ --accent-glow: rgba(168, 85, 247, 0.15);
105
+ --success: #22c55e;
106
+ --warning: #f59e0b;
107
+ --error: #ef4444;
108
+ --border: #27272a;
109
+ --border-subtle: #1f1f23;
110
+ }
111
+
112
+ body {
113
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
114
+ background: var(--bg-dark);
115
+ color: var(--text-primary);
116
+ min-height: 100vh;
117
+ display: flex;
118
+ }
119
+
120
+ /* Sidebar */
121
+ .sidebar {
122
+ width: 280px;
123
+ background: var(--bg-sidebar);
124
+ border-right: 1px solid var(--border);
125
+ display: flex;
126
+ flex-direction: column;
127
+ position: fixed;
128
+ height: 100vh;
129
+ z-index: 100;
130
+ }
131
+
132
+ .sidebar-header {
133
+ padding: 1.5rem;
134
+ border-bottom: 1px solid var(--border);
135
+ }
136
+
137
+ .logo {
138
+ display: flex;
139
+ align-items: center;
140
+ gap: 0.75rem;
141
+ }
142
+
143
+ .logo-icon {
144
+ width: 40px;
145
+ height: 40px;
146
+ background: linear-gradient(135deg, #a855f7 0%, #ec4899 50%, #f97316 100%);
147
+ border-radius: 12px;
148
+ display: flex;
149
+ align-items: center;
150
+ justify-content: center;
151
+ font-weight: 700;
152
+ font-size: 1.25rem;
153
+ box-shadow: 0 0 20px rgba(168, 85, 247, 0.3);
154
+ }
155
+
156
+ .logo-text {
157
+ display: flex;
158
+ flex-direction: column;
159
+ }
160
+
161
+ .logo-title {
162
+ font-weight: 700;
163
+ font-size: 1.125rem;
164
+ background: linear-gradient(135deg, #fff 0%, #a1a1aa 100%);
165
+ -webkit-background-clip: text;
166
+ -webkit-text-fill-color: transparent;
167
+ }
168
+
169
+ .logo-version {
170
+ font-size: 0.75rem;
171
+ color: var(--text-muted);
172
+ font-family: 'JetBrains Mono', monospace;
173
+ }
174
+
175
+ /* Status Cards */
176
+ .status-section {
177
+ padding: 1.5rem;
178
+ border-bottom: 1px solid var(--border);
179
+ }
180
+
181
+ .status-grid {
182
+ display: flex;
183
+ flex-direction: column;
184
+ gap: 0.75rem;
185
+ }
186
+
187
+ .status-item {
188
+ display: flex;
189
+ align-items: center;
190
+ justify-content: space-between;
191
+ padding: 0.75rem 1rem;
192
+ background: var(--bg-card);
193
+ border-radius: 10px;
194
+ border: 1px solid var(--border-subtle);
195
+ }
196
+
197
+ .status-label {
198
+ font-size: 0.8rem;
199
+ color: var(--text-secondary);
200
+ display: flex;
201
+ align-items: center;
202
+ gap: 0.5rem;
203
+ }
204
+
205
+ .status-value {
206
+ font-size: 0.875rem;
207
+ font-weight: 600;
208
+ font-family: 'JetBrains Mono', monospace;
209
+ }
210
+
211
+ .status-value.connected {
212
+ color: var(--success);
213
+ }
214
+
215
+ .status-value.active {
216
+ color: var(--accent);
217
+ }
218
+
219
+ .status-dot {
220
+ width: 8px;
221
+ height: 8px;
222
+ border-radius: 50%;
223
+ animation: pulse 2s infinite;
224
+ }
225
+
226
+ .status-dot.green { background: var(--success); box-shadow: 0 0 8px var(--success); }
227
+ .status-dot.purple { background: var(--accent); box-shadow: 0 0 8px var(--accent); }
228
+
229
+ @keyframes pulse {
230
+ 0%, 100% { opacity: 1; transform: scale(1); }
231
+ 50% { opacity: 0.7; transform: scale(0.95); }
232
+ }
233
+
234
+ /* Navigation */
235
+ .nav-section {
236
+ padding: 1rem;
237
+ flex: 1;
238
+ }
239
+
240
+ .nav-label {
241
+ font-size: 0.7rem;
242
+ text-transform: uppercase;
243
+ letter-spacing: 0.05em;
244
+ color: var(--text-muted);
245
+ padding: 0.5rem 1rem;
246
+ margin-bottom: 0.5rem;
247
+ }
248
+
249
+ .nav-item {
250
+ display: flex;
251
+ align-items: center;
252
+ gap: 0.75rem;
253
+ padding: 0.75rem 1rem;
254
+ border-radius: 10px;
255
+ color: var(--text-secondary);
256
+ text-decoration: none;
257
+ font-size: 0.875rem;
258
+ font-weight: 500;
259
+ transition: all 0.2s;
260
+ cursor: pointer;
261
+ margin-bottom: 0.25rem;
262
+ }
263
+
264
+ .nav-item:hover {
265
+ background: var(--bg-card);
266
+ color: var(--text-primary);
267
+ }
268
+
269
+ .nav-item.active {
270
+ background: var(--accent-glow);
271
+ color: var(--accent);
272
+ border: 1px solid rgba(168, 85, 247, 0.2);
273
+ }
274
+
275
+ .nav-icon {
276
+ font-size: 1.1rem;
277
+ }
278
+
279
+ .nav-badge {
280
+ margin-left: auto;
281
+ background: var(--bg-card);
282
+ padding: 0.125rem 0.5rem;
283
+ border-radius: 6px;
284
+ font-size: 0.7rem;
285
+ font-family: 'JetBrains Mono', monospace;
286
+ color: var(--text-muted);
287
+ }
288
+
289
+ /* Sidebar Footer */
290
+ .sidebar-footer {
291
+ padding: 1rem 1.5rem;
292
+ border-top: 1px solid var(--border);
293
+ }
294
+
295
+ .docs-link {
296
+ display: flex;
297
+ align-items: center;
298
+ justify-content: center;
299
+ gap: 0.5rem;
300
+ padding: 0.75rem;
301
+ background: linear-gradient(135deg, rgba(168, 85, 247, 0.1), rgba(236, 72, 153, 0.1));
302
+ border: 1px solid rgba(168, 85, 247, 0.2);
303
+ border-radius: 10px;
304
+ color: var(--accent);
305
+ text-decoration: none;
306
+ font-size: 0.875rem;
307
+ font-weight: 500;
308
+ transition: all 0.2s;
309
+ }
310
+
311
+ .docs-link:hover {
312
+ background: linear-gradient(135deg, rgba(168, 85, 247, 0.2), rgba(236, 72, 153, 0.2));
313
+ transform: translateY(-1px);
314
+ }
315
+
316
+ /* Main Content */
317
+ .main {
318
+ margin-left: 280px;
319
+ flex: 1;
320
+ padding: 2rem;
321
+ min-height: 100vh;
322
+ }
323
+
324
+ .page-header {
325
+ margin-bottom: 2rem;
326
+ }
327
+
328
+ .page-title {
329
+ font-size: 1.75rem;
330
+ font-weight: 700;
331
+ margin-bottom: 0.5rem;
332
+ }
333
+
334
+ .page-subtitle {
335
+ color: var(--text-secondary);
336
+ font-size: 0.9rem;
337
+ }
338
+
339
+ /* Metrics Grid */
340
+ .metrics-grid {
341
+ display: grid;
342
+ grid-template-columns: repeat(4, 1fr);
343
+ gap: 1rem;
344
+ margin-bottom: 2rem;
345
+ }
346
+
347
+ .metric-card {
348
+ background: var(--bg-card);
349
+ border: 1px solid var(--border-subtle);
350
+ border-radius: 16px;
351
+ padding: 1.5rem;
352
+ transition: all 0.3s;
353
+ position: relative;
354
+ overflow: hidden;
355
+ }
356
+
357
+ .metric-card::before {
358
+ content: '';
359
+ position: absolute;
360
+ top: 0;
361
+ left: 0;
362
+ right: 0;
363
+ height: 2px;
364
+ background: linear-gradient(90deg, var(--accent), #ec4899);
365
+ opacity: 0;
366
+ transition: opacity 0.3s;
367
+ }
368
+
369
+ .metric-card:hover {
370
+ border-color: var(--accent);
371
+ transform: translateY(-2px);
372
+ box-shadow: 0 8px 32px rgba(168, 85, 247, 0.1);
373
+ }
374
+
375
+ .metric-card:hover::before {
376
+ opacity: 1;
377
+ }
378
+
379
+ .metric-icon {
380
+ font-size: 1.5rem;
381
+ margin-bottom: 1rem;
382
+ }
383
+
384
+ .metric-value {
385
+ font-size: 2rem;
386
+ font-weight: 700;
387
+ font-family: 'JetBrains Mono', monospace;
388
+ margin-bottom: 0.25rem;
389
+ }
390
+
391
+ .metric-label {
392
+ font-size: 0.8rem;
393
+ color: var(--text-secondary);
394
+ text-transform: uppercase;
395
+ letter-spacing: 0.05em;
396
+ }
397
+
398
+ .metric-change {
399
+ position: absolute;
400
+ top: 1rem;
401
+ right: 1rem;
402
+ font-size: 0.75rem;
403
+ padding: 0.25rem 0.5rem;
404
+ border-radius: 6px;
405
+ font-weight: 500;
406
+ }
407
+
408
+ .metric-change.up { background: rgba(34, 197, 94, 0.1); color: var(--success); }
409
+ .metric-change.down { background: rgba(239, 68, 68, 0.1); color: var(--error); }
410
+
411
+ /* Sections */
412
+ .content-grid {
413
+ display: grid;
414
+ grid-template-columns: 1fr 1fr;
415
+ gap: 1.5rem;
416
+ }
417
+
418
+ .section {
419
+ background: var(--bg-card);
420
+ border: 1px solid var(--border-subtle);
421
+ border-radius: 16px;
422
+ overflow: hidden;
423
+ }
424
+
425
+ .section.full-width {
426
+ grid-column: 1 / -1;
427
+ }
428
+
429
+ .section-header {
430
+ padding: 1.25rem 1.5rem;
431
+ border-bottom: 1px solid var(--border);
432
+ display: flex;
433
+ align-items: center;
434
+ justify-content: space-between;
435
+ }
436
+
437
+ .section-title {
438
+ font-size: 1rem;
439
+ font-weight: 600;
440
+ display: flex;
441
+ align-items: center;
442
+ gap: 0.5rem;
443
+ }
444
+
445
+ .section-badge {
446
+ background: var(--bg-dark);
447
+ padding: 0.25rem 0.75rem;
448
+ border-radius: 8px;
449
+ font-size: 0.75rem;
450
+ color: var(--text-muted);
451
+ font-family: 'JetBrains Mono', monospace;
452
+ }
453
+
454
+ .section-content {
455
+ max-height: 400px;
456
+ overflow-y: auto;
457
+ }
458
+
459
+ /* Routes List */
460
+ .route-item {
461
+ display: flex;
462
+ align-items: center;
463
+ gap: 1rem;
464
+ padding: 1rem 1.5rem;
465
+ border-bottom: 1px solid var(--border-subtle);
466
+ transition: background 0.2s;
467
+ }
468
+
469
+ .route-item:last-child {
470
+ border-bottom: none;
471
+ }
472
+
473
+ .route-item:hover {
474
+ background: var(--bg-card-hover);
475
+ }
476
+
477
+ .route-type {
478
+ padding: 0.25rem 0.75rem;
479
+ border-radius: 6px;
480
+ font-size: 0.7rem;
481
+ font-weight: 600;
482
+ text-transform: uppercase;
483
+ letter-spacing: 0.05em;
484
+ min-width: 60px;
485
+ text-align: center;
486
+ }
487
+
488
+ .route-type.page { background: rgba(59, 130, 246, 0.15); color: #60a5fa; }
489
+ .route-type.api { background: rgba(34, 197, 94, 0.15); color: #4ade80; }
490
+ .route-type.layout { background: rgba(168, 85, 247, 0.15); color: #c084fc; }
491
+
492
+ .route-path {
493
+ font-family: 'JetBrains Mono', monospace;
494
+ font-size: 0.875rem;
495
+ flex: 1;
496
+ }
497
+
498
+ .route-file {
499
+ color: var(--text-muted);
500
+ font-size: 0.75rem;
501
+ font-family: 'JetBrains Mono', monospace;
502
+ }
503
+
504
+ /* Request Logs */
505
+ .log-item {
506
+ display: flex;
507
+ align-items: center;
508
+ gap: 1rem;
509
+ padding: 0.875rem 1.5rem;
510
+ border-bottom: 1px solid var(--border-subtle);
511
+ font-size: 0.875rem;
512
+ transition: background 0.2s;
513
+ }
514
+
515
+ .log-item:hover {
516
+ background: var(--bg-card-hover);
517
+ }
518
+
519
+ .log-method {
520
+ font-weight: 600;
521
+ padding: 0.25rem 0.5rem;
522
+ border-radius: 4px;
523
+ font-size: 0.7rem;
524
+ min-width: 50px;
525
+ text-align: center;
526
+ font-family: 'JetBrains Mono', monospace;
527
+ }
528
+
529
+ .log-method.GET { background: rgba(59, 130, 246, 0.15); color: #60a5fa; }
530
+ .log-method.POST { background: rgba(34, 197, 94, 0.15); color: #4ade80; }
531
+ .log-method.PUT { background: rgba(245, 158, 11, 0.15); color: #fbbf24; }
532
+ .log-method.DELETE { background: rgba(239, 68, 68, 0.15); color: #f87171; }
533
+
534
+ .log-status {
535
+ font-weight: 600;
536
+ font-family: 'JetBrains Mono', monospace;
537
+ font-size: 0.8rem;
538
+ }
539
+
540
+ .log-status.s2xx { color: var(--success); }
541
+ .log-status.s3xx { color: #60a5fa; }
542
+ .log-status.s4xx { color: var(--warning); }
543
+ .log-status.s5xx { color: var(--error); }
544
+
545
+ .log-path {
546
+ font-family: 'JetBrains Mono', monospace;
547
+ flex: 1;
548
+ color: var(--text-secondary);
549
+ }
550
+
551
+ .log-duration {
552
+ font-family: 'JetBrains Mono', monospace;
553
+ color: var(--text-muted);
554
+ font-size: 0.8rem;
555
+ }
556
+
557
+ .log-time {
558
+ color: var(--text-muted);
559
+ font-size: 0.75rem;
560
+ }
561
+
562
+ /* Empty State */
563
+ .empty-state {
564
+ text-align: center;
565
+ padding: 3rem;
566
+ color: var(--text-muted);
567
+ }
568
+
569
+ .empty-icon {
570
+ font-size: 2.5rem;
571
+ margin-bottom: 1rem;
572
+ opacity: 0.5;
573
+ }
574
+
575
+ /* Scrollbar */
576
+ ::-webkit-scrollbar { width: 6px; }
577
+ ::-webkit-scrollbar-track { background: transparent; }
578
+ ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
579
+ ::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
580
+
581
+ /* Responsive */
582
+ @media (max-width: 1200px) {
583
+ .metrics-grid { grid-template-columns: repeat(2, 1fr); }
584
+ .content-grid { grid-template-columns: 1fr; }
585
+ }
586
+ </style>
587
+ </head>
588
+ <body>
589
+ <!-- Sidebar -->
590
+ <aside class="sidebar">
591
+ <div class="sidebar-header">
592
+ <div class="logo">
593
+ <div class="logo-icon">\u26A1</div>
594
+ <div class="logo-text">
595
+ <span class="logo-title">Float.js</span>
596
+ <span class="logo-version">v2.0.1</span>
597
+ </div>
598
+ </div>
599
+ </div>
600
+
601
+ <div class="status-section">
602
+ <div class="status-grid">
603
+ <div class="status-item">
604
+ <span class="status-label">
605
+ <span class="status-dot green"></span>
606
+ Estado
607
+ </span>
608
+ <span class="status-value connected">Conectado</span>
609
+ </div>
610
+ <div class="status-item">
611
+ <span class="status-label">
612
+ <span class="status-dot purple"></span>
613
+ HMR
614
+ </span>
615
+ <span class="status-value active">Activo</span>
616
+ </div>
617
+ <div class="status-item">
618
+ <span class="status-label">\xDAltimo build</span>
619
+ <span class="status-value">${lastBuildTime}</span>
620
+ </div>
621
+ <div class="status-item">
622
+ <span class="status-label">Uptime</span>
623
+ <span class="status-value">${uptimeFormatted}</span>
624
+ </div>
625
+ </div>
626
+ </div>
627
+
628
+ <nav class="nav-section">
629
+ <div class="nav-label">Dashboard</div>
630
+ <div class="nav-item active">
631
+ <span class="nav-icon">\u{1F4CA}</span>
632
+ Overview
633
+ </div>
634
+ <div class="nav-item">
635
+ <span class="nav-icon">\u{1F6E4}\uFE0F</span>
636
+ Routes
637
+ <span class="nav-badge">${state.routes.length}</span>
638
+ </div>
639
+ <div class="nav-item">
640
+ <span class="nav-icon">\u{1F4DD}</span>
641
+ Logs
642
+ <span class="nav-badge">${state.requestLogs.length}</span>
643
+ </div>
644
+ <div class="nav-item">
645
+ <span class="nav-icon">\u2699\uFE0F</span>
646
+ Settings
647
+ </div>
648
+ </nav>
649
+
650
+ <div class="sidebar-footer">
651
+ <a href="https://floatjs.dev/docs" target="_blank" class="docs-link">
652
+ <span>\u{1F4DA}</span>
653
+ Documentation
654
+ </a>
655
+ </div>
656
+ </aside>
657
+
658
+ <!-- Main Content -->
659
+ <main class="main">
660
+ <div class="page-header">
661
+ <h1 class="page-title">Dev Dashboard</h1>
662
+ <p class="page-subtitle">Monitor your Float.js application in real-time</p>
663
+ </div>
664
+
665
+ <!-- Metrics -->
666
+ <div class="metrics-grid">
667
+ <div class="metric-card">
668
+ <div class="metric-icon">\u{1F4E1}</div>
669
+ <div class="metric-value">${metrics.requests}</div>
670
+ <div class="metric-label">Total Requests</div>
671
+ </div>
672
+ <div class="metric-card">
673
+ <div class="metric-icon">\u26A1</div>
674
+ <div class="metric-value">${metrics.avgResponseTime}ms</div>
675
+ <div class="metric-label">Avg Response</div>
676
+ </div>
677
+ <div class="metric-card">
678
+ <div class="metric-icon">\u{1F4BE}</div>
679
+ <div class="metric-value">${memoryMB}/${memoryTotal}</div>
680
+ <div class="metric-label">Memory (MB)</div>
681
+ </div>
682
+ <div class="metric-card">
683
+ <div class="metric-icon">${metrics.errorRate > 0 ? "\u26A0\uFE0F" : "\u2705"}</div>
684
+ <div class="metric-value">${metrics.errorRate}%</div>
685
+ <div class="metric-label">Error Rate</div>
686
+ </div>
687
+ </div>
688
+
689
+ <!-- Content Grid -->
690
+ <div class="content-grid">
691
+ <!-- Routes -->
692
+ <div class="section">
693
+ <div class="section-header">
694
+ <span class="section-title">\u{1F6E4}\uFE0F Routes</span>
695
+ <span class="section-badge">${state.routes.length} registered</span>
696
+ </div>
697
+ <div class="section-content">
698
+ ${state.routes.length === 0 ? `
699
+ <div class="empty-state">
700
+ <div class="empty-icon">\u{1F6E4}\uFE0F</div>
701
+ <p>No routes registered</p>
702
+ </div>
703
+ ` : state.routes.map((route) => `
704
+ <div class="route-item">
705
+ <span class="route-type ${route.type}">${route.type}</span>
706
+ <span class="route-path">${route.path}</span>
707
+ <span class="route-file">${route.file.split("/").pop()}</span>
708
+ </div>
709
+ `).join("")}
710
+ </div>
711
+ </div>
712
+
713
+ <!-- Request Logs -->
714
+ <div class="section">
715
+ <div class="section-header">
716
+ <span class="section-title">\u{1F4DD} Request Logs</span>
717
+ <span class="section-badge">Live</span>
718
+ </div>
719
+ <div class="section-content">
720
+ ${state.requestLogs.length === 0 ? `
721
+ <div class="empty-state">
722
+ <div class="empty-icon">\u{1F4DD}</div>
723
+ <p>No requests yet</p>
724
+ </div>
725
+ ` : state.requestLogs.slice(0, 20).map((log) => {
726
+ const statusClass = log.status < 300 ? "s2xx" : log.status < 400 ? "s3xx" : log.status < 500 ? "s4xx" : "s5xx";
727
+ return `
728
+ <div class="log-item">
729
+ <span class="log-method ${log.method}">${log.method}</span>
730
+ <span class="log-status ${statusClass}">${log.status}</span>
731
+ <span class="log-path">${log.path}</span>
732
+ <span class="log-duration">${log.duration}ms</span>
733
+ </div>
734
+ `;
735
+ }).join("")}
736
+ </div>
737
+ </div>
738
+ </div>
739
+ </main>
740
+
741
+ <script>
742
+ // Auto-refresh every 3 seconds
743
+ setTimeout(() => location.reload(), 3000);
744
+ </script>
745
+ </body>
746
+ </html>`;
747
+ }
748
+ function generateAPIResponse(state) {
749
+ return JSON.stringify({
750
+ metrics: state.getMetrics(),
751
+ routes: state.routes,
752
+ builds: state.builds.slice(0, 10),
753
+ requestLogs: state.requestLogs.slice(0, 50)
754
+ });
755
+ }
756
+ function createDevDashboard(options = {}) {
757
+ const {
758
+ enabled = process.env.NODE_ENV !== "production",
759
+ path = "/__float",
760
+ auth
761
+ } = options;
762
+ if (!enabled) {
763
+ return (_req, _res, next) => next();
764
+ }
765
+ return (req, res, next) => {
766
+ const url = req.url || "";
767
+ if (!url.startsWith(path)) {
768
+ return next();
769
+ }
770
+ if (auth) {
771
+ const authHeader = req.headers.authorization;
772
+ if (!authHeader || !authHeader.startsWith("Basic ")) {
773
+ res.setHeader("WWW-Authenticate", 'Basic realm="Float.js Dev Dashboard"');
774
+ res.statusCode = 401;
775
+ res.end("Unauthorized");
776
+ return;
777
+ }
778
+ const credentials = Buffer.from(authHeader.slice(6), "base64").toString();
779
+ const [username, password] = credentials.split(":");
780
+ if (username !== auth.username || password !== auth.password) {
781
+ res.statusCode = 401;
782
+ res.end("Invalid credentials");
783
+ return;
784
+ }
785
+ }
786
+ const subPath = url.slice(path.length);
787
+ if (subPath === "" || subPath === "/") {
788
+ res.setHeader("Content-Type", "text/html");
789
+ res.end(generateDashboardHTML(dashboardState));
790
+ return;
791
+ }
792
+ if (subPath === "/api" || subPath === "/api/") {
793
+ res.setHeader("Content-Type", "application/json");
794
+ res.end(generateAPIResponse(dashboardState));
795
+ return;
796
+ }
797
+ if (subPath === "/api/routes") {
798
+ res.setHeader("Content-Type", "application/json");
799
+ res.end(JSON.stringify(dashboardState.routes));
800
+ return;
801
+ }
802
+ if (subPath === "/api/metrics") {
803
+ res.setHeader("Content-Type", "application/json");
804
+ res.end(JSON.stringify(dashboardState.getMetrics()));
805
+ return;
806
+ }
807
+ if (subPath === "/api/logs") {
808
+ res.setHeader("Content-Type", "application/json");
809
+ res.end(JSON.stringify(dashboardState.requestLogs.slice(0, 100)));
810
+ return;
811
+ }
812
+ if (subPath === "/api/clear" && req.method === "POST") {
813
+ dashboardState.clear();
814
+ res.setHeader("Content-Type", "application/json");
815
+ res.end(JSON.stringify({ success: true }));
816
+ return;
817
+ }
818
+ res.statusCode = 404;
819
+ res.end("Not found");
820
+ };
821
+ }
822
+ var devtools = {
823
+ dashboard: createDevDashboard,
824
+ logger: createRequestLogger,
825
+ state: dashboardState,
826
+ // Helpers for manual logging
827
+ logRoute: (route) => dashboardState.addRoute(route),
828
+ logBuild: (build) => dashboardState.addBuild(build),
829
+ logRequest: (log) => dashboardState.logRequest(log),
830
+ getMetrics: () => dashboardState.getMetrics(),
831
+ clear: () => dashboardState.clear()
832
+ };
833
+ export {
834
+ createDevDashboard,
835
+ createRequestLogger,
836
+ dashboardState,
837
+ devtools
838
+ };
839
+ //# sourceMappingURL=index.js.map