@@ -2,6 +2,7 @@ package server
2
2
3
3
import (
4
4
"context"
5
+ _ "embed"
5
6
"encoding/hex"
6
7
"encoding/json"
7
8
"fmt"
@@ -14,6 +15,9 @@ import (
14
15
"github.com/rs/zerolog"
15
16
)
16
17
18
+ //go:embed templates/da_visualization.html
19
+ var daVisualizationHTML string
20
+
17
21
// DASubmissionInfo represents information about a DA submission
18
22
type DASubmissionInfo struct {
19
23
ID string `json:"id"`
@@ -446,228 +450,6 @@ func (s *DAVisualizationServer) handleDAVisualizationHTML(w http.ResponseWriter,
446
450
submissions [i ], submissions [j ] = submissions [j ], submissions [i ]
447
451
}
448
452
449
- tmpl := `
450
- <!DOCTYPE html>
451
- <html>
452
- <head>
453
- <title>Evolve DA Layer Visualization</title>
454
- <style>
455
- body { font-family: Arial, sans-serif; margin: 20px; }
456
- .header { background-color: #f5f5f5; padding: 20px; border-radius: 5px; margin-bottom: 20px; }
457
- .api-section { background-color: #e8f4f8; padding: 20px; border-radius: 5px; margin-bottom: 20px; border: 1px solid #b3d9e6; }
458
- .api-endpoint { background-color: #fff; padding: 15px; margin: 10px 0; border-radius: 5px; border: 1px solid #ddd; }
459
- .api-endpoint h4 { margin: 0 0 10px 0; color: #007cba; }
460
- .api-endpoint code { background-color: #f4f4f4; padding: 4px 8px; border-radius: 3px; font-size: 14px; }
461
- .api-response { background-color: #f9f9f9; padding: 10px; margin-top: 10px; border-radius: 3px; border-left: 3px solid #007cba; }
462
- .submission { border: 1px solid #ddd; margin: 10px 0; padding: 15px; border-radius: 5px; }
463
- .success { border-left: 4px solid #4CAF50; }
464
- .error { border-left: 4px solid #f44336; }
465
- .pending { border-left: 4px solid #ff9800; }
466
- .blob-ids { margin-top: 10px; }
467
- .blob-id { background-color: #f0f0f0; padding: 2px 6px; margin: 2px; border-radius: 3px; font-family: monospace; font-size: 12px; }
468
- .meta { color: #666; font-size: 14px; }
469
- table { border-collapse: collapse; width: 100%; }
470
- th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
471
- th { background-color: #f2f2f2; }
472
- .blob-link { color: #007cba; text-decoration: none; }
473
- .blob-link:hover { text-decoration: underline; }
474
- .method { display: inline-block; padding: 2px 6px; border-radius: 3px; font-weight: bold; font-size: 12px; margin-right: 8px; }
475
- .method-get { background-color: #61b5ff; color: white; }
476
- .method-post { background-color: #49cc90; color: white; }
477
- .example-link { color: #007cba; text-decoration: none; font-size: 13px; }
478
- .example-link:hover { text-decoration: underline; }
479
- </style>
480
- </head>
481
- <body>
482
- <div class="header">
483
- <h1>DA Layer Visualization</h1>
484
- <p>Real-time view of blob submissions from the sequencer node to the Data Availability layer.</p>
485
- {{if .IsAggregator}}
486
- {{if .Submissions}}
487
- <p><strong>Recent Submissions:</strong> {{len .Submissions}} (last 100) | <strong>Last Update:</strong> {{.LastUpdate}}</p>
488
- {{else}}
489
- <p><strong>Node Type:</strong> Aggregator | <strong>Recent Submissions:</strong> 0 | <strong>Last Update:</strong> {{.LastUpdate}}</p>
490
- {{end}}
491
- {{else}}
492
- <p><strong>Node Type:</strong> Non-aggregator | This node does not submit data to the DA layer.</p>
493
- {{end}}
494
- </div>
495
-
496
- {{if .IsAggregator}}
497
- <div class="api-section">
498
- <h2>Available API Endpoints</h2>
499
-
500
- <div class="api-endpoint">
501
- <h4><span class="method method-get">GET</span> /da</h4>
502
- <p>Returns this HTML visualization dashboard with real-time DA submission data.</p>
503
- <p><strong>Example:</strong> <a href="/da" class="example-link">View Dashboard</a></p>
504
- </div>
505
-
506
- <div class="api-endpoint">
507
- <h4><span class="method method-get">GET</span> /da/submissions</h4>
508
- <p>Returns a JSON array of the most recent DA submissions (up to 100) with metadata.</p>
509
- <p><strong>Note:</strong> Only aggregator nodes submit to the DA layer.</p>
510
- <p><strong>Example:</strong> <code>curl http://localhost:8080/da/submissions</code></p>
511
- <div class="api-response">
512
- <strong>Response:</strong>
513
- <pre>{
514
- "submissions": [
515
- {
516
- "id": "submission_1234_1699999999",
517
- "height": 1234,
518
- "blob_size": 2048,
519
- "timestamp": "2023-11-15T10:30:00Z",
520
- "gas_price": 0.000001,
521
- "status_code": "Success",
522
- "num_blobs": 1,
523
- "blob_ids": ["a1b2c3d4..."]
524
- }
525
- ],
526
- "total": 42
527
- }</pre>
528
- </div>
529
- </div>
530
-
531
- <div class="api-endpoint">
532
- <h4><span class="method method-get">GET</span> /da/blob?id={blob_id}</h4>
533
- <p>Returns detailed information about a specific blob including its content.</p>
534
- <p><strong>Parameters:</strong> <code>id</code> - Hexadecimal blob ID</p>
535
- <p><strong>Example:</strong> <code>curl http://localhost:8080/da/blob?id=a1b2c3d4...</code></p>
536
- <div class="api-response">
537
- <strong>Response:</strong>
538
- <pre>{
539
- "id": "a1b2c3d4...",
540
- "height": 1234,
541
- "commitment": "deadbeef...",
542
- "size": 2048,
543
- "content": "0x1234...",
544
- "content_preview": "..."
545
- }</pre>
546
- </div>
547
- </div>
548
-
549
- <div class="api-endpoint">
550
- <h4><span class="method method-get">GET</span> /da/stats</h4>
551
- <p>Returns aggregated statistics about DA submissions.</p>
552
- <p><strong>Example:</strong> <code>curl http://localhost:8080/da/stats</code></p>
553
- <div class="api-response">
554
- <strong>Response:</strong>
555
- <pre>{
556
- "total_submissions": 42,
557
- "success_count": 40,
558
- "error_count": 2,
559
- "success_rate": "95.24%",
560
- "total_blob_size": 86016,
561
- "avg_blob_size": 2048,
562
- "avg_gas_price": 0.000001,
563
- "time_range": {
564
- "first": "2023-11-15T10:00:00Z",
565
- "last": "2023-11-15T10:30:00Z"
566
- }
567
- }</pre>
568
- </div>
569
- </div>
570
-
571
- <div class="api-endpoint">
572
- <h4><span class="method method-get">GET</span> /da/health</h4>
573
- <p>Returns health status of the DA layer connection.</p>
574
- <p><strong>Example:</strong> <code>curl http://localhost:8080/da/health</code></p>
575
- <div class="api-response">
576
- <strong>Response:</strong>
577
- <pre>{
578
- "status": "healthy",
579
- "is_healthy": true,
580
- "connection_status": "connected",
581
- "connection_healthy": true,
582
- "metrics": {
583
- "recent_error_rate": "10.0%",
584
- "recent_errors": 1,
585
- "recent_successes": 9,
586
- "recent_sample_size": 10,
587
- "total_submissions": 42,
588
- "last_submission_time": "2023-11-15T10:30:00Z",
589
- "last_success_time": "2023-11-15T10:29:45Z",
590
- "last_error_time": "2023-11-15T10:25:00Z"
591
- },
592
- "issues": [],
593
- "timestamp": "2023-11-15T10:30:15Z"
594
- }</pre>
595
- </div>
596
- <p style="margin-top: 15px;"><strong>Health Status Values:</strong></p>
597
- <ul style="margin-left: 20px; font-size: 13px; line-height: 1.8;">
598
- <li><code>healthy</code> - System operating normally</li>
599
- <li><code>degraded</code> - Elevated error rate but still functional</li>
600
- <li><code>unhealthy</code> - Critical issues detected</li>
601
- <li><code>warning</code> - Potential issues that need attention</li>
602
- <li><code>unknown</code> - Insufficient data to determine health</li>
603
- </ul>
604
- </div>
605
- </div>
606
- {{end}}
607
-
608
- {{if .IsAggregator}}
609
- <h2>Recent Submissions</h2>
610
- {{if .Submissions}}
611
- <table>
612
- <tr>
613
- <th>Timestamp</th>
614
- <th>Height</th>
615
- <th>Status</th>
616
- <th>Blobs</th>
617
- <th>Size (bytes)</th>
618
- <th>Gas Price</th>
619
- <th>Message</th>
620
- </tr>
621
- {{range .Submissions}}
622
- <tr class="{{if eq .StatusCode "Success"}}success{{else if eq .StatusCode "Error"}}error{{else}}pending{{end}}">
623
- <td>{{if not .Timestamp.IsZero}}{{.Timestamp.Format "15:04:05"}}{{else}}--:--:--{{end}}</td>
624
- <td>{{.Height}}</td>
625
- <td>{{.StatusCode}}</td>
626
- <td>
627
- {{.NumBlobs}}
628
- {{if .BlobIDs}}
629
- <div class="blob-ids">
630
- {{range .BlobIDs}}
631
- <a href="/da/blob?id={{.}}" class="blob-link blob-id" title="Click to view blob details">{{slice . 0 8}}...</a>
632
- {{end}}
633
- </div>
634
- {{end}}
635
- </td>
636
- <td>{{.BlobSize}}</td>
637
- <td>{{printf "%.6f" .GasPrice}}</td>
638
- <td>{{.Message}}</td>
639
- </tr>
640
- {{end}}
641
- </table>
642
- {{else}}
643
- <p>No submissions recorded yet. This aggregator node has not submitted any data to the DA layer yet.</p>
644
- {{end}}
645
- {{else}}
646
- <h2>Node Information</h2>
647
- <p>This is a non-aggregator node. Non-aggregator nodes do not submit data to the DA layer and therefore do not have submission statistics, health metrics, or DA-related API endpoints available.</p>
648
- <p>Only aggregator nodes that actively produce blocks and submit data to the DA layer will display this information.</p>
649
- {{end}}
650
-
651
- <div style="margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd; color: #666;">
652
- <p><em>Auto-refresh: <span id="countdown">30</span>s</em> | <a href="javascript:location.reload()" style="color: #007cba;">Refresh Now</a></p>
653
- </div>
654
-
655
- <script>
656
- // Auto-refresh page every 30 seconds
657
- let countdown = 30;
658
- const countdownEl = document.getElementById('countdown');
659
- setInterval(() => {
660
- countdown--;
661
- countdownEl.textContent = countdown;
662
- if (countdown <= 0) {
663
- location.reload();
664
- }
665
- }, 1000);
666
- </script>
667
- </body>
668
- </html>
669
- `
670
-
671
453
t , err := template .New ("da" ).Funcs (template.FuncMap {
672
454
"slice" : func (s string , start , end int ) string {
673
455
if end > len (s ) {
@@ -690,7 +472,7 @@ func (s *DAVisualizationServer) handleDAVisualizationHTML(w http.ResponseWriter,
690
472
return 0
691
473
}
692
474
},
693
- }).Parse (tmpl )
475
+ }).Parse (daVisualizationHTML )
694
476
695
477
if err != nil {
696
478
s .logger .Error ().Err (err ).Msg ("Failed to parse template" )
0 commit comments