hono-status-monitor 1.0.1 → 1.0.3
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.
- package/README.md +59 -2
- package/dist/dashboard.d.ts +13 -0
- package/dist/dashboard.d.ts.map +1 -1
- package/dist/dashboard.js +391 -0
- package/dist/dashboard.js.map +1 -1
- package/dist/index-edge.d.ts +58 -0
- package/dist/index-edge.d.ts.map +1 -0
- package/dist/index-edge.js +94 -0
- package/dist/index-edge.js.map +1 -0
- package/dist/index.d.ts +51 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +121 -13
- package/dist/index.js.map +1 -1
- package/dist/monitor-edge.d.ts +21 -0
- package/dist/monitor-edge.d.ts.map +1 -0
- package/dist/monitor-edge.js +376 -0
- package/dist/monitor-edge.js.map +1 -0
- package/dist/platform.d.ts +33 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +80 -0
- package/dist/platform.js.map +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +24 -6
package/README.md
CHANGED
|
@@ -366,11 +366,67 @@ app.use('/status/*', basicAuth({
|
|
|
366
366
|
app.route('/status', monitor.routes);
|
|
367
367
|
```
|
|
368
368
|
|
|
369
|
+
## ☁️ Cloudflare Workers / Edge Support
|
|
370
|
+
|
|
371
|
+
This package provides a separate edge entry point that has **zero Node.js dependencies**.
|
|
372
|
+
|
|
373
|
+
### Available Metrics in Edge Environments
|
|
374
|
+
|
|
375
|
+
| Metric | Available | Notes |
|
|
376
|
+
|--------|-----------|-------|
|
|
377
|
+
| Request count & RPS | ✅ | Fully supported |
|
|
378
|
+
| Response time percentiles | ✅ | P50, P95, P99, Avg |
|
|
379
|
+
| Status code breakdown | ✅ | 2xx, 3xx, 4xx, 5xx |
|
|
380
|
+
| Route analytics | ✅ | Top routes, slowest routes |
|
|
381
|
+
| Error tracking | ✅ | Recent errors with timestamps |
|
|
382
|
+
| CPU / Memory / Heap | ❌ | Not available in Workers |
|
|
383
|
+
| Event loop lag | ❌ | Not available in Workers |
|
|
384
|
+
| Load average | ❌ | Not available in Workers |
|
|
385
|
+
| WebSocket real-time | ❌ | Uses polling (5s interval) |
|
|
386
|
+
|
|
387
|
+
### Usage in Cloudflare Workers
|
|
388
|
+
|
|
389
|
+
> **Important:** Use the `/edge` import path for Cloudflare Workers!
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
import { Hono } from 'hono';
|
|
393
|
+
// Use the edge-specific import (no Node.js dependencies)
|
|
394
|
+
import { statusMonitor } from 'hono-status-monitor/edge';
|
|
395
|
+
|
|
396
|
+
const app = new Hono();
|
|
397
|
+
|
|
398
|
+
// Create status monitor
|
|
399
|
+
const monitor = statusMonitor();
|
|
400
|
+
|
|
401
|
+
// Add middleware to track requests
|
|
402
|
+
app.use('*', monitor.middleware);
|
|
403
|
+
|
|
404
|
+
// Mount status dashboard
|
|
405
|
+
app.route('/status', monitor.routes);
|
|
406
|
+
|
|
407
|
+
// Your routes
|
|
408
|
+
app.get('/', (c) => c.text('Hello from Cloudflare Workers!'));
|
|
409
|
+
|
|
410
|
+
export default app;
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
> **Note:** In edge environments, the dashboard uses HTTP polling (every 5 seconds) instead of WebSocket for updates. The dashboard will display an "Edge Mode" indicator.
|
|
414
|
+
|
|
415
|
+
### Force Edge Mode in Node.js
|
|
416
|
+
|
|
417
|
+
You can also use the edge-compatible monitor in Node.js if you don't need system metrics:
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
import { statusMonitor } from 'hono-status-monitor/edge';
|
|
421
|
+
|
|
422
|
+
const monitor = statusMonitor();
|
|
423
|
+
```
|
|
424
|
+
|
|
369
425
|
## 📋 Requirements
|
|
370
426
|
|
|
371
|
-
- Node.js >= 18.0.0
|
|
427
|
+
- Node.js >= 18.0.0 (for Node.js mode)
|
|
372
428
|
- Hono.js >= 4.0.0
|
|
373
|
-
- @hono/node-server >= 1.0.0
|
|
429
|
+
- @hono/node-server >= 1.0.0 (for Node.js mode only)
|
|
374
430
|
|
|
375
431
|
## 🤝 Contributing
|
|
376
432
|
|
|
@@ -383,3 +439,4 @@ MIT © [Vinit Kumar Goel](https://github.com/vinitkumargoel)
|
|
|
383
439
|
---
|
|
384
440
|
|
|
385
441
|
Made with ❤️ for the Hono.js community
|
|
442
|
+
|
package/dist/dashboard.d.ts
CHANGED
|
@@ -3,4 +3,17 @@ import type { DashboardProps } from './types.js';
|
|
|
3
3
|
* Generate the status dashboard HTML
|
|
4
4
|
*/
|
|
5
5
|
export declare function generateDashboard({ hostname, uptime, socketPath, title }: DashboardProps): string;
|
|
6
|
+
/**
|
|
7
|
+
* Edge Dashboard Props (no socketPath)
|
|
8
|
+
*/
|
|
9
|
+
export interface EdgeDashboardProps {
|
|
10
|
+
hostname: string;
|
|
11
|
+
uptime: string;
|
|
12
|
+
title: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Generate the edge-compatible status dashboard HTML
|
|
16
|
+
* Uses polling instead of WebSocket, only shows available metrics
|
|
17
|
+
*/
|
|
18
|
+
export declare function generateEdgeDashboard({ hostname, uptime, title }: EdgeDashboardProps): string;
|
|
6
19
|
//# sourceMappingURL=dashboard.d.ts.map
|
package/dist/dashboard.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../src/dashboard.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,EAAE,cAAc,GAAG,MAAM,CA6bjG"}
|
|
1
|
+
{"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../src/dashboard.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,EAAE,cAAc,GAAG,MAAM,CA6bjG;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,kBAAkB,GAAG,MAAM,CAkY7F"}
|
package/dist/dashboard.js
CHANGED
|
@@ -451,4 +451,395 @@ export function generateDashboard({ hostname, uptime, socketPath, title }) {
|
|
|
451
451
|
</body>
|
|
452
452
|
</html>`;
|
|
453
453
|
}
|
|
454
|
+
/**
|
|
455
|
+
* Generate the edge-compatible status dashboard HTML
|
|
456
|
+
* Uses polling instead of WebSocket, only shows available metrics
|
|
457
|
+
*/
|
|
458
|
+
export function generateEdgeDashboard({ hostname, uptime, title }) {
|
|
459
|
+
return `<!DOCTYPE html>
|
|
460
|
+
<html lang="en">
|
|
461
|
+
<head>
|
|
462
|
+
<meta charset="UTF-8">
|
|
463
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
464
|
+
<title>${title}</title>
|
|
465
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
|
|
466
|
+
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
|
|
467
|
+
<style>
|
|
468
|
+
:root {
|
|
469
|
+
--bg: #fff;
|
|
470
|
+
--bg-secondary: #f8f9fa;
|
|
471
|
+
--bg-card: #fff;
|
|
472
|
+
--border: #e5e5e5;
|
|
473
|
+
--text: #111;
|
|
474
|
+
--text-secondary: #666;
|
|
475
|
+
--text-muted: #999;
|
|
476
|
+
--accent: #3b82f6;
|
|
477
|
+
--success: #10b981;
|
|
478
|
+
--warning: #f59e0b;
|
|
479
|
+
--danger: #ef4444;
|
|
480
|
+
--edge: #f97316;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.dark {
|
|
484
|
+
--bg: #0f0f0f;
|
|
485
|
+
--bg-secondary: #1a1a1a;
|
|
486
|
+
--bg-card: #1a1a1a;
|
|
487
|
+
--border: #2a2a2a;
|
|
488
|
+
--text: #fafafa;
|
|
489
|
+
--text-secondary: #a0a0a0;
|
|
490
|
+
--text-muted: #666;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
494
|
+
|
|
495
|
+
body {
|
|
496
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
|
497
|
+
background: var(--bg-secondary);
|
|
498
|
+
color: var(--text);
|
|
499
|
+
min-height: 100vh;
|
|
500
|
+
transition: all 0.3s;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
.container {
|
|
504
|
+
max-width: 800px;
|
|
505
|
+
margin: 0 auto;
|
|
506
|
+
padding: 20px;
|
|
507
|
+
background: var(--bg);
|
|
508
|
+
min-height: 100vh;
|
|
509
|
+
border-left: 1px solid var(--border);
|
|
510
|
+
border-right: 1px solid var(--border);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
header {
|
|
514
|
+
display: flex;
|
|
515
|
+
justify-content: space-between;
|
|
516
|
+
align-items: center;
|
|
517
|
+
margin-bottom: 16px;
|
|
518
|
+
padding-bottom: 16px;
|
|
519
|
+
border-bottom: 1px solid var(--border);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.title-section h1 { font-size: 18px; font-weight: 600; }
|
|
523
|
+
.title-section .subtitle { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
|
|
524
|
+
|
|
525
|
+
.header-controls { display: flex; align-items: center; gap: 12px; }
|
|
526
|
+
|
|
527
|
+
.theme-toggle {
|
|
528
|
+
width: 36px; height: 36px;
|
|
529
|
+
border: 1px solid var(--border);
|
|
530
|
+
background: var(--bg-card);
|
|
531
|
+
border-radius: 8px;
|
|
532
|
+
cursor: pointer;
|
|
533
|
+
display: flex;
|
|
534
|
+
align-items: center;
|
|
535
|
+
justify-content: center;
|
|
536
|
+
font-size: 16px;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.status-badge {
|
|
540
|
+
display: flex; align-items: center; gap: 6px;
|
|
541
|
+
padding: 6px 12px; border-radius: 16px;
|
|
542
|
+
font-size: 11px; font-weight: 500;
|
|
543
|
+
}
|
|
544
|
+
.status-badge.edge { background: #fff7ed; color: #c2410c; }
|
|
545
|
+
.dark .status-badge.edge { background: #431407; color: #fdba74; }
|
|
546
|
+
|
|
547
|
+
.edge-notice {
|
|
548
|
+
background: linear-gradient(135deg, #fff7ed, #fef3c7);
|
|
549
|
+
border: 1px solid #fed7aa;
|
|
550
|
+
border-radius: 8px;
|
|
551
|
+
padding: 12px;
|
|
552
|
+
margin-bottom: 16px;
|
|
553
|
+
font-size: 12px;
|
|
554
|
+
color: #9a3412;
|
|
555
|
+
}
|
|
556
|
+
.dark .edge-notice {
|
|
557
|
+
background: linear-gradient(135deg, #431407, #422006);
|
|
558
|
+
border-color: #c2410c;
|
|
559
|
+
color: #fdba74;
|
|
560
|
+
}
|
|
561
|
+
.edge-notice strong { display: block; margin-bottom: 4px; }
|
|
562
|
+
|
|
563
|
+
/* Stats Bar */
|
|
564
|
+
.stats-bar {
|
|
565
|
+
display: grid;
|
|
566
|
+
grid-template-columns: repeat(4, 1fr);
|
|
567
|
+
gap: 8px;
|
|
568
|
+
margin-bottom: 16px;
|
|
569
|
+
}
|
|
570
|
+
.stat-box {
|
|
571
|
+
padding: 12px;
|
|
572
|
+
background: var(--bg-secondary);
|
|
573
|
+
border-radius: 8px;
|
|
574
|
+
text-align: center;
|
|
575
|
+
}
|
|
576
|
+
.stat-box .label { font-size: 10px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; }
|
|
577
|
+
.stat-box .value { font-size: 18px; font-weight: 600; margin-top: 4px; }
|
|
578
|
+
|
|
579
|
+
/* Percentiles */
|
|
580
|
+
.percentiles {
|
|
581
|
+
display: grid;
|
|
582
|
+
grid-template-columns: repeat(4, 1fr);
|
|
583
|
+
gap: 8px;
|
|
584
|
+
margin-bottom: 16px;
|
|
585
|
+
padding: 12px;
|
|
586
|
+
background: var(--bg-secondary);
|
|
587
|
+
border-radius: 8px;
|
|
588
|
+
}
|
|
589
|
+
.percentile-item { text-align: center; }
|
|
590
|
+
.percentile-item .label { font-size: 10px; color: var(--text-muted); }
|
|
591
|
+
.percentile-item .value { font-size: 16px; font-weight: 600; color: var(--accent); }
|
|
592
|
+
|
|
593
|
+
/* Metric Rows */
|
|
594
|
+
.metric-row {
|
|
595
|
+
display: flex; align-items: center;
|
|
596
|
+
padding: 12px 0; border-bottom: 1px solid var(--border);
|
|
597
|
+
}
|
|
598
|
+
.metric-info { width: 140px; flex-shrink: 0; }
|
|
599
|
+
.metric-label { font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.3px; }
|
|
600
|
+
.metric-value { font-size: 28px; font-weight: 300; line-height: 1.1; }
|
|
601
|
+
.metric-unit { font-size: 14px; color: var(--text-muted); }
|
|
602
|
+
.metric-alert { color: var(--danger) !important; }
|
|
603
|
+
.chart-container { flex: 1; height: 50px; margin-left: 16px; }
|
|
604
|
+
|
|
605
|
+
/* Section Titles */
|
|
606
|
+
.section-title {
|
|
607
|
+
font-size: 11px; font-weight: 600; color: var(--text-muted);
|
|
608
|
+
text-transform: uppercase; letter-spacing: 0.5px;
|
|
609
|
+
margin: 20px 0 12px; padding-top: 12px;
|
|
610
|
+
border-top: 1px solid var(--border);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/* Route Tables */
|
|
614
|
+
.routes-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
|
|
615
|
+
.route-section { background: var(--bg-secondary); border-radius: 8px; padding: 12px; }
|
|
616
|
+
.route-section h3 { font-size: 11px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; margin-bottom: 8px; }
|
|
617
|
+
.route-item { display: flex; justify-content: space-between; font-size: 12px; padding: 6px 0; border-bottom: 1px solid var(--border); }
|
|
618
|
+
.route-item:last-child { border-bottom: none; }
|
|
619
|
+
.route-path { font-family: monospace; color: var(--text-secondary); max-width: 150px; overflow: hidden; text-overflow: ellipsis; }
|
|
620
|
+
.route-stat { font-weight: 500; }
|
|
621
|
+
.route-stat.slow { color: var(--warning); }
|
|
622
|
+
.route-stat.error { color: var(--danger); }
|
|
623
|
+
|
|
624
|
+
/* Status Codes */
|
|
625
|
+
.status-codes { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; }
|
|
626
|
+
.status-code-box { text-align: center; padding: 10px; background: var(--bg-secondary); border-radius: 6px; }
|
|
627
|
+
.status-code-box .code { font-size: 10px; color: var(--text-muted); }
|
|
628
|
+
.status-code-box .count { font-size: 18px; font-weight: 600; margin-top: 2px; }
|
|
629
|
+
.s2xx { color: var(--success); }
|
|
630
|
+
.s3xx { color: var(--accent); }
|
|
631
|
+
.s4xx { color: var(--warning); }
|
|
632
|
+
.s5xx { color: var(--danger); }
|
|
633
|
+
|
|
634
|
+
/* Errors Panel */
|
|
635
|
+
.errors-panel { background: var(--bg-secondary); border-radius: 8px; padding: 12px; }
|
|
636
|
+
.error-item { font-size: 12px; padding: 8px; background: var(--bg-card); border-radius: 4px; margin-top: 6px; border-left: 3px solid var(--danger); }
|
|
637
|
+
.error-item:first-of-type { margin-top: 0; }
|
|
638
|
+
.error-time { font-size: 10px; color: var(--text-muted); }
|
|
639
|
+
.error-path { font-family: monospace; color: var(--danger); }
|
|
640
|
+
|
|
641
|
+
@media (max-width: 640px) {
|
|
642
|
+
.container { padding: 12px; }
|
|
643
|
+
.stats-bar, .percentiles { grid-template-columns: repeat(2, 1fr); }
|
|
644
|
+
.routes-grid { grid-template-columns: 1fr; }
|
|
645
|
+
.status-codes { grid-template-columns: repeat(3, 1fr); }
|
|
646
|
+
}
|
|
647
|
+
</style>
|
|
648
|
+
</head>
|
|
649
|
+
<body>
|
|
650
|
+
<div class="container">
|
|
651
|
+
<header>
|
|
652
|
+
<div class="title-section">
|
|
653
|
+
<h1>${title}</h1>
|
|
654
|
+
<div class="subtitle">${hostname}</div>
|
|
655
|
+
</div>
|
|
656
|
+
<div class="header-controls">
|
|
657
|
+
<button class="theme-toggle" onclick="toggleTheme()" title="Toggle dark mode">🌓</button>
|
|
658
|
+
<div class="status-badge edge" id="connBadge">
|
|
659
|
+
<span>☁️</span>
|
|
660
|
+
<span id="connText">Edge Mode</span>
|
|
661
|
+
</div>
|
|
662
|
+
</div>
|
|
663
|
+
</header>
|
|
664
|
+
|
|
665
|
+
<div class="edge-notice">
|
|
666
|
+
<strong>☁️ Running in Edge/Cloudflare Workers Mode</strong>
|
|
667
|
+
System metrics (CPU, Memory, Heap) are not available. Dashboard updates via polling every 5 seconds.
|
|
668
|
+
</div>
|
|
669
|
+
|
|
670
|
+
<div class="stats-bar">
|
|
671
|
+
<div class="stat-box"><div class="label">Uptime</div><div class="value" id="uptime">${uptime}</div></div>
|
|
672
|
+
<div class="stat-box"><div class="label">Requests</div><div class="value" id="totalReq">0</div></div>
|
|
673
|
+
<div class="stat-box"><div class="label">Active</div><div class="value" id="activeConn">0</div></div>
|
|
674
|
+
<div class="stat-box"><div class="label">Error Rate</div><div class="value" id="errorRate">0%</div></div>
|
|
675
|
+
</div>
|
|
676
|
+
|
|
677
|
+
<div class="percentiles">
|
|
678
|
+
<div class="percentile-item"><div class="label">Avg</div><div class="value" id="pAvg">0ms</div></div>
|
|
679
|
+
<div class="percentile-item"><div class="label">P50</div><div class="value" id="p50">0ms</div></div>
|
|
680
|
+
<div class="percentile-item"><div class="label">P95</div><div class="value" id="p95">0ms</div></div>
|
|
681
|
+
<div class="percentile-item"><div class="label">P99</div><div class="value" id="p99">0ms</div></div>
|
|
682
|
+
</div>
|
|
683
|
+
|
|
684
|
+
<div class="metric-row">
|
|
685
|
+
<div class="metric-info"><div class="metric-label">Response</div><div class="metric-value"><span id="rtVal">0</span><span class="metric-unit">ms</span></div></div>
|
|
686
|
+
<div class="chart-container"><canvas id="rtChart"></canvas></div>
|
|
687
|
+
</div>
|
|
688
|
+
<div class="metric-row">
|
|
689
|
+
<div class="metric-info"><div class="metric-label">RPS</div><div class="metric-value" id="rpsVal">0</div></div>
|
|
690
|
+
<div class="chart-container"><canvas id="rpsChart"></canvas></div>
|
|
691
|
+
</div>
|
|
692
|
+
<div class="metric-row">
|
|
693
|
+
<div class="metric-info"><div class="metric-label">Error Rate</div><div class="metric-value"><span id="errRateVal">0</span><span class="metric-unit">%</span></div></div>
|
|
694
|
+
<div class="chart-container"><canvas id="errChart"></canvas></div>
|
|
695
|
+
</div>
|
|
696
|
+
|
|
697
|
+
<div class="section-title">Route Analytics</div>
|
|
698
|
+
<div class="routes-grid">
|
|
699
|
+
<div class="route-section">
|
|
700
|
+
<h3>🔥 Top Routes</h3>
|
|
701
|
+
<div id="topRoutes"><div class="route-item"><span class="route-path">No data yet</span></div></div>
|
|
702
|
+
</div>
|
|
703
|
+
<div class="route-section">
|
|
704
|
+
<h3>🐢 Slowest Routes</h3>
|
|
705
|
+
<div id="slowRoutes"><div class="route-item"><span class="route-path">No data yet</span></div></div>
|
|
706
|
+
</div>
|
|
707
|
+
</div>
|
|
708
|
+
|
|
709
|
+
<div class="section-title">HTTP Status Codes</div>
|
|
710
|
+
<div class="status-codes">
|
|
711
|
+
<div class="status-code-box"><div class="code">2xx</div><div class="count s2xx" id="s2xx">0</div></div>
|
|
712
|
+
<div class="status-code-box"><div class="code">3xx</div><div class="count s3xx" id="s3xx">0</div></div>
|
|
713
|
+
<div class="status-code-box"><div class="code">4xx</div><div class="count s4xx" id="s4xx">0</div></div>
|
|
714
|
+
<div class="status-code-box"><div class="code">5xx</div><div class="count s5xx" id="s5xx">0</div></div>
|
|
715
|
+
<div class="status-code-box"><div class="code">Rate Limited</div><div class="count" id="rateLimited">0</div></div>
|
|
716
|
+
</div>
|
|
717
|
+
|
|
718
|
+
<div class="section-title">Recent Errors</div>
|
|
719
|
+
<div class="errors-panel" id="errorsPanel">
|
|
720
|
+
<div style="color: var(--text-muted); font-size: 12px;">No errors recorded</div>
|
|
721
|
+
</div>
|
|
722
|
+
</div>
|
|
723
|
+
|
|
724
|
+
<script>
|
|
725
|
+
(function() {
|
|
726
|
+
var isDark = localStorage.getItem('statusDark') === 'true';
|
|
727
|
+
if (isDark) document.body.classList.add('dark');
|
|
728
|
+
|
|
729
|
+
window.toggleTheme = function() {
|
|
730
|
+
document.body.classList.toggle('dark');
|
|
731
|
+
localStorage.setItem('statusDark', document.body.classList.contains('dark'));
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
var gridColor = isDark ? '#2a2a2a' : '#f0f0f0';
|
|
735
|
+
|
|
736
|
+
var chartConfig = {
|
|
737
|
+
responsive: true, maintainAspectRatio: false, animation: false,
|
|
738
|
+
plugins: { legend: { display: false } },
|
|
739
|
+
scales: {
|
|
740
|
+
x: { type: 'time', time: { unit: 'second' }, grid: { display: false }, ticks: { display: false } },
|
|
741
|
+
y: { beginAtZero: true, grid: { color: gridColor, drawBorder: false }, ticks: { font: { size: 9 }, color: '#999', maxTicksLimit: 3 } }
|
|
742
|
+
},
|
|
743
|
+
elements: { point: { radius: 0 }, line: { tension: 0.2, borderWidth: 1.5 } }
|
|
744
|
+
};
|
|
745
|
+
|
|
746
|
+
function createChart(id, color) {
|
|
747
|
+
var ctx = document.getElementById(id).getContext('2d');
|
|
748
|
+
var config = JSON.parse(JSON.stringify(chartConfig));
|
|
749
|
+
return new Chart(ctx, { type: 'line', data: { datasets: [{ data: [], borderColor: color, fill: false }] }, options: config });
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
var charts = {
|
|
753
|
+
rt: createChart('rtChart', '#10b981'),
|
|
754
|
+
rps: createChart('rpsChart', '#ec4899'),
|
|
755
|
+
err: createChart('errChart', '#ef4444')
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
function updateChart(chart, points) {
|
|
759
|
+
chart.data.datasets[0].data = points.map(function(p) { return { x: new Date(p.timestamp), y: p.value }; });
|
|
760
|
+
chart.update('none');
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function formatUptime(s) {
|
|
764
|
+
var d=Math.floor(s/86400), h=Math.floor((s%86400)/3600), m=Math.floor((s%3600)/60), parts=[];
|
|
765
|
+
if(d)parts.push(d+'d'); if(h)parts.push(h+'h'); if(m)parts.push(m+'m'); parts.push((s%60)+'s');
|
|
766
|
+
return parts.join(' ');
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
function sumCodes(codes, prefix) { var sum=0; for(var c in codes) if(c.startsWith(prefix)) sum+=codes[c]; return sum; }
|
|
770
|
+
|
|
771
|
+
function renderRoutes(containerId, routes, statKey, isSlow) {
|
|
772
|
+
var container = document.getElementById(containerId);
|
|
773
|
+
if (!routes || routes.length === 0) { container.innerHTML = '<div class="route-item"><span class="route-path">No data yet</span></div>'; return; }
|
|
774
|
+
container.innerHTML = routes.slice(0,5).map(function(r) {
|
|
775
|
+
var val = statKey === 'avgTime' ? r.avgTime.toFixed(1) + 'ms' : (statKey === 'errors' ? r.errors : r.count);
|
|
776
|
+
var cls = isSlow && r.avgTime > 100 ? 'slow' : (statKey === 'errors' ? 'error' : '');
|
|
777
|
+
return '<div class="route-item"><span class="route-path">' + r.method + ' ' + r.path + '</span><span class="route-stat ' + cls + '">' + val + '</span></div>';
|
|
778
|
+
}).join('');
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
function renderErrors(errors) {
|
|
782
|
+
var panel = document.getElementById('errorsPanel');
|
|
783
|
+
if (!errors || errors.length === 0) { panel.innerHTML = '<div style="color:var(--text-muted);font-size:12px;">No errors recorded</div>'; return; }
|
|
784
|
+
panel.innerHTML = errors.slice(0,5).map(function(e) {
|
|
785
|
+
return '<div class="error-item"><div class="error-time">' + new Date(e.timestamp).toLocaleTimeString() + '</div><div class="error-path">' + e.method + ' ' + e.path + ' → ' + e.status + '</div></div>';
|
|
786
|
+
}).join('');
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
function applyAlertColors(alerts) {
|
|
790
|
+
document.getElementById('rtVal').style.color = alerts.responseTime ? 'var(--danger)' : '';
|
|
791
|
+
document.getElementById('errorRate').style.color = alerts.errorRate ? 'var(--danger)' : '';
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function fetchMetrics() {
|
|
795
|
+
fetch('./api/metrics')
|
|
796
|
+
.then(function(res) { return res.json(); })
|
|
797
|
+
.then(function(data) {
|
|
798
|
+
var s = data.snapshot, c = data.charts;
|
|
799
|
+
|
|
800
|
+
document.getElementById('rtVal').textContent = s.responseTime.toFixed(1);
|
|
801
|
+
document.getElementById('rpsVal').textContent = s.rps.toFixed(1);
|
|
802
|
+
document.getElementById('errRateVal').textContent = s.errorRate.toFixed(1);
|
|
803
|
+
|
|
804
|
+
document.getElementById('uptime').textContent = formatUptime(s.processUptime);
|
|
805
|
+
document.getElementById('totalReq').textContent = s.totalRequests.toLocaleString();
|
|
806
|
+
document.getElementById('activeConn').textContent = s.activeConnections;
|
|
807
|
+
document.getElementById('errorRate').textContent = s.errorRate.toFixed(1) + '%';
|
|
808
|
+
|
|
809
|
+
document.getElementById('pAvg').textContent = s.percentiles.avg.toFixed(1) + 'ms';
|
|
810
|
+
document.getElementById('p50').textContent = s.percentiles.p50.toFixed(1) + 'ms';
|
|
811
|
+
document.getElementById('p95').textContent = s.percentiles.p95.toFixed(1) + 'ms';
|
|
812
|
+
document.getElementById('p99').textContent = s.percentiles.p99.toFixed(1) + 'ms';
|
|
813
|
+
|
|
814
|
+
if (c.responseTime) updateChart(charts.rt, c.responseTime);
|
|
815
|
+
if (c.rps) updateChart(charts.rps, c.rps);
|
|
816
|
+
if (c.errorRate) updateChart(charts.err, c.errorRate);
|
|
817
|
+
|
|
818
|
+
renderRoutes('topRoutes', s.topRoutes, 'count', false);
|
|
819
|
+
renderRoutes('slowRoutes', s.slowestRoutes, 'avgTime', true);
|
|
820
|
+
|
|
821
|
+
document.getElementById('s2xx').textContent = sumCodes(s.statusCodes, '2');
|
|
822
|
+
document.getElementById('s3xx').textContent = sumCodes(s.statusCodes, '3');
|
|
823
|
+
document.getElementById('s4xx').textContent = sumCodes(s.statusCodes, '4');
|
|
824
|
+
document.getElementById('s5xx').textContent = sumCodes(s.statusCodes, '5');
|
|
825
|
+
document.getElementById('rateLimited').textContent = s.rateLimitStats.blocked;
|
|
826
|
+
|
|
827
|
+
renderErrors(s.recentErrors);
|
|
828
|
+
applyAlertColors(s.alerts);
|
|
829
|
+
})
|
|
830
|
+
.catch(function(err) {
|
|
831
|
+
console.error('Failed to fetch metrics:', err);
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// Initial fetch
|
|
836
|
+
fetchMetrics();
|
|
837
|
+
|
|
838
|
+
// Poll every 5 seconds
|
|
839
|
+
setInterval(fetchMetrics, 5000);
|
|
840
|
+
})();
|
|
841
|
+
</script>
|
|
842
|
+
</body>
|
|
843
|
+
</html>`;
|
|
844
|
+
}
|
|
454
845
|
//# sourceMappingURL=dashboard.js.map
|
package/dist/dashboard.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../src/dashboard.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,kCAAkC;AAClC,gDAAgD;AAChD,gFAAgF;AAIhF;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAkB;IACrF,OAAO;;;;;aAKE,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBAiMI,KAAK;wCACa,QAAQ;;;;;;;;;;;kGAWkD,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+DA6KzC,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA4DjE,CAAC;AACT,CAAC"}
|
|
1
|
+
{"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../src/dashboard.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,kCAAkC;AAClC,gDAAgD;AAChD,gFAAgF;AAIhF;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAkB;IACrF,OAAO;;;;;aAKE,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBAiMI,KAAK;wCACa,QAAQ;;;;;;;;;;;kGAWkD,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+DA6KzC,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA4DjE,CAAC;AACT,CAAC;AAWD;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAsB;IACjF,OAAO;;;;;aAKE,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBA6LI,KAAK;wCACa,QAAQ;;;;;;;;;;;;;;;;;kGAiBkD,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA4KhG,CAAC;AACT,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import type { StatusMonitorConfig } from './types.js';
|
|
3
|
+
export type { StatusMonitorConfig, MetricsSnapshot, ChartData, RouteStats, ErrorEntry, PercentileData, AlertStatus } from './types.js';
|
|
4
|
+
export { createEdgeMonitor, type EdgeMonitor } from './monitor-edge.js';
|
|
5
|
+
export { generateEdgeDashboard } from './dashboard.js';
|
|
6
|
+
/**
|
|
7
|
+
* Create a status monitor for Edge/Cloudflare Workers environments
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { Hono } from 'hono';
|
|
12
|
+
* import { statusMonitor } from 'hono-status-monitor/edge';
|
|
13
|
+
*
|
|
14
|
+
* const app = new Hono();
|
|
15
|
+
* const monitor = statusMonitor();
|
|
16
|
+
*
|
|
17
|
+
* app.use('*', monitor.middleware);
|
|
18
|
+
* app.route('/status', monitor.routes);
|
|
19
|
+
*
|
|
20
|
+
* export default app;
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function statusMonitor(config?: StatusMonitorConfig): {
|
|
24
|
+
/** Hono middleware for tracking all requests */
|
|
25
|
+
middleware: (c: any, next: () => Promise<void>) => Promise<void>;
|
|
26
|
+
/** Pre-configured Hono routes for dashboard and API */
|
|
27
|
+
routes: Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
|
|
28
|
+
/** Initialize Socket.io - not available in edge, returns null */
|
|
29
|
+
initSocket: () => any;
|
|
30
|
+
/** Track rate limit events for the dashboard */
|
|
31
|
+
trackRateLimit: (blocked: boolean) => void;
|
|
32
|
+
/** Get current metrics snapshot */
|
|
33
|
+
getMetrics: () => Promise<import("./types.js").MetricsSnapshot>;
|
|
34
|
+
/** Get chart data for all metrics */
|
|
35
|
+
getCharts: () => import("./types.js").ChartData;
|
|
36
|
+
/** Stop metrics collection */
|
|
37
|
+
stop: () => void;
|
|
38
|
+
/** Access to the underlying monitor instance */
|
|
39
|
+
monitor: {
|
|
40
|
+
config: Required<StatusMonitorConfig>;
|
|
41
|
+
trackRequest: (path: string, method: string) => void;
|
|
42
|
+
trackRequestComplete: (path: string, method: string, durationMs: number, statusCode: number) => void;
|
|
43
|
+
trackRateLimitEvent: (blocked: boolean) => void;
|
|
44
|
+
getMetricsSnapshot: () => Promise<import("./types.js").MetricsSnapshot>;
|
|
45
|
+
getChartData: () => import("./types.js").ChartData;
|
|
46
|
+
start: () => void;
|
|
47
|
+
stop: () => void;
|
|
48
|
+
initSocket: () => null;
|
|
49
|
+
formatUptime: (seconds: number) => string;
|
|
50
|
+
isEdgeMode: boolean;
|
|
51
|
+
readonly io: null;
|
|
52
|
+
};
|
|
53
|
+
/** Whether running in edge mode */
|
|
54
|
+
isEdgeMode: boolean;
|
|
55
|
+
};
|
|
56
|
+
export declare const statusMonitorEdge: typeof statusMonitor;
|
|
57
|
+
export default statusMonitor;
|
|
58
|
+
//# sourceMappingURL=index-edge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-edge.d.ts","sourceRoot":"","sources":["../src/index-edge.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAKtD,YAAY,EACR,mBAAmB,EACnB,eAAe,EACf,SAAS,EACT,UAAU,EACV,UAAU,EACV,cAAc,EACd,WAAW,EACd,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,iBAAiB,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACxE,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAEvD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAAC,MAAM,GAAE,mBAAwB;IAiDtD,gDAAgD;oBA5CvB,GAAG,QAAQ,MAAM,OAAO,CAAC,IAAI,CAAC;IA8CvD,uDAAuD;;IAEvD,iEAAiE;sBACvC,GAAG;IAC7B,gDAAgD;8BACtB,OAAO;IACjC,mCAAmC;;IAEnC,qCAAqC;;IAErC,8BAA8B;;IAE9B,gDAAgD;;;;;;;;;;;;;;;IAEhD,mCAAmC;;EAG1C;AAGD,eAAO,MAAM,iBAAiB,sBAAgB,CAAC;AAG/C,eAAe,aAAa,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// HONO STATUS MONITOR - EDGE ENTRY POINT
|
|
3
|
+
// For Cloudflare Workers / Edge environments only
|
|
4
|
+
// No Node.js dependencies (os, cluster, socket.io, etc.)
|
|
5
|
+
// =============================================================================
|
|
6
|
+
import { Hono } from 'hono';
|
|
7
|
+
import { createEdgeMonitor } from './monitor-edge.js';
|
|
8
|
+
import { generateEdgeDashboard } from './dashboard.js';
|
|
9
|
+
export { createEdgeMonitor } from './monitor-edge.js';
|
|
10
|
+
export { generateEdgeDashboard } from './dashboard.js';
|
|
11
|
+
/**
|
|
12
|
+
* Create a status monitor for Edge/Cloudflare Workers environments
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import { Hono } from 'hono';
|
|
17
|
+
* import { statusMonitor } from 'hono-status-monitor/edge';
|
|
18
|
+
*
|
|
19
|
+
* const app = new Hono();
|
|
20
|
+
* const monitor = statusMonitor();
|
|
21
|
+
*
|
|
22
|
+
* app.use('*', monitor.middleware);
|
|
23
|
+
* app.route('/status', monitor.routes);
|
|
24
|
+
*
|
|
25
|
+
* export default app;
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export function statusMonitor(config = {}) {
|
|
29
|
+
const monitor = createEdgeMonitor(config);
|
|
30
|
+
const routes = new Hono();
|
|
31
|
+
// Create middleware for edge
|
|
32
|
+
const middleware = async (c, next) => {
|
|
33
|
+
const path = new URL(c.req.url).pathname;
|
|
34
|
+
const method = c.req.method;
|
|
35
|
+
// Skip status route itself
|
|
36
|
+
if (path.startsWith(config.path || '/status')) {
|
|
37
|
+
return next();
|
|
38
|
+
}
|
|
39
|
+
const start = performance.now();
|
|
40
|
+
monitor.trackRequest(path, method);
|
|
41
|
+
try {
|
|
42
|
+
await next();
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
const duration = performance.now() - start;
|
|
46
|
+
const status = c.res?.status || 200;
|
|
47
|
+
monitor.trackRequestComplete(path, method, duration, status);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
// Dashboard page - uses polling mode
|
|
51
|
+
routes.get('/', async (c) => {
|
|
52
|
+
const snapshot = await monitor.getMetricsSnapshot();
|
|
53
|
+
const html = generateEdgeDashboard({
|
|
54
|
+
hostname: snapshot.hostname,
|
|
55
|
+
uptime: monitor.formatUptime(snapshot.uptime),
|
|
56
|
+
title: monitor.config.title
|
|
57
|
+
});
|
|
58
|
+
return c.html(html);
|
|
59
|
+
});
|
|
60
|
+
// JSON API endpoint
|
|
61
|
+
routes.get('/api/metrics', async (c) => {
|
|
62
|
+
return c.json({
|
|
63
|
+
snapshot: await monitor.getMetricsSnapshot(),
|
|
64
|
+
charts: monitor.getChartData()
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
// Start (no-op in edge mode)
|
|
68
|
+
monitor.start();
|
|
69
|
+
return {
|
|
70
|
+
/** Hono middleware for tracking all requests */
|
|
71
|
+
middleware,
|
|
72
|
+
/** Pre-configured Hono routes for dashboard and API */
|
|
73
|
+
routes,
|
|
74
|
+
/** Initialize Socket.io - not available in edge, returns null */
|
|
75
|
+
initSocket: () => null,
|
|
76
|
+
/** Track rate limit events for the dashboard */
|
|
77
|
+
trackRateLimit: (blocked) => monitor.trackRateLimitEvent(blocked),
|
|
78
|
+
/** Get current metrics snapshot */
|
|
79
|
+
getMetrics: () => monitor.getMetricsSnapshot(),
|
|
80
|
+
/** Get chart data for all metrics */
|
|
81
|
+
getCharts: () => monitor.getChartData(),
|
|
82
|
+
/** Stop metrics collection */
|
|
83
|
+
stop: () => monitor.stop(),
|
|
84
|
+
/** Access to the underlying monitor instance */
|
|
85
|
+
monitor,
|
|
86
|
+
/** Whether running in edge mode */
|
|
87
|
+
isEdgeMode: true
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// Also export as statusMonitorEdge for clarity
|
|
91
|
+
export const statusMonitorEdge = statusMonitor;
|
|
92
|
+
// Default export
|
|
93
|
+
export default statusMonitor;
|
|
94
|
+
//# sourceMappingURL=index-edge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-edge.js","sourceRoot":"","sources":["../src/index-edge.ts"],"names":[],"mappings":"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"}
|