@@ -2,6 +2,7 @@ package server
22
33import (
44 "context"
5+ _ "embed"
56 "encoding/hex"
67 "encoding/json"
78 "fmt"
@@ -14,6 +15,9 @@ import (
1415 "github.com/rs/zerolog"
1516)
1617
18+ //go:embed templates/da_visualization.html
19+ var daVisualizationHTML string
20+
1721// DASubmissionInfo represents information about a DA submission
1822type DASubmissionInfo struct {
1923 ID string `json:"id"`
@@ -446,228 +450,6 @@ func (s *DAVisualizationServer) handleDAVisualizationHTML(w http.ResponseWriter,
446450 submissions [i ], submissions [j ] = submissions [j ], submissions [i ]
447451 }
448452
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-
671453 t , err := template .New ("da" ).Funcs (template.FuncMap {
672454 "slice" : func (s string , start , end int ) string {
673455 if end > len (s ) {
@@ -690,7 +472,7 @@ func (s *DAVisualizationServer) handleDAVisualizationHTML(w http.ResponseWriter,
690472 return 0
691473 }
692474 },
693- }).Parse (tmpl )
475+ }).Parse (daVisualizationHTML )
694476
695477 if err != nil {
696478 s .logger .Error ().Err (err ).Msg ("Failed to parse template" )
0 commit comments