@@ -2,6 +2,7 @@ package server
22
33import (
44 "context"
5+ _ "embed"
56 "encoding/hex"
67 "encoding/json"
78 "fmt"
@@ -16,268 +17,8 @@ import (
1617 "github.com/rs/zerolog"
1718)
1819
19- // daVisualizationHTML contains the DA visualization dashboard template.
20- // Inlined as a constant instead of using go:embed to avoid file-access issues
21- // in sandboxed build environments.
22- const daVisualizationHTML = `<!DOCTYPE html>
23- <html>
24- <head>
25- <title>Evolve DA Layer Visualization</title>
26- <style>
27- body { font-family: Arial, sans-serif; margin: 20px; }
28- .header { background-color: #f5f5f5; padding: 20px; border-radius: 5px; margin-bottom: 20px; }
29- .api-section { background-color: #e8f4f8; padding: 20px; border-radius: 5px; margin-bottom: 20px; border: 1px solid #b3d9e6; }
30- .api-endpoint { background-color: #fff; padding: 15px; margin: 10px 0; border-radius: 5px; border: 1px solid #ddd; }
31- .api-endpoint h4 { margin: 0 0 10px 0; color: #007cba; }
32- .api-endpoint code { background-color: #f4f4f4; padding: 4px 8px; border-radius: 3px; font-size: 14px; }
33- .api-response { background-color: #f9f9f9; padding: 10px; margin-top: 10px; border-radius: 3px; border-left: 3px solid #007cba; }
34- .submission { border: 1px solid #ddd; margin: 10px 0; padding: 15px; border-radius: 5px; }
35- .success { border-left: 4px solid #4CAF50; }
36- .error { border-left: 4px solid #f44336; }
37- .pending { border-left: 4px solid #ff9800; }
38- .blob-ids { margin-top: 10px; }
39- .blob-id { background-color: #f0f0f0; padding: 2px 6px; margin: 2px; border-radius: 3px; font-family: monospace; font-size: 12px; }
40- .meta { color: #666; font-size: 14px; }
41- table { border-collapse: collapse; width: 100%; }
42- th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
43- th { background-color: #f2f2f2; }
44- .blob-link { color: #007cba; text-decoration: none; }
45- .blob-link:hover { text-decoration: underline; }
46- .method { display: inline-block; padding: 2px 6px; border-radius: 3px; font-weight: bold; font-size: 12px; margin-right: 8px; }
47- .method-get { background-color: #61b5ff; color: white; }
48- .method-post { background-color: #49cc90; color: white; }
49- .example-link { color: #007cba; text-decoration: none; font-size: 13px; }
50- .example-link:hover { text-decoration: underline; }
51- .blob-ids { margin-top: 5px; }
52- .blob-ids-preview { display: inline; }
53- .blob-ids-full { display: none; max-height: 200px; overflow-y: auto; margin-top: 5px; padding: 5px; background: #fafafa; border-radius: 3px; }
54- .blob-ids-full.expanded { display: block; }
55- .blob-toggle { background: none; border: 1px solid #007cba; color: #007cba; padding: 2px 8px; border-radius: 3px; cursor: pointer; font-size: 11px; margin-left: 5px; }
56- .blob-toggle:hover { background: #007cba; color: white; }
57- </style>
58- </head>
59- <body>
60- <div class="header">
61- <h1>DA Layer Visualization</h1>
62- <p>Real-time view of blob submissions from the sequencer node to the Data Availability layer.</p>
63- {{if .IsAggregator}}
64- {{if .Submissions}}
65- <p><strong>Recent Submissions:</strong> {{len .Submissions}} (last 100) | <strong>Last Update:</strong> {{.LastUpdate}}</p>
66- {{else}}
67- <p><strong>Node Type:</strong> Aggregator | <strong>Recent Submissions:</strong> 0 | <strong>Last Update:</strong> {{.LastUpdate}}</p>
68- {{end}}
69- {{else}}
70- <p><strong>Node Type:</strong> Non-aggregator | This node does not submit data to the DA layer.</p>
71- {{end}}
72- </div>
73-
74- {{if .IsAggregator}}
75- <div class="api-section">
76- <h2>Available API Endpoints</h2>
77-
78- <div class="api-endpoint">
79- <h4><span class="method method-get">GET</span> /da</h4>
80- <p>Returns this HTML visualization dashboard with real-time DA submission data.</p>
81- <p><strong>Example:</strong> <a href="/da" class="example-link">View Dashboard</a></p>
82- </div>
83-
84- <div class="api-endpoint">
85- <h4><span class="method method-get">GET</span> /da/submissions</h4>
86- <p>Returns a JSON array of the most recent DA submissions (up to 100) with metadata.</p>
87- <p><strong>Note:</strong> Only aggregator nodes submit to the DA layer.</p>
88- <p><strong>Example:</strong> <code>curl http://localhost:8080/da/submissions</code></p>
89- <div class="api-response">
90- <strong>Response:</strong>
91- <pre>{
92- "submissions": [
93- {
94- "id": "submission_1234_1699999999",
95- "height": 1234,
96- "blob_size": 2048,
97- "timestamp": "2023-11-15T10:30:00Z",
98- "gas_price": 0.000001,
99- "status_code": "Success",
100- "num_blobs": 1,
101- "blob_ids": ["a1b2c3d4..."]
102- }
103- ],
104- "total": 42
105- }</pre>
106- </div>
107- </div>
108-
109- <div class="api-endpoint">
110- <h4><span class="method method-get">GET</span> /da/blob?id={blob_id}</h4>
111- <p>Returns detailed information about a specific blob including its content.</p>
112- <p><strong>Parameters:</strong> <code>id</code> - Hexadecimal blob ID</p>
113- <p><strong>Example:</strong> <code>curl http://localhost:8080/da/blob?id=a1b2c3d4...</code></p>
114- <div class="api-response">
115- <strong>Response:</strong>
116- <pre>{
117- "id": "a1b2c3d4...",
118- "height": 1234,
119- "commitment": "deadbeef...",
120- "size": 2048,
121- "content": "0x1234...",
122- "content_preview": "..."
123- }</pre>
124- </div>
125- </div>
126-
127- <div class="api-endpoint">
128- <h4><span class="method method-get">GET</span> /da/stats</h4>
129- <p>Returns aggregated statistics about DA submissions.</p>
130- <p><strong>Example:</strong> <code>curl http://localhost:8080/da/stats</code></p>
131- <div class="api-response">
132- <strong>Response:</strong>
133- <pre>{
134- "total_submissions": 42,
135- "success_count": 40,
136- "error_count": 2,
137- "success_rate": "95.24%",
138- "total_blob_size": 86016,
139- "avg_blob_size": 2048,
140- "avg_gas_price": 0.000001,
141- "time_range": {
142- "first": "2023-11-15T10:00:00Z",
143- "last": "2023-11-15T10:30:00Z"
144- }
145- }</pre>
146- </div>
147- </div>
148-
149- <div class="api-endpoint">
150- <h4><span class="method method-get">GET</span> /da/health</h4>
151- <p>Returns health status of the DA layer connection.</p>
152- <p><strong>Example:</strong> <code>curl http://localhost:8080/da/health</code></p>
153- <div class="api-response">
154- <strong>Response:</strong>
155- <pre>{
156- "status": "healthy",
157- "is_healthy": true,
158- "connection_status": "connected",
159- "connection_healthy": true,
160- "metrics": {
161- "recent_error_rate": "10.0%",
162- "recent_errors": 1,
163- "recent_successes": 9,
164- "recent_sample_size": 10,
165- "total_submissions": 42,
166- "last_submission_time": "2023-11-15T10:30:00Z",
167- "last_success_time": "2023-11-15T10:29:45Z",
168- "last_error_time": "2023-11-15T10:25:00Z"
169- },
170- "issues": [],
171- "timestamp": "2023-11-15T10:30:15Z"
172- }</pre>
173- <p style="margin-top: 15px;"><strong>Health Status Values:</strong></p>
174- <ul style="margin-left: 20px; font-size: 13px; line-height: 1.8;">
175- <li><code>healthy</code> - System operating normally</li>
176- <li><code>degraded</code> - Elevated error rate but still functional</li>
177- <li><code>unhealthy</code> - Critical issues detected</li>
178- <li><code>warning</code> - Potential issues that need attention</li>
179- <li><code>unknown</code> - Insufficient data to determine health</li>
180- </ul>
181- </div>
182- </div>
183- {{end}}
184-
185- {{if .IsAggregator}}
186- <h2>Recent Submissions</h2>
187- {{if .Submissions}}
188- <table>
189- <tr>
190- <th>Timestamp</th>
191- <th>Height</th>
192- <th>Status</th>
193- <th>Blobs</th>
194- <th>Size (bytes)</th>
195- <th>Gas Price</th>
196- <th>Message</th>
197- </tr>
198- {{range .Submissions}}
199- <tr class="{{if eq .StatusCode "Success"}}success{{else if eq .StatusCode "Error"}}error{{else}}pending{{end}}">
200- <td>{{if not .Timestamp.IsZero}}{{.Timestamp.Format "15:04:05"}}{{else}}--:--:--{{end}}</td>
201- <td>{{.Height}}</td>
202- <td>{{.StatusCode}}</td>
203- <td>
204- {{.NumBlobs}}
205- {{if .BlobIDs}}
206- {{$namespace := .Namespace}}
207- {{$numBlobs := len .BlobIDs}}
208- {{if le $numBlobs 5}}
209- <div class="blob-ids">
210- {{range .BlobIDs}}
211- <a href="/da/blob?id={{.}}&namespace={{$namespace}}" class="blob-link blob-id" title="Click to view blob details">{{slice . 0 8}}...</a>
212- {{end}}
213- </div>
214- {{else}}
215- <div class="blob-ids">
216- <span class="blob-ids-preview">
217- {{range $i, $id := .BlobIDs}}{{if lt $i 3}}<a href="/da/blob?id={{$id}}&namespace={{$namespace}}" class="blob-link blob-id" title="Click to view blob details">{{slice $id 0 8}}...</a>{{end}}{{end}}
218- </span>
219- <button class="blob-toggle" onclick="toggleBlobs(this)">+{{subtract $numBlobs 3}} more</button>
220- <div class="blob-ids-full">
221- {{range .BlobIDs}}
222- <a href="/da/blob?id={{.}}&namespace={{$namespace}}" class="blob-link blob-id" title="Click to view blob details">{{slice . 0 8}}...</a>
223- {{end}}
224- </div>
225- </div>
226- {{end}}
227- {{end}}
228- </td>
229- <td>{{.BlobSize}}</td>
230- <td>{{printf "%.6f" .GasPrice}}</td>
231- <td>{{.Message}}</td>
232- </tr>
233- {{end}}
234- </table>
235- {{else}}
236- <p>No submissions recorded yet. This aggregator node has not submitted any data to the DA layer yet.</p>
237- {{end}}
238- {{else}}
239- <h2>Node Information</h2>
240- <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>
241- <p>Only aggregator nodes that actively produce blocks and submit data to the DA layer will display this information.</p>
242- {{end}}
243-
244- <div style="margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd; color: #666;">
245- <p><em>Auto-refresh: <span id="countdown">30</span>s</em> | <a href="javascript:location.reload()" style="color: #007cba;">Refresh Now</a></p>
246- </div>
247-
248- <script>
249- // Auto-refresh page every 30 seconds
250- let countdown = 30;
251- const countdownEl = document.getElementById('countdown');
252- setInterval(() => {
253- countdown--;
254- countdownEl.textContent = countdown;
255- if (countdown <= 0) {
256- location.reload();
257- }
258- }, 1000);
259-
260- // Toggle blob list expansion
261- function toggleBlobs(btn) {
262- const container = btn.parentElement;
263- const fullList = container.querySelector('.blob-ids-full');
264- const preview = container.querySelector('.blob-ids-preview');
265- const isExpanded = fullList.classList.contains('expanded');
266-
267- if (isExpanded) {
268- fullList.classList.remove('expanded');
269- preview.style.display = 'inline';
270- btn.textContent = btn.dataset.expandText;
271- } else {
272- fullList.classList.add('expanded');
273- preview.style.display = 'none';
274- btn.dataset.expandText = btn.textContent;
275- btn.textContent = 'Show less';
276- }
277- }
278- </script>
279- </body>
280- </html>`
20+ //go:embed templates/da_visualization.html
21+ var daVisualizationHTML string
28122
28223// DASubmissionInfo represents information about a DA submission
28324type DASubmissionInfo struct {
0 commit comments