Skip to content

Commit 839e674

Browse files
authored
feat: adding gui to tx-submit-api (#251)
* feat: adding gui to tx-submit-api Signed-off-by: Brian Lee <[email protected]> * fix: port 8081 should work on remote hosts Signed-off-by: Brian Lee <[email protected]> --------- Signed-off-by: Brian Lee <[email protected]>
1 parent e937f26 commit 839e674

File tree

1 file changed

+385
-12
lines changed

1 file changed

+385
-12
lines changed

internal/api/static/index.html

Lines changed: 385 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,387 @@
11
<!DOCTYPE html>
2-
<html>
3-
<head>
4-
<title>Tx Submit API</title>
5-
</head>
6-
<body>
7-
<p align="center">
8-
<img src="txsubmit-logo.png" />
9-
</p>
10-
<p align="center">
11-
GitHub: <a href="https://github.com/blinklabs-io/tx-submit-api">https://github.com/blinklabs-io/tx-submit-api</a>
12-
</p>
13-
</body>
2+
<html lang="en" class="bg-black h-full">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Metrics Dashboard</title>
7+
<script src="https://cdnjs.cloudflare.com/ajax/libs/htmx/1.9.10/htmx.min.js"></script>
8+
<script src="https://cdn.tailwindcss.com"></script>
9+
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.1/chart.min.js"></script>
10+
<script>
11+
tailwind.config = {
12+
theme: {
13+
extend: {
14+
colors: {
15+
terminal: {
16+
text: '#F0F0F0',
17+
bg: '#1E1E1E',
18+
header: '#0F3B82',
19+
},
20+
},
21+
},
22+
},
23+
};
24+
</script>
25+
<style>
26+
.terminal-3d {
27+
transform: perspective(1000px);
28+
box-shadow: 0 10px 30px -5px rgba(0, 0, 0, 0.5),
29+
20px 20px 60px -10px rgba(0, 0, 0, 0.3),
30+
-20px -20px 60px -10px rgba(255, 255, 255, 0.1);
31+
transition: all 0.3s ease;
32+
}
33+
.terminal-3d:hover {
34+
transform: perspective(1000px) rotateX(0deg) rotateY(0deg);
35+
}
36+
</style>
37+
</head>
38+
<body
39+
class="bg-gradient-to-br from-gray-900 to-black text-terminal-text font-mono h-full flex items-center justify-center p-4"
40+
>
41+
<div
42+
class="w-full max-w-6xl bg-terminal-bg rounded-lg overflow-hidden terminal-3d"
43+
>
44+
<div class="bg-terminal-header p-2 flex items-center">
45+
<div class="flex space-x-2">
46+
<div class="w-3 h-3 rounded-full bg-red-500"></div>
47+
<div class="w-3 h-3 rounded-full bg-yellow-500"></div>
48+
<div class="w-3 h-3 rounded-full bg-green-500"></div>
49+
</div>
50+
<div class="flex-grow text-center text-sm">Blink Labs Software</div>
51+
</div>
52+
<div class="p-6 h-[800px] overflow-y-auto">
53+
<div class="text-sm mb-4">
54+
<br />
55+
Blink Labs Software: tx-submit-api
56+
</div>
57+
<div
58+
id="metrics-container"
59+
class="flex items-center flex-wrap font-mono gap-4 whitespace-pre mb-4"
60+
hx-get="javascript:window.location.protocol + '//' + window.location.hostname + ':8081/'"
61+
hx-trigger="load, every 1s"
62+
hx-swap="innerHTML"
63+
>
64+
Loading metrics...
65+
</div>
66+
<div class="grid grid-cols-2 gap-8">
67+
<div id="memory-chart"><canvas></canvas></div>
68+
<div id="process-chart"><canvas></canvas></div>
69+
<div id="go-runtime-chart"><canvas></canvas></div>
70+
<div id="tx-chart"><canvas></canvas></div>
71+
</div>
72+
</div>
73+
</div>
74+
75+
<script>
76+
let charts = {};
77+
78+
function initializeCharts() {
79+
charts.memory = createChart('memory-chart', {
80+
type: 'bar',
81+
data: {
82+
labels: ['Alloc Bytes', 'Sys Bytes', 'GC Sys Bytes'],
83+
datasets: [
84+
{
85+
label: 'Memory Usage',
86+
data: [0, 0, 0],
87+
backgroundColor: [
88+
'rgba(255, 99, 132, 0.2)',
89+
'rgba(54, 162, 235, 0.2)',
90+
'rgba(255, 206, 86, 0.2)',
91+
],
92+
borderColor: [
93+
'rgba(255, 99, 132, 1)',
94+
'rgba(54, 162, 235, 1)',
95+
'rgba(255, 206, 86, 1)',
96+
],
97+
borderWidth: 1,
98+
},
99+
],
100+
},
101+
options: getCommonOptions('Memory Usage', true),
102+
});
103+
104+
charts.process = createChart('process-chart', {
105+
type: 'bar',
106+
data: {
107+
labels: ['CPU Seconds', 'Open FDs'],
108+
datasets: [
109+
{
110+
data: [0, 0],
111+
backgroundColor: [
112+
'rgba(255, 99, 132, 0.2)',
113+
'rgba(54, 162, 235, 0.2)',
114+
],
115+
borderColor: ['rgba(255, 99, 132, 1)', 'rgba(54, 162, 235, 1)'],
116+
borderWidth: 1,
117+
},
118+
],
119+
},
120+
options: {
121+
...getCommonOptions('Process Metrics', false, false),
122+
scales: {
123+
y: {
124+
beginAtZero: true,
125+
max: 15,
126+
ticks: {
127+
stepSize: 1,
128+
callback: function (value) {
129+
return value % 3 === 0 ? value : '';
130+
},
131+
color: 'white',
132+
},
133+
},
134+
x: {
135+
ticks: {
136+
color: 'white',
137+
},
138+
},
139+
},
140+
},
141+
});
142+
143+
charts.goRuntime = createChart('go-runtime-chart', {
144+
type: 'bar',
145+
data: {
146+
labels: ['Goroutines', 'Threads'],
147+
datasets: [
148+
{
149+
data: [0, 0, 0],
150+
backgroundColor: ['#00FFFF33', '#1E90FF33'],
151+
borderColor: ['#00FFFF', '#1E90FF'],
152+
borderWidth: 0.5,
153+
},
154+
],
155+
},
156+
options: {
157+
...getCommonOptions('Go Runtime Metrics'),
158+
scales: {
159+
y: {
160+
beginAtZero: true,
161+
max: 15,
162+
ticks: {
163+
stepSize: 5,
164+
callback: function (value) {
165+
return value % 5 === 0 ? value : '';
166+
},
167+
color: 'white',
168+
},
169+
},
170+
x: {
171+
ticks: {
172+
color: 'white',
173+
},
174+
},
175+
},
176+
},
177+
});
178+
179+
charts.tx = createChart('tx-chart', {
180+
type: 'bar',
181+
data: {
182+
labels: ['Submit Count', 'Submit Fail Count'],
183+
datasets: [
184+
{
185+
label: 'Transactions',
186+
data: [0, 0],
187+
backgroundColor: [
188+
'rgba(75, 192, 192, 0.2)',
189+
'rgba(255, 99, 132, 0.2)',
190+
],
191+
borderColor: ['rgba(75, 192, 192, 1)', 'rgba(255, 99, 132, 1)'],
192+
borderWidth: 1,
193+
},
194+
],
195+
},
196+
options: {
197+
...getCommonOptions('Transaction Metrics'),
198+
scales: {
199+
y: {
200+
beginAtZero: true,
201+
max: 20,
202+
ticks: {
203+
stepSize: 5,
204+
callback: function (value) {
205+
return value % 5 === 0 ? value : '';
206+
},
207+
color: 'white',
208+
},
209+
},
210+
x: {
211+
ticks: {
212+
color: 'white',
213+
},
214+
},
215+
},
216+
},
217+
});
218+
}
219+
220+
function createChart(elementId, config) {
221+
const ctx = document
222+
.querySelector(`#${elementId} canvas`)
223+
.getContext('2d');
224+
return new Chart(ctx, config);
225+
}
226+
227+
function getCommonOptions(
228+
title,
229+
useCustomYAxis = false,
230+
useLegend = false
231+
) {
232+
const options = {
233+
responsive: true,
234+
animation: {
235+
duration: 700,
236+
},
237+
scales: {
238+
y: {
239+
beginAtZero: true,
240+
ticks: {
241+
color: 'white',
242+
},
243+
},
244+
x: {
245+
ticks: {
246+
color: 'white',
247+
},
248+
},
249+
},
250+
plugins: {
251+
title: {
252+
display: true,
253+
text: title,
254+
color: 'white',
255+
},
256+
legend: {
257+
display: useLegend,
258+
position: 'bottom',
259+
labels: {
260+
color: 'white',
261+
},
262+
},
263+
},
264+
};
265+
266+
if (useCustomYAxis) {
267+
options.scales.y.ticks.callback = function (value) {
268+
return (value / 1024 / 1024).toFixed(2) + ' MB';
269+
};
270+
}
271+
272+
return options;
273+
}
274+
275+
function updateCharts(metrics) {
276+
charts.memory.data.datasets[0].data = [
277+
metrics.go_memstats_alloc_bytes,
278+
metrics.go_memstats_sys_bytes,
279+
metrics.go_memstats_gc_sys_bytes,
280+
];
281+
charts.memory.update();
282+
283+
charts.process.data.datasets[0].data = [
284+
metrics.process_cpu_seconds_total || 0,
285+
metrics.process_open_fds || 0,
286+
];
287+
charts.process.update();
288+
289+
charts.goRuntime.data.datasets[0].data = [
290+
metrics.go_goroutines,
291+
metrics.go_threads,
292+
];
293+
charts.goRuntime.update();
294+
295+
charts.tx.data.datasets[0].data = [
296+
metrics.tx_submit_count,
297+
metrics.tx_submit_fail_count,
298+
];
299+
300+
charts.tx.update();
301+
}
302+
303+
function formatMetrics(metrics) {
304+
return Object.entries(metrics)
305+
.map(
306+
([key, value]) =>
307+
`<span class="text-green-400">${key}</span> <span class="text-yellow-300">${
308+
value === null ? 'null' : value
309+
}</span>`
310+
)
311+
.join('\n');
312+
}
313+
314+
function getMetricValue(data, metricName) {
315+
const regex = new RegExp(`^${metricName}\\s+([\\d\\.e+-]+)`, 'm');
316+
const match = data.match(regex);
317+
return match ? parseFloat(match[1]) : null;
318+
}
319+
320+
function parseMetrics(metricsData) {
321+
return {
322+
go_goroutines: getMetricValue(metricsData, 'go_goroutines'),
323+
go_memstats_alloc_bytes: getMetricValue(
324+
metricsData,
325+
'go_memstats_alloc_bytes'
326+
),
327+
go_memstats_sys_bytes: getMetricValue(
328+
metricsData,
329+
'go_memstats_sys_bytes'
330+
),
331+
process_cpu_seconds_total: getMetricValue(
332+
metricsData,
333+
'process_cpu_seconds_total'
334+
),
335+
process_resident_memory_bytes: getMetricValue(
336+
metricsData,
337+
'process_resident_memory_bytes'
338+
),
339+
process_open_fds: getMetricValue(metricsData, 'process_open_fds'),
340+
go_threads: getMetricValue(metricsData, 'go_threads'),
341+
go_memstats_gc_sys_bytes: getMetricValue(
342+
metricsData,
343+
'go_memstats_gc_sys_bytes'
344+
),
345+
tx_submit_count: getMetricValue(metricsData, 'tx_submit_count'),
346+
tx_submit_fail_count: getMetricValue(
347+
metricsData,
348+
'tx_submit_fail_count'
349+
),
350+
};
351+
}
352+
353+
document.addEventListener('DOMContentLoaded', function () {
354+
initializeCharts();
355+
htmx.config.defaultHeaders = {};
356+
htmx.config.useTemplateFragments = true;
357+
});
358+
359+
htmx.on('htmx:afterRequest', function (evt) {
360+
if (evt.detail.elt.id === 'metrics-container') {
361+
if (evt.detail.failed) {
362+
console.error('Failed to load metrics');
363+
evt.detail.elt.innerHTML =
364+
'<p class="text-red-500">Failed to load metrics. Please check your connection.</p>';
365+
} else if (evt.detail.xhr.status === 200) {
366+
try {
367+
const metricsData = evt.detail.xhr.response;
368+
const parsedMetrics = parseMetrics(metricsData);
369+
370+
evt.detail.elt.innerHTML = formatMetrics(parsedMetrics);
371+
372+
updateCharts(parsedMetrics);
373+
374+
console.log(
375+
'Metrics updated at: ' + new Date().toLocaleTimeString()
376+
);
377+
} catch (error) {
378+
console.error('Error parsing metrics data:', error);
379+
evt.detail.elt.innerHTML =
380+
'<p class="text-red-500">Error parsing metrics data. Please check the server response.</p>';
381+
}
382+
}
383+
}
384+
});
385+
</script>
386+
</body>
14387
</html>

0 commit comments

Comments
 (0)