express-performance-toolkit 1.0.0 → 2.0.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 (78) hide show
  1. package/README.md +119 -76
  2. package/dashboard-ui/README.md +73 -0
  3. package/dashboard-ui/eslint.config.js +23 -0
  4. package/dashboard-ui/index.html +13 -0
  5. package/dashboard-ui/package-lock.json +3382 -0
  6. package/dashboard-ui/package.json +32 -0
  7. package/dashboard-ui/src/App.css +184 -0
  8. package/dashboard-ui/src/App.tsx +182 -0
  9. package/dashboard-ui/src/components/BlockedModal.tsx +108 -0
  10. package/dashboard-ui/src/components/CachePanel.tsx +45 -0
  11. package/dashboard-ui/src/components/HealthCharts.tsx +142 -0
  12. package/dashboard-ui/src/components/InsightsPanel.tsx +49 -0
  13. package/dashboard-ui/src/components/KpiGrid.tsx +178 -0
  14. package/dashboard-ui/src/components/LiveLogs.tsx +76 -0
  15. package/dashboard-ui/src/components/Login.tsx +83 -0
  16. package/dashboard-ui/src/components/RoutesTable.tsx +110 -0
  17. package/dashboard-ui/src/hooks/useMetrics.ts +131 -0
  18. package/dashboard-ui/src/index.css +652 -0
  19. package/dashboard-ui/src/main.tsx +10 -0
  20. package/dashboard-ui/src/pages/InsightsPage.tsx +42 -0
  21. package/dashboard-ui/src/pages/LogsPage.tsx +26 -0
  22. package/dashboard-ui/src/pages/OverviewPage.tsx +32 -0
  23. package/dashboard-ui/src/pages/RoutesPage.tsx +26 -0
  24. package/dashboard-ui/src/utils/formatters.ts +27 -0
  25. package/dashboard-ui/tsconfig.app.json +28 -0
  26. package/dashboard-ui/tsconfig.json +7 -0
  27. package/dashboard-ui/tsconfig.node.json +26 -0
  28. package/dashboard-ui/vite.config.ts +12 -0
  29. package/dist/analyzer.d.ts +6 -0
  30. package/dist/analyzer.d.ts.map +1 -0
  31. package/dist/analyzer.js +70 -0
  32. package/dist/analyzer.js.map +1 -0
  33. package/dist/dashboard/dashboardRouter.d.ts +4 -4
  34. package/dist/dashboard/dashboardRouter.d.ts.map +1 -1
  35. package/dist/dashboard/dashboardRouter.js +67 -21
  36. package/dist/dashboard/dashboardRouter.js.map +1 -1
  37. package/dist/dashboard-ui/assets/index-CX-zE-Qy.css +1 -0
  38. package/dist/dashboard-ui/assets/index-Q9TGkd8n.js +41 -0
  39. package/dist/dashboard-ui/index.html +14 -0
  40. package/dist/index.d.ts +11 -10
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +35 -11
  43. package/dist/index.js.map +1 -1
  44. package/dist/logger.d.ts +3 -3
  45. package/dist/logger.d.ts.map +1 -1
  46. package/dist/logger.js +167 -9
  47. package/dist/logger.js.map +1 -1
  48. package/dist/queryHelper.d.ts.map +1 -1
  49. package/dist/queryHelper.js +1 -0
  50. package/dist/queryHelper.js.map +1 -1
  51. package/dist/rateLimit.d.ts +5 -0
  52. package/dist/rateLimit.d.ts.map +1 -0
  53. package/dist/rateLimit.js +67 -0
  54. package/dist/rateLimit.js.map +1 -0
  55. package/dist/store.d.ts +9 -2
  56. package/dist/store.d.ts.map +1 -1
  57. package/dist/store.js +147 -25
  58. package/dist/store.js.map +1 -1
  59. package/dist/types.d.ts +93 -0
  60. package/dist/types.d.ts.map +1 -1
  61. package/example/server.ts +68 -37
  62. package/package.json +9 -6
  63. package/src/analyzer.ts +78 -0
  64. package/src/dashboard/dashboardRouter.ts +88 -23
  65. package/src/index.ts +70 -30
  66. package/src/logger.ts +177 -13
  67. package/src/queryHelper.ts +2 -0
  68. package/src/rateLimit.ts +86 -0
  69. package/src/store.ts +136 -27
  70. package/src/types.ts +98 -0
  71. package/tests/analyzer.test.ts +108 -0
  72. package/tests/auth.test.ts +79 -0
  73. package/tests/bandwidth.test.ts +72 -0
  74. package/tests/integration.test.ts +51 -54
  75. package/tests/rateLimit.test.ts +57 -0
  76. package/tests/store.test.ts +37 -18
  77. package/tsconfig.json +1 -0
  78. package/src/dashboard/dashboard.html +0 -756
