dashboard.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. package dashboard
  2. import (
  3. "net/http"
  4. )
  5. type Handler struct{}
  6. func NewHandler() *Handler {
  7. return &Handler{}
  8. }
  9. func (h *Handler) ServeIndex(w http.ResponseWriter, r *http.Request) {
  10. html := `<!DOCTYPE html>
  11. <html lang="en">
  12. <head>
  13. <meta charset="UTF-8">
  14. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  15. <title>SeaweedFS Telemetry Dashboard</title>
  16. <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
  17. <style>
  18. body {
  19. font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  20. margin: 0;
  21. padding: 20px;
  22. background-color: #f5f5f5;
  23. }
  24. .container {
  25. max-width: 1200px;
  26. margin: 0 auto;
  27. }
  28. .header {
  29. background: white;
  30. padding: 20px;
  31. border-radius: 8px;
  32. margin-bottom: 20px;
  33. box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  34. }
  35. .stats-grid {
  36. display: grid;
  37. grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  38. gap: 20px;
  39. margin-bottom: 20px;
  40. }
  41. .stat-card {
  42. background: white;
  43. padding: 20px;
  44. border-radius: 8px;
  45. box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  46. }
  47. .stat-value {
  48. font-size: 2em;
  49. font-weight: bold;
  50. color: #2196F3;
  51. }
  52. .stat-label {
  53. color: #666;
  54. margin-top: 5px;
  55. }
  56. .chart-container {
  57. background: white;
  58. padding: 20px;
  59. border-radius: 8px;
  60. margin-bottom: 20px;
  61. box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  62. }
  63. .chart-title {
  64. font-size: 1.2em;
  65. font-weight: bold;
  66. margin-bottom: 15px;
  67. }
  68. .loading {
  69. text-align: center;
  70. padding: 40px;
  71. color: #666;
  72. }
  73. .error {
  74. background: #ffebee;
  75. color: #c62828;
  76. padding: 15px;
  77. border-radius: 4px;
  78. margin: 10px 0;
  79. }
  80. </style>
  81. </head>
  82. <body>
  83. <div class="container">
  84. <div class="header">
  85. <h1>SeaweedFS Telemetry Dashboard</h1>
  86. <p>Privacy-respecting usage analytics for SeaweedFS</p>
  87. </div>
  88. <div id="loading" class="loading">Loading telemetry data...</div>
  89. <div id="error" class="error" style="display: none;"></div>
  90. <div id="dashboard" style="display: none;">
  91. <div class="stats-grid">
  92. <div class="stat-card">
  93. <div class="stat-value" id="totalInstances">-</div>
  94. <div class="stat-label">Total Instances (30 days)</div>
  95. </div>
  96. <div class="stat-card">
  97. <div class="stat-value" id="activeInstances">-</div>
  98. <div class="stat-label">Active Instances (7 days)</div>
  99. </div>
  100. <div class="stat-card">
  101. <div class="stat-value" id="totalVersions">-</div>
  102. <div class="stat-label">Different Versions</div>
  103. </div>
  104. <div class="stat-card">
  105. <div class="stat-value" id="totalOS">-</div>
  106. <div class="stat-label">Operating Systems</div>
  107. </div>
  108. </div>
  109. <div class="chart-container">
  110. <div class="chart-title">Version Distribution</div>
  111. <canvas id="versionChart" width="400" height="200"></canvas>
  112. </div>
  113. <div class="chart-container">
  114. <div class="chart-title">Operating System Distribution</div>
  115. <canvas id="osChart" width="400" height="200"></canvas>
  116. </div>
  117. <div class="chart-container">
  118. <div class="chart-title">Volume Servers Over Time</div>
  119. <canvas id="serverChart" width="400" height="200"></canvas>
  120. </div>
  121. <div class="chart-container">
  122. <div class="chart-title">Total Disk Usage Over Time</div>
  123. <canvas id="diskChart" width="400" height="200"></canvas>
  124. </div>
  125. </div>
  126. </div>
  127. <script>
  128. let charts = {};
  129. async function loadDashboard() {
  130. try {
  131. // Load stats
  132. const statsResponse = await fetch('/api/stats');
  133. const stats = await statsResponse.json();
  134. // Load metrics
  135. const metricsResponse = await fetch('/api/metrics?days=30');
  136. const metrics = await metricsResponse.json();
  137. updateStats(stats);
  138. updateCharts(stats, metrics);
  139. document.getElementById('loading').style.display = 'none';
  140. document.getElementById('dashboard').style.display = 'block';
  141. } catch (error) {
  142. console.error('Error loading dashboard:', error);
  143. showError('Failed to load telemetry data: ' + error.message);
  144. }
  145. }
  146. function updateStats(stats) {
  147. document.getElementById('totalInstances').textContent = stats.total_instances || 0;
  148. document.getElementById('activeInstances').textContent = stats.active_instances || 0;
  149. document.getElementById('totalVersions').textContent = Object.keys(stats.versions || {}).length;
  150. document.getElementById('totalOS').textContent = Object.keys(stats.os_distribution || {}).length;
  151. }
  152. function updateCharts(stats, metrics) {
  153. // Version chart
  154. createPieChart('versionChart', 'Version Distribution', stats.versions || {});
  155. // OS chart
  156. createPieChart('osChart', 'Operating System Distribution', stats.os_distribution || {});
  157. // Server count over time
  158. if (metrics.dates && metrics.server_counts) {
  159. createLineChart('serverChart', 'Volume Servers', metrics.dates, metrics.server_counts, '#2196F3');
  160. }
  161. // Disk usage over time
  162. if (metrics.dates && metrics.disk_usage) {
  163. const diskUsageGB = metrics.disk_usage.map(bytes => Math.round(bytes / (1024 * 1024 * 1024)));
  164. createLineChart('diskChart', 'Disk Usage (GB)', metrics.dates, diskUsageGB, '#4CAF50');
  165. }
  166. }
  167. function createPieChart(canvasId, title, data) {
  168. const ctx = document.getElementById(canvasId).getContext('2d');
  169. if (charts[canvasId]) {
  170. charts[canvasId].destroy();
  171. }
  172. const labels = Object.keys(data);
  173. const values = Object.values(data);
  174. charts[canvasId] = new Chart(ctx, {
  175. type: 'pie',
  176. data: {
  177. labels: labels,
  178. datasets: [{
  179. data: values,
  180. backgroundColor: [
  181. '#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0',
  182. '#9966FF', '#FF9F40', '#FF6384', '#C9CBCF'
  183. ]
  184. }]
  185. },
  186. options: {
  187. responsive: true,
  188. plugins: {
  189. legend: {
  190. position: 'bottom'
  191. }
  192. }
  193. }
  194. });
  195. }
  196. function createLineChart(canvasId, label, labels, data, color) {
  197. const ctx = document.getElementById(canvasId).getContext('2d');
  198. if (charts[canvasId]) {
  199. charts[canvasId].destroy();
  200. }
  201. charts[canvasId] = new Chart(ctx, {
  202. type: 'line',
  203. data: {
  204. labels: labels,
  205. datasets: [{
  206. label: label,
  207. data: data,
  208. borderColor: color,
  209. backgroundColor: color + '20',
  210. fill: true,
  211. tension: 0.1
  212. }]
  213. },
  214. options: {
  215. responsive: true,
  216. scales: {
  217. y: {
  218. beginAtZero: true
  219. }
  220. }
  221. }
  222. });
  223. }
  224. function showError(message) {
  225. document.getElementById('loading').style.display = 'none';
  226. document.getElementById('error').style.display = 'block';
  227. document.getElementById('error').textContent = message;
  228. }
  229. // Load dashboard on page load
  230. loadDashboard();
  231. // Refresh every 5 minutes
  232. setInterval(loadDashboard, 5 * 60 * 1000);
  233. </script>
  234. </body>
  235. </html>`
  236. w.Header().Set("Content-Type", "text/html")
  237. w.WriteHeader(http.StatusOK)
  238. w.Write([]byte(html))
  239. }