@@ -0,0 +1,652 @@
1
+ :root {
2
+ --bg-base: #09090b; /* zinc-950 */
3
+ --bg-surface: #18181b; /* zinc-900 */
4
+ --bg-surface-glass: rgba(24, 24, 27, 0.7);
5
+ --bg-hover: #27272a; /* zinc-800 */
6
+ --border: #27272a;
7
+
8
+ --text-100: #f4f4f5;
9
+ --text-200: #e4e4e7;
10
+ --text-300: #a1a1aa;
11
+ --text-400: #71717a;
12
+
13
+ --accent-cyan: #06b6d4;
14
+ --accent-emerald: #10b981;
15
+ --accent-indigo: #6366f1;
16
+ --accent-rose: #f43f5e;
17
+ --accent-amber: #f59e0b;
18
+
19
+ --grad-primary: linear-gradient(135deg, var(--accent-cyan), var(--accent-indigo));
20
+ --grad-success: linear-gradient(135deg, var(--accent-emerald), var(--accent-cyan));
21
+ --grad-warning: linear-gradient(135deg, var(--accent-amber), var(--accent-rose));
22
+ --grad-danger: linear-gradient(135deg, var(--accent-rose), #9f1239);
23
+
24
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.5);
25
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.5), 0 2px 4px -1px rgba(0, 0, 0, 0.4);
26
+ --shadow-glow: 0 0 20px rgba(6, 182, 212, 0.15);
27
+
28
+ --font-sans: "Inter", sans-serif;
29
+ --font-display: "Outfit", sans-serif;
30
+ --font-mono: "JetBrains Mono", monospace;
31
+ }
32
+
33
+ * {
34
+ margin: 0;
35
+ padding: 0;
36
+ box-sizing: border-box;
37
+ }
38
+
39
+ body {
40
+ font-family: var(--font-sans);
41
+ background-color: var(--bg-base);
42
+ color: var(--text-200);
43
+ min-height: 100vh;
44
+ display: flex;
45
+ flex-direction: column;
46
+ overflow-x: hidden;
47
+ background-image:
48
+ radial-gradient(circle at 15% 50%, rgba(99, 102, 241, 0.08), transparent 25%),
49
+ radial-gradient(circle at 85% 30%, rgba(6, 182, 212, 0.08), transparent 25%);
50
+ background-attachment: fixed;
51
+ }
52
+
53
+ /* ── Header ─────────────────────── */
54
+ .navbar {
55
+ position: sticky;
56
+ top: 0;
57
+ z-index: 50;
58
+ background: var(--bg-surface-glass);
59
+ backdrop-filter: blur(16px);
60
+ -webkit-backdrop-filter: blur(16px);
61
+ border-bottom: 1px solid var(--border);
62
+ padding: 0.75rem 2rem;
63
+ display: flex;
64
+ justify-content: space-between;
65
+ align-items: center;
66
+ transition: all 0.3s ease;
67
+ }
68
+
69
+ .brand {
70
+ display: flex;
71
+ align-items: center;
72
+ gap: 0.75rem;
73
+ }
74
+
75
+ .brand-icon {
76
+ width: 32px;
77
+ height: 32px;
78
+ background: var(--grad-primary);
79
+ border-radius: 8px;
80
+ display: grid;
81
+ place-items: center;
82
+ color: #fff;
83
+ font-size: 18px;
84
+ box-shadow: 0 0 15px rgba(99, 102, 241, 0.4);
85
+ animation: pulse-glow 3s infinite alternate;
86
+ }
87
+
88
+ .brand-title {
89
+ font-family: var(--font-display);
90
+ font-weight: 700;
91
+ font-size: 1.25rem;
92
+ color: var(--text-100);
93
+ letter-spacing: -0.02em;
94
+ }
95
+
96
+ .brand-title span {
97
+ background: var(--grad-primary);
98
+ -webkit-background-clip: text;
99
+ background-clip: text;
100
+ -webkit-text-fill-color: transparent;
101
+ }
102
+
103
+ .nav-actions {
104
+ display: flex;
105
+ align-items: center;
106
+ gap: 1.5rem;
107
+ }
108
+
109
+ .live-indicator {
110
+ display: flex;
111
+ align-items: center;
112
+ gap: 0.5rem;
113
+ font-size: 0.875rem;
114
+ color: var(--text-300);
115
+ font-weight: 500;
116
+ background: rgba(255, 255, 255, 0.03);
117
+ padding: 0.25rem 0.75rem;
118
+ border-radius: 99px;
119
+ border: 1px solid var(--border);
120
+ }
121
+
122
+ .pulse-dot {
123
+ width: 8px;
124
+ height: 8px;
125
+ background: var(--accent-emerald);
126
+ border-radius: 50%;
127
+ box-shadow: 0 0 8px var(--accent-emerald);
128
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
129
+ }
130
+
131
+ @keyframes pulse {
132
+ 0%, 100% { opacity: 1; }
133
+ 50% { opacity: 0.4; }
134
+ }
135
+ @keyframes pulse-glow {
136
+ 0% { box-shadow: 0 0 10px rgba(99, 102, 241, 0.2); }
137
+ 100% { box-shadow: 0 0 20px rgba(6, 182, 212, 0.5); }
138
+ }
139
+
140
+ /* ── Main Layout ────────────────── */
141
+ .dashboard-wrapper {
142
+ max-width: 1400px;
143
+ margin: 0 auto;
144
+ padding: 2rem;
145
+ width: 100%;
146
+ display: flex;
147
+ flex-direction: column;
148
+ gap: 1.5rem;
149
+ }
150
+
151
+ /* ── KPI Grid ───────────────────── */
152
+ .kpi-grid {
153
+ display: grid;
154
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
155
+ gap: 1.25rem;
156
+ }
157
+
158
+ .kpi-card {
159
+ background: var(--bg-surface-glass);
160
+ backdrop-filter: blur(12px);
161
+ border: 1px solid var(--border);
162
+ border-radius: 16px;
163
+ padding: 1.5rem;
164
+ display: flex;
165
+ flex-direction: column;
166
+ position: relative;
167
+ overflow: hidden;
168
+ transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1), border-color 0.25s, box-shadow 0.25s;
169
+ }
170
+
171
+ .kpi-card:hover {
172
+ transform: translateY(-4px);
173
+ border-color: rgba(99, 102, 241, 0.4);
174
+ box-shadow: var(--shadow-glow);
175
+ }
176
+
177
+ .kpi-card::before {
178
+ content: "";
179
+ position: absolute;
180
+ top: 0; left: 0; right: 0; height: 3px;
181
+ background: var(--bg-hover);
182
+ transition: background 0.3s;
183
+ }
184
+
185
+ .kpi-card.grad-1:hover::before { background: var(--grad-primary); }
186
+ .kpi-card.grad-2:hover::before { background: var(--grad-success); }
187
+ .kpi-card.grad-3:hover::before { background: var(--grad-warning); }
188
+ .kpi-card.grad-4:hover::before { background: var(--grad-danger); }
189
+ .kpi-card.grad-5:hover::before { background: linear-gradient(135deg, #10b981, #f59e0b); }
190
+
191
+ .kpi-title {
192
+ font-size: 0.8125rem; font-weight: 600; color: var(--text-400); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem;
193
+ }
194
+
195
+ .kpi-value {
196
+ font-family: var(--font-display); font-size: 2.5rem; font-weight: 800; line-height: 1.1; color: var(--text-100); margin-bottom: 0.25rem;
197
+ }
198
+
199
+ .kpi-subtext {
200
+ font-size: 0.875rem; color: var(--text-300); font-weight: 500;
201
+ }
202
+
203
+ .val-emerald { color: var(--accent-emerald) !important; text-shadow: 0 0 15px rgba(16, 185, 129, 0.3); }
204
+ .val-rose { color: var(--accent-rose) !important; text-shadow: 0 0 15px rgba(244, 63, 94, 0.3); }
205
+ .val-amber { color: var(--accent-amber) !important; text-shadow: 0 0 15px rgba(245, 158, 11, 0.3); }
206
+
207
+ /* ── Middle Section ─────────────── */
208
+ .middle-grid {
209
+ display: grid;
210
+ grid-template-columns: 3fr 2fr;
211
+ gap: 1.5rem;
212
+ }
213
+ @media (max-width: 1024px) {
214
+ .middle-grid { grid-template-columns: 1fr; }
215
+ }
216
+
217
+ .panel {
218
+ background: var(--bg-surface-glass);
219
+ backdrop-filter: blur(12px);
220
+ border: 1px solid var(--border);
221
+ border-radius: 16px;
222
+ display: flex; flex-direction: column;
223
+ box-shadow: var(--shadow-md);
224
+ overflow: hidden;
225
+ }
226
+
227
+ .panel-header {
228
+ padding: 1.25rem 1.5rem; border-bottom: 1px solid var(--border);
229
+ display: flex; justify-content: space-between; align-items: center;
230
+ background: rgba(255, 255, 255, 0.01);
231
+ }
232
+
233
+ .panel-title {
234
+ font-family: var(--font-display); font-size: 1.125rem; font-weight: 600; color: var(--text-100); display: flex; align-items: center; gap: 0.5rem;
235
+ }
236
+
237
+ .panel-body {
238
+ padding: 1.5rem; flex: 1; overflow-y: auto;
239
+ }
240
+
241
+ ::-webkit-scrollbar { width: 6px; height: 6px; }
242
+ ::-webkit-scrollbar-track { background: transparent; }
243
+ ::-webkit-scrollbar-thumb { background: var(--bg-hover); border-radius: 4px; }
244
+ ::-webkit-scrollbar-thumb:hover { background: var(--text-400); }
245
+
246
+ /* ── Status Bars ────────────────── */
247
+ .status-bars { display: flex; flex-direction: column; gap: 1rem; }
248
+ .status-row { display: flex; align-items: center; gap: 1rem; }
249
+ .status-code { font-family: var(--font-mono); font-weight: 600; font-size: 0.875rem; width: 40px; text-align: right; }
250
+ .status-code.s2xx { color: var(--accent-emerald); }
251
+ .status-code.s3xx { color: var(--accent-cyan); }
252
+ .status-code.s4xx { color: var(--accent-amber); }
253
+ .status-code.s5xx { color: var(--accent-rose); }
254
+
255
+ .track { flex: 1; height: 8px; background: rgba(255, 255, 255, 0.05); border-radius: 99px; overflow: hidden; }
256
+ .fill { height: 100%; border-radius: 99px; transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1); }
257
+ .fill.s2xx { background: var(--grad-success); box-shadow: 0 0 10px rgba(16, 185, 129, 0.4); }
258
+ .fill.s3xx { background: var(--grad-primary); }
259
+ .fill.s4xx { background: var(--grad-warning); }
260
+ .fill.s5xx { background: var(--grad-danger); box-shadow: 0 0 10px rgba(244, 63, 94, 0.4); }
261
+ .status-count { font-family: var(--font-mono); font-size: 0.875rem; color: var(--text-300); width: 40px; }
262
+
263
+ /* ── Cache Donut ────────────────── */
264
+ .cache-viz { display: flex; align-items: center; justify-content: center; gap: 2.5rem; height: 100%; }
265
+ .donut-wrap { position: relative; width: 140px; height: 140px; filter: drop-shadow(0 0 10px rgba(6, 182, 212, 0.2)); }
266
+ .donut-center { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; }
267
+ .donut-val { font-family: var(--font-display); font-size: 1.75rem; font-weight: 800; color: var(--text-100); }
268
+ .donut-lbl { font-size: 0.75rem; color: var(--text-400); text-transform: uppercase; font-weight: 600; letter-spacing: 0.05em; }
269
+
270
+ /* ── Tables ─────────────────────── */
271
+ .table-container { width: 100%; overflow-x: auto; }
272
+ table { width: 100%; border-collapse: separate; border-spacing: 0; }
273
+ th { text-align: left; padding: 0.75rem 1rem; font-size: 0.75rem; font-weight: 600; color: var(--text-400); text-transform: uppercase; letter-spacing: 0.05em; border-bottom: 1px solid var(--border); position: sticky; top: 0; background: var(--bg-surface); z-index: 10; }
274
+ td { padding: 0.875rem 1rem; font-size: 0.875rem; border-bottom: 1px solid rgba(255, 255, 255, 0.03); transition: background 0.2s; }
275
+ tr:hover td { background: rgba(255, 255, 255, 0.02); }
276
+ .routes-table td { font-family: var(--font-mono); }
277
+ .route-path { color: var(--text-100); font-weight: 500; }
278
+
279
+ /* ── Badges ─────────────────────── */
280
+ .badge { display: inline-flex; align-items: center; padding: 0.25rem 0.6rem; border-radius: 6px; font-size: 0.75rem; font-weight: 600; font-family: var(--font-mono); letter-spacing: 0.02em; }
281
+ .badge-GET { background: rgba(16, 185, 129, 0.1); color: var(--accent-emerald); border: 1px solid rgba(16, 185, 129, 0.2); }
282
+ .badge-POST { background: rgba(99, 102, 241, 0.1); color: var(--accent-indigo); border: 1px solid rgba(99, 102, 241, 0.2); }
283
+ .badge-PUT, .badge-PATCH { background: rgba(245, 158, 11, 0.1); color: var(--accent-amber); border: 1px solid rgba(245, 158, 11, 0.2); }
284
+ .badge-DELETE { background: rgba(244, 63, 94, 0.1); color: var(--accent-rose); border: 1px solid rgba(244, 63, 94, 0.2); }
285
+
286
+ .time-fast { color: var(--accent-emerald); }
287
+ .time-med { color: var(--accent-amber); }
288
+ .time-slow { color: var(--accent-rose); font-weight: 700; text-shadow: 0 0 10px rgba(244, 63, 94, 0.4); }
289
+
290
+ .cache-hit { background: rgba(6, 182, 212, 0.1); color: var(--accent-cyan); border: 1px solid rgba(6, 182, 212, 0.2); }
291
+ .cache-miss { background: rgba(113, 113, 122, 0.1); color: var(--text-400); border: 1px solid rgba(113, 113, 122, 0.2); }
292
+
293
+ .fire-icon { color: var(--accent-rose); animation: flicker 2s infinite alternate; }
294
+ @keyframes flicker {
295
+ 0%, 100% { opacity: 1; filter: drop-shadow(0 0 4px var(--accent-rose)); }
296
+ 50% { opacity: 0.7; filter: drop-shadow(0 0 2px var(--accent-rose)); }
297
+ }
298
+
299
+ /* ── Filters ────────────────────── */
300
+ .filters { display: flex; gap: 0.5rem; }
301
+ .filter-btn { background: transparent; border: 1px solid var(--border); color: var(--text-300); padding: 0.4rem 0.8rem; border-radius: 8px; font-size: 0.8125rem; font-weight: 500; cursor: pointer; transition: all 0.2s; }
302
+ .filter-btn:hover { background: var(--bg-hover); color: var(--text-100); }
303
+ .filter-btn.active { background: rgba(99, 102, 241, 0.1); color: var(--accent-indigo); border-color: var(--accent-indigo); box-shadow: 0 0 10px rgba(99, 102, 241, 0.2); }
304
+
305
+ .empty-state { text-align: center; padding: 3rem 1rem; color: var(--text-400); }
306
+ .empty-icon { font-size: 2rem; margin-bottom: 1rem; opacity: 0.5; }
307
+
308
+ /* Animations */
309
+ .animate-in { animation: slideUp 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards; opacity: 0; transform: translateY(10px); }
310
+ .delay-1 { animation-delay: 0.05s; }
311
+ .delay-2 { animation-delay: 0.1s; }
312
+ .delay-3 { animation-delay: 0.15s; }
313
+ .delay-4 { animation-delay: 0.2s; }
314
+ .delay-5 { animation-delay: 0.25s; }
315
+
316
+ @keyframes slideUp { to { opacity: 1; transform: translateY(0); } }
317
+
318
+ /* ── Modals ─────────────────────── */
319
+ .modal-overlay {
320
+ position: fixed;
321
+ inset: 0;
322
+ background: rgba(0, 0, 0, 0.8);
323
+ backdrop-filter: blur(8px);
324
+ z-index: 1000;
325
+ display: grid;
326
+ place-items: center;
327
+ padding: 2rem;
328
+ }
329
+
330
+ .modal-content {
331
+ background: var(--bg-surface);
332
+ border: 1px solid var(--border);
333
+ border-radius: 20px;
334
+ width: 100%;
335
+ max-width: 800px;
336
+ max-height: 80vh;
337
+ display: flex;
338
+ flex-direction: column;
339
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5), var(--shadow-glow);
340
+ }
341
+
342
+ .modal-header {
343
+ padding: 1.5rem;
344
+ border-bottom: 1px solid var(--border);
345
+ display: flex;
346
+ justify-content: space-between;
347
+ align-items: center;
348
+ }
349
+
350
+ .modal-title {
351
+ font-family: var(--font-display);
352
+ font-size: 1.25rem;
353
+ font-weight: 700;
354
+ color: var(--text-100);
355
+ display: flex;
356
+ align-items: center;
357
+ }
358
+
359
+ .modal-close {
360
+ background: transparent;
361
+ border: none;
362
+ color: var(--text-400);
363
+ cursor: pointer;
364
+ padding: 0.5rem;
365
+ border-radius: 8px;
366
+ transition: all 0.2s;
367
+ }
368
+
369
+ .modal-close:hover {
370
+ background: var(--bg-hover);
371
+ color: var(--text-100);
372
+ }
373
+
374
+ .modal-body {
375
+ padding: 1.5rem;
376
+ overflow-y: auto;
377
+ }
378
+
379
+ .modal-footer {
380
+ padding: 1.25rem 1.5rem;
381
+ border-top: 1px solid var(--border);
382
+ background: rgba(0, 0, 0, 0.2);
383
+ }
384
+
385
+ .blocked-list table {
386
+ width: 100%;
387
+ }
388
+
389
+ .method-badge {
390
+ padding: 2px 6px;
391
+ border-radius: 4px;
392
+ font-size: 0.7rem;
393
+ font-weight: 700;
394
+ text-transform: uppercase;
395
+ background: rgba(255, 255, 255, 0.1);
396
+ }
397
+
398
+ .method-badge.get { color: var(--accent-emerald); background: rgba(16, 185, 129, 0.1); }
399
+ .method-badge.post { color: var(--accent-indigo); background: rgba(99, 102, 241, 0.1); }
400
+ .method-badge.put { color: var(--accent-amber); background: rgba(245, 158, 11, 0.1); }
401
+ .method-badge.delete { color: var(--accent-rose); background: rgba(244, 63, 94, 0.1); }
402
+
403
+
404
+ /* Insights Panel */
405
+ .insights-list {
406
+ display: flex;
407
+ flex-direction: column;
408
+ }
409
+
410
+ .insight-item {
411
+ display: flex;
412
+ gap: 16px;
413
+ padding: 16px;
414
+ border-bottom: 1px solid var(--border);
415
+ }
416
+
417
+ .insight-item:last-child {
418
+ border-bottom: none;
419
+ }
420
+
421
+ .insight-icon {
422
+ flex-shrink: 0;
423
+ padding-top: 2px;
424
+ }
425
+
426
+ .insight-content {
427
+ flex-grow: 1;
428
+ }
429
+
430
+ .insight-title {
431
+ font-weight: 600;
432
+ font-size: 0.95rem;
433
+ margin-bottom: 4px;
434
+ }
435
+
436
+ .insight-message {
437
+ font-size: 0.85rem;
438
+ color: var(--text-300);
439
+ }
440
+
441
+ .insight-action {
442
+ margin-top: 10px;
443
+ display: inline-flex;
444
+ align-items: center;
445
+ background: rgba(255, 255, 255, 0.05);
446
+ padding: 4px 10px;
447
+ border-radius: 4px;
448
+ font-size: 0.8rem;
449
+ color: var(--text-200);
450
+ border: 1px solid var(--border);
451
+ }
452
+
453
+ .type-error .insight-title { color: var(--accent-rose); }
454
+ .type-warning .insight-title { color: var(--accent-amber); }
455
+ .type-info .insight-title { color: var(--accent-emerald); }
456
+
457
+ .type-error { background: rgba(244, 63, 94, 0.02); }
458
+ .type-warning { background: rgba(245, 158, 11, 0.02); }
459
+ .type-info { background: rgba(16, 185, 129, 0.02); }
460
+
461
+ /* Login Page Styles */
462
+ .login-wrapper {
463
+ display: flex;
464
+ align-items: center;
465
+ justify-content: center;
466
+ min-height: 100vh;
467
+ padding: 20px;
468
+ background: radial-gradient(circle at top left, rgba(16, 185, 129, 0.05) 0%, transparent 40%),
469
+ radial-gradient(circle at bottom right, rgba(244, 63, 94, 0.05) 0%, transparent 40%);
470
+ }
471
+
472
+ .login-card {
473
+ width: 100%;
474
+ max-width: 400px;
475
+ padding: 3rem;
476
+ text-align: center;
477
+ }
478
+
479
+ .login-header h1 {
480
+ font-family: var(--font-display);
481
+ font-size: 1.5rem;
482
+ margin-bottom: 0.5rem;
483
+ color: var(--text-100);
484
+ }
485
+
486
+ .login-header p {
487
+ color: var(--text-400);
488
+ font-size: 0.9rem;
489
+ margin-bottom: 2rem;
490
+ }
491
+
492
+ .login-form {
493
+ display: flex;
494
+ flex-direction: column;
495
+ gap: 1rem;
496
+ }
497
+
498
+ .input-group {
499
+ position: relative;
500
+ display: flex;
501
+ align-items: center;
502
+ }
503
+
504
+ .input-icon {
505
+ position: absolute;
506
+ left: 12px;
507
+ color: var(--text-400);
508
+ }
509
+
510
+ .login-form input {
511
+ width: 100%;
512
+ padding: 12px 12px 12px 40px;
513
+ background: rgba(255, 255, 255, 0.03);
514
+ border: 1px solid var(--border);
515
+ border-radius: 8px;
516
+ color: var(--text-100);
517
+ font-size: 0.9rem;
518
+ transition: all 0.2s;
519
+ }
520
+
521
+ .login-form input:focus {
522
+ outline: none;
523
+ border-color: var(--accent-emerald);
524
+ background: rgba(255, 255, 255, 0.05);
525
+ box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.1);
526
+ }
527
+
528
+ .login-button {
529
+ width: 100%;
530
+ padding: 12px;
531
+ margin-top: 1rem;
532
+ background: var(--accent-emerald);
533
+ color: white;
534
+ border: none;
535
+ border-radius: 8px;
536
+ font-weight: 600;
537
+ font-size: 0.95rem;
538
+ cursor: pointer;
539
+ transition: all 0.2s;
540
+ display: flex;
541
+ align-items: center;
542
+ justify-content: center;
543
+ }
544
+
545
+ .login-button:hover:not(:disabled) {
546
+ background: #10b981;
547
+ transform: translateY(-1px);
548
+ box-shadow: 0 4px 12px rgba(16, 185, 129, 0.2);
549
+ }
550
+
551
+ .login-button:active:not(:disabled) {
552
+ transform: translateY(0);
553
+ }
554
+
555
+ .login-button:disabled {
556
+ opacity: 0.6;
557
+ cursor: not-allowed;
558
+ }
559
+
560
+ .login-error {
561
+ background: rgba(244, 63, 94, 0.1);
562
+ color: var(--accent-rose);
563
+ padding: 10px;
564
+ border-radius: 6px;
565
+ font-size: 0.85rem;
566
+ border: 1px solid rgba(244, 63, 94, 0.2);
567
+ }
568
+
569
+ /* --- Refined Multi-Page Styles --- */
570
+ .nav-links {
571
+ display: flex;
572
+ gap: 0.5rem;
573
+ margin: 0 1.5rem;
574
+ }
575
+
576
+ .nav-link {
577
+ display: flex;
578
+ align-items: center;
579
+ gap: 0.6rem;
580
+ padding: 0.5rem 0.85rem;
581
+ border-radius: 8px;
582
+ color: var(--text-400);
583
+ background: transparent;
584
+ border: none;
585
+ font-size: 0.85rem;
586
+ font-weight: 500;
587
+ cursor: pointer;
588
+ transition: all 0.2s;
589
+ position: relative;
590
+ white-space: nowrap;
591
+ }
592
+
593
+ .nav-link:hover {
594
+ color: var(--text-100);
595
+ background: rgba(255, 255, 255, 0.05);
596
+ }
597
+
598
+ .nav-link.active {
599
+ color: var(--accent-emerald);
600
+ background: rgba(16, 185, 129, 0.1);
601
+ }
602
+
603
+ .nav-link .badge {
604
+ position: absolute;
605
+ top: 2px;
606
+ right: -4px;
607
+ background: var(--accent-rose);
608
+ color: white;
609
+ font-size: 0.65rem;
610
+ font-weight: 700;
611
+ padding: 0 5px;
612
+ border-radius: 10px;
613
+ min-width: 16px;
614
+ height: 16px;
615
+ line-height: 16px;
616
+ text-align: center;
617
+ box-shadow: 0 2px 4px rgba(244, 63, 94, 0.3);
618
+ }
619
+
620
+ .page-content {
621
+ display: flex;
622
+ flex-direction: column;
623
+ gap: 1.25rem;
624
+ }
625
+
626
+ .centered {
627
+ display: flex;
628
+ align-items: center;
629
+ justify-content: center;
630
+ min-height: calc(100vh - 160px);
631
+ }
632
+
633
+ .error-state {
634
+ text-align: center;
635
+ max-width: 500px;
636
+ padding: 3rem;
637
+ }
638
+
639
+ .uptime-mono, .lag-mono {
640
+ margin-left: 6px;
641
+ font-family: var(--font-mono);
642
+ font-weight: 600;
643
+ }
644
+
645
+ @media (max-width: 1024px) {
646
+ .nav-link span { display: none; }
647
+ .nav-links { gap: 0.25rem; margin: 0 0.5rem; }
648
+ }
649
+
650
+ @media (max-width: 768px) {
651
+ .nav-links { display: none; }
652
+ }
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import './index.css'
4
+ import App from './App.tsx'
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ )
@@ -0,0 +1,42 @@
1
+ import { InsightsPanel } from "../components/InsightsPanel";
2
+ import type { MetricsData } from "../hooks/useMetrics";
3
+ import { Bell } from "lucide-react";
4
+
5
+ interface InsightsPageProps {
6
+ data: MetricsData;
7
+ }
8
+
9
+ export function InsightsPage({ data }: InsightsPageProps) {
10
+ return (
11
+ <div className="page-content animate-in">
12
+ <div
13
+ className="panel-header"
14
+ style={{
15
+ marginBottom: "1rem",
16
+ justifyContent: "flex-start",
17
+ gap: "0.75rem",
18
+ }}
19
+ >
20
+ <div
21
+ className="brand-icon"
22
+ style={{
23
+ width: "32px",
24
+ height: "32px",
25
+ borderRadius: "8px",
26
+ display: "grid",
27
+ placeItems: "center",
28
+ }}
29
+ >
30
+ <Bell size={18} />
31
+ </div>
32
+ <div>
33
+ <h2 style={{ fontSize: "1.25rem" }}>Performance Insights</h2>
34
+ <p style={{ color: "var(--text-400)", fontSize: "0.9rem" }}>
35
+ AI-generated recommendations and health alerts.
36
+ </p>
37
+ </div>
38
+ </div>
39
+ <InsightsPanel insights={data.insights} />
40
+ </div>
41
+ );
42
+ }