a cache for slack profile pictures and emojis

chore: fix license url and add github to dashboard

dunkirk.sh 25b1a4dd f9d5fc5e

verified
Changed files
+763 -808
src
+762 -806
src/dashboard.html
··· 1 1 <!doctype html> 2 2 <html lang="en"> 3 - <head> 4 - <meta charset="UTF-8" /> 5 - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 - <title>Cachet Analytics Dashboard</title> 7 - <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> 8 - <style> 9 - * { 10 - margin: 0; 11 - padding: 0; 12 - box-sizing: border-box; 13 - } 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 + <title>Cachet Analytics Dashboard</title> 7 + <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> 8 + <style> 9 + * { 10 + margin: 0; 11 + padding: 0; 12 + box-sizing: border-box; 13 + } 14 14 15 - body { 16 - font-family: 17 - -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, 18 - sans-serif; 19 - background: #f5f5f5; 20 - color: #333; 21 - } 15 + body { 16 + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, 17 + sans-serif; 18 + background: #f5f5f5; 19 + color: #333; 20 + } 22 21 23 - .header { 24 - background: #fff; 25 - padding: 1rem 2rem; 26 - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 27 - margin-bottom: 2rem; 28 - display: flex; 29 - justify-content: space-between; 30 - align-items: center; 31 - } 22 + .header { 23 + background: #fff; 24 + padding: 1rem 2rem; 25 + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 26 + margin-bottom: 2rem; 27 + display: flex; 28 + justify-content: space-between; 29 + align-items: center; 30 + } 32 31 33 - .header h1 { 34 - color: #2c3e50; 35 - } 32 + .header h1 { 33 + color: #2c3e50; 34 + } 36 35 37 - .header-links a { 38 - margin-left: 1rem; 39 - color: #3498db; 40 - text-decoration: none; 41 - } 36 + .header-links a { 37 + margin-left: 1rem; 38 + color: #3498db; 39 + text-decoration: none; 40 + } 42 41 43 - .header-links a:hover { 44 - text-decoration: underline; 45 - } 42 + .header-links a:hover { 43 + text-decoration: underline; 44 + } 46 45 47 - .controls { 48 - margin-bottom: 2rem; 49 - text-align: center; 50 - } 46 + .controls { 47 + margin-bottom: 2rem; 48 + text-align: center; 49 + } 51 50 52 - .controls select, 53 - .controls button { 54 - padding: 0.5rem 1rem; 55 - margin: 0 0.5rem; 56 - border: 1px solid #ddd; 57 - border-radius: 4px; 58 - background: white; 59 - cursor: pointer; 60 - } 51 + .controls select, 52 + .controls button { 53 + padding: 0.5rem 1rem; 54 + margin: 0 0.5rem; 55 + border: 1px solid #ddd; 56 + border-radius: 4px; 57 + background: white; 58 + cursor: pointer; 59 + } 61 60 62 - .controls button { 63 - background: #3498db; 64 - color: white; 65 - border: none; 66 - } 61 + .controls button { 62 + background: #3498db; 63 + color: white; 64 + border: none; 65 + } 67 66 68 - .controls button:hover { 69 - background: #2980b9; 70 - } 67 + .controls button:hover { 68 + background: #2980b9; 69 + } 71 70 72 - .dashboard { 73 - max-width: 1200px; 74 - margin: 0 auto; 75 - padding: 0 2rem; 76 - } 71 + .dashboard { 72 + max-width: 1200px; 73 + margin: 0 auto; 74 + padding: 0 2rem; 75 + } 77 76 78 - .stats-grid { 79 - display: grid; 80 - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); 81 - gap: 1rem; 82 - margin-bottom: 2rem; 83 - } 77 + .stats-grid { 78 + display: grid; 79 + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); 80 + gap: 1rem; 81 + margin-bottom: 2rem; 82 + } 84 83 85 - .stat-card { 86 - background: white; 87 - padding: 1.5rem; 88 - border-radius: 8px; 89 - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 90 - text-align: center; 91 - } 84 + .stat-card { 85 + background: white; 86 + padding: 1.5rem; 87 + border-radius: 8px; 88 + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 89 + text-align: center; 90 + } 92 91 93 - .stat-number { 94 - font-size: 2rem; 95 - font-weight: bold; 96 - color: #3498db; 97 - } 92 + .stat-number { 93 + font-size: 2rem; 94 + font-weight: bold; 95 + color: #3498db; 96 + } 98 97 99 - .stat-label { 100 - color: #666; 101 - margin-top: 0.5rem; 102 - } 98 + .stat-label { 99 + color: #666; 100 + margin-top: 0.5rem; 101 + } 103 102 104 - .charts-grid { 105 - display: grid; 106 - grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); 107 - gap: 2rem; 108 - margin-bottom: 2rem; 109 - } 103 + .charts-grid { 104 + display: grid; 105 + grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); 106 + gap: 2rem; 107 + margin-bottom: 2rem; 108 + } 110 109 111 - .chart-container { 112 - background: white; 113 - padding: 1.5rem; 114 - border-radius: 8px; 115 - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 116 - } 110 + .chart-container { 111 + background: white; 112 + padding: 1.5rem; 113 + border-radius: 8px; 114 + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 115 + } 117 116 118 - .chart-title { 119 - font-size: 1.2rem; 120 - margin-bottom: 1rem; 121 - color: #2c3e50; 122 - } 117 + .chart-title { 118 + font-size: 1.2rem; 119 + margin-bottom: 1rem; 120 + color: #2c3e50; 121 + } 123 122 124 - .loading { 125 - text-align: center; 126 - padding: 2rem; 127 - color: #666; 128 - } 123 + .loading { 124 + text-align: center; 125 + padding: 2rem; 126 + color: #666; 127 + } 129 128 130 - .error { 131 - background: #e74c3c; 132 - color: white; 133 - padding: 1rem; 134 - border-radius: 4px; 135 - margin: 1rem 0; 136 - } 129 + .error { 130 + background: #e74c3c; 131 + color: white; 132 + padding: 1rem; 133 + border-radius: 4px; 134 + margin: 1rem 0; 135 + } 137 136 138 - .auto-refresh { 139 - display: flex; 140 - align-items: center; 141 - gap: 0.5rem; 142 - justify-content: center; 143 - margin-top: 1rem; 144 - } 137 + .auto-refresh { 138 + display: flex; 139 + align-items: center; 140 + gap: 0.5rem; 141 + justify-content: center; 142 + margin-top: 1rem; 143 + } 145 144 146 - .auto-refresh input[type="checkbox"] { 147 - transform: scale(1.2); 148 - } 145 + .auto-refresh input[type="checkbox"] { 146 + transform: scale(1.2); 147 + } 149 148 150 - @media (max-width: 768px) { 151 - .charts-grid { 152 - grid-template-columns: 1fr; 153 - } 149 + @media (max-width: 768px) { 150 + .charts-grid { 151 + grid-template-columns: 1fr; 152 + } 154 153 155 - .dashboard { 156 - padding: 0 1rem; 157 - } 154 + .dashboard { 155 + padding: 0 1rem; 156 + } 158 157 159 - .header { 160 - flex-direction: column; 161 - gap: 1rem; 162 - text-align: center; 163 - } 164 - } 165 - </style> 166 - </head> 167 - <body> 168 - <div class="header"> 169 - <h1>📊 Cachet Analytics Dashboard</h1> 170 - <div class="header-links"> 171 - <a href="/swagger">API Docs</a> 172 - <a href="/stats">Raw Stats</a> 173 - </div> 174 - </div> 158 + .header { 159 + flex-direction: column; 160 + gap: 1rem; 161 + text-align: center; 162 + } 163 + } 164 + </style> 165 + </head> 166 + <body> 167 + <div class="header"> 168 + <h1>📊 Cachet Analytics Dashboard</h1> 169 + <div class="header-links"> 170 + <a href="https://github.com/taciturnaxolotl/cachet">Github</a> 171 + <a href="/swagger">API Docs</a> 172 + <a href="/stats">Raw Stats</a> 173 + </div> 174 + </div> 175 175 176 - <div class="dashboard"> 177 - <div class="controls"> 178 - <select id="daysSelect"> 179 - <option value="1">Last 24 hours</option> 180 - <option value="7" selected>Last 7 days</option> 181 - <option value="30">Last 30 days</option> 182 - </select> 183 - <button onclick="loadData()">Refresh</button> 184 - <div class="auto-refresh"> 185 - <input type="checkbox" id="autoRefresh" /> 186 - <label for="autoRefresh">Auto-refresh (30s)</label> 187 - </div> 188 - </div> 176 + <div class="dashboard"> 177 + <div class="controls"> 178 + <select id="daysSelect"> 179 + <option value="1">Last 24 hours</option> 180 + <option value="7" selected>Last 7 days</option> 181 + <option value="30">Last 30 days</option> 182 + </select> 183 + <button onclick="loadData()">Refresh</button> 184 + <div class="auto-refresh"> 185 + <input type="checkbox" id="autoRefresh" /> 186 + <label for="autoRefresh">Auto-refresh (30s)</label> 187 + </div> 188 + </div> 189 189 190 - <div id="loading" class="loading">Loading analytics data...</div> 191 - <div id="error" class="error" style="display: none"></div> 190 + <div id="loading" class="loading">Loading analytics data...</div> 191 + <div id="error" class="error" style="display: none"></div> 192 192 193 - <div id="content" style="display: none"> 194 - <div 195 - class="chart-container" 196 - style="margin-bottom: 2rem; height: 450px" 197 - > 198 - <div class="chart-title"> 199 - Traffic Overview - All Routes Over Time 200 - </div> 201 - <canvas 202 - id="trafficOverviewChart" 203 - style="padding-bottom: 2rem" 204 - ></canvas> 205 - </div> 193 + <div id="content" style="display: none"> 194 + <div class="chart-container" style="margin-bottom: 2rem; height: 450px"> 195 + <div class="chart-title">Traffic Overview - All Routes Over Time</div> 196 + <canvas 197 + id="trafficOverviewChart" 198 + style="padding-bottom: 2rem" 199 + ></canvas> 200 + </div> 206 201 207 - <div class="stats-grid"> 208 - <div class="stat-card"> 209 - <div class="stat-number" id="totalRequests">-</div> 210 - <div class="stat-label">Total Requests</div> 211 - </div> 212 - <div class="stat-card"> 213 - <div class="stat-number" id="avgResponseTime">-</div> 214 - <div class="stat-label">Avg Response Time (ms)</div> 215 - </div> 216 - <div class="stat-card"> 217 - <div class="stat-number" id="p95ResponseTime">-</div> 218 - <div class="stat-label">P95 Response Time (ms)</div> 219 - </div> 220 - <div class="stat-card"> 221 - <div class="stat-number" id="uniqueEndpoints">-</div> 222 - <div class="stat-label">Unique Endpoints</div> 223 - </div> 224 - <div class="stat-card"> 225 - <div class="stat-number" id="errorRate">-</div> 226 - <div class="stat-label">Error Rate (%)</div> 227 - </div> 228 - <div class="stat-card"> 229 - <div class="stat-number" id="fastRequests">-</div> 230 - <div class="stat-label">Fast Requests (&lt;100ms)</div> 231 - </div> 232 - <div class="stat-card"> 233 - <div class="stat-number" id="uptime">-</div> 234 - <div class="stat-label">Uptime (%)</div> 235 - </div> 236 - <div class="stat-card"> 237 - <div class="stat-number" id="throughput">-</div> 238 - <div class="stat-label">Throughput (req/hr)</div> 239 - </div> 240 - <div class="stat-card"> 241 - <div class="stat-number" id="apdex">-</div> 242 - <div class="stat-label">APDEX Score</div> 243 - </div> 244 - <div class="stat-card"> 245 - <div class="stat-number" id="cacheHitRate">-</div> 246 - <div class="stat-label">Cache Hit Rate (%)</div> 247 - </div> 248 - </div> 202 + <div class="stats-grid"> 203 + <div class="stat-card"> 204 + <div class="stat-number" id="totalRequests">-</div> 205 + <div class="stat-label">Total Requests</div> 206 + </div> 207 + <div class="stat-card"> 208 + <div class="stat-number" id="avgResponseTime">-</div> 209 + <div class="stat-label">Avg Response Time (ms)</div> 210 + </div> 211 + <div class="stat-card"> 212 + <div class="stat-number" id="p95ResponseTime">-</div> 213 + <div class="stat-label">P95 Response Time (ms)</div> 214 + </div> 215 + <div class="stat-card"> 216 + <div class="stat-number" id="uniqueEndpoints">-</div> 217 + <div class="stat-label">Unique Endpoints</div> 218 + </div> 219 + <div class="stat-card"> 220 + <div class="stat-number" id="errorRate">-</div> 221 + <div class="stat-label">Error Rate (%)</div> 222 + </div> 223 + <div class="stat-card"> 224 + <div class="stat-number" id="fastRequests">-</div> 225 + <div class="stat-label">Fast Requests (&lt;100ms)</div> 226 + </div> 227 + <div class="stat-card"> 228 + <div class="stat-number" id="uptime">-</div> 229 + <div class="stat-label">Uptime (%)</div> 230 + </div> 231 + <div class="stat-card"> 232 + <div class="stat-number" id="throughput">-</div> 233 + <div class="stat-label">Throughput (req/hr)</div> 234 + </div> 235 + <div class="stat-card"> 236 + <div class="stat-number" id="apdex">-</div> 237 + <div class="stat-label">APDEX Score</div> 238 + </div> 239 + <div class="stat-card"> 240 + <div class="stat-number" id="cacheHitRate">-</div> 241 + <div class="stat-label">Cache Hit Rate (%)</div> 242 + </div> 243 + </div> 249 244 250 - <div class="stats-grid"> 251 - <div class="stat-card"> 252 - <div class="stat-number" id="peakHour">-</div> 253 - <div class="stat-label">Peak Hour</div> 254 - </div> 255 - <div class="stat-card"> 256 - <div class="stat-number" id="peakHourRequests">-</div> 257 - <div class="stat-label">Peak Hour Requests</div> 258 - </div> 259 - <div class="stat-card"> 260 - <div class="stat-number" id="peakDay">-</div> 261 - <div class="stat-label">Peak Day</div> 262 - </div> 263 - <div class="stat-card"> 264 - <div class="stat-number" id="peakDayRequests">-</div> 265 - <div class="stat-label">Peak Day Requests</div> 266 - </div> 267 - <div class="stat-card"> 268 - <div class="stat-number" id="dashboardRequests">-</div> 269 - <div class="stat-label">Dashboard Requests</div> 270 - </div> 271 - </div> 245 + <div class="stats-grid"> 246 + <div class="stat-card"> 247 + <div class="stat-number" id="peakHour">-</div> 248 + <div class="stat-label">Peak Hour</div> 249 + </div> 250 + <div class="stat-card"> 251 + <div class="stat-number" id="peakHourRequests">-</div> 252 + <div class="stat-label">Peak Hour Requests</div> 253 + </div> 254 + <div class="stat-card"> 255 + <div class="stat-number" id="peakDay">-</div> 256 + <div class="stat-label">Peak Day</div> 257 + </div> 258 + <div class="stat-card"> 259 + <div class="stat-number" id="peakDayRequests">-</div> 260 + <div class="stat-label">Peak Day Requests</div> 261 + </div> 262 + <div class="stat-card"> 263 + <div class="stat-number" id="dashboardRequests">-</div> 264 + <div class="stat-label">Dashboard Requests</div> 265 + </div> 266 + </div> 272 267 273 - <div class="charts-grid"> 274 - <div class="chart-container"> 275 - <div class="chart-title">Requests Over Time</div> 276 - <canvas id="timeChart"></canvas> 277 - </div> 268 + <div class="charts-grid"> 269 + <div class="chart-container"> 270 + <div class="chart-title">Requests Over Time</div> 271 + <canvas id="timeChart"></canvas> 272 + </div> 278 273 279 - <div class="chart-container"> 280 - <div class="chart-title"> 281 - Latency Over Time (Hourly) 282 - </div> 283 - <canvas id="latencyTimeChart"></canvas> 284 - </div> 274 + <div class="chart-container"> 275 + <div class="chart-title">Latency Over Time (Hourly)</div> 276 + <canvas id="latencyTimeChart"></canvas> 277 + </div> 285 278 286 - <div class="chart-container"> 287 - <div class="chart-title"> 288 - Response Time Distribution 289 - </div> 290 - <canvas id="latencyDistributionChart"></canvas> 291 - </div> 279 + <div class="chart-container"> 280 + <div class="chart-title">Response Time Distribution</div> 281 + <canvas id="latencyDistributionChart"></canvas> 282 + </div> 292 283 293 - <div class="chart-container"> 294 - <div class="chart-title">Latency Percentiles</div> 295 - <canvas id="percentilesChart"></canvas> 296 - </div> 284 + <div class="chart-container"> 285 + <div class="chart-title">Latency Percentiles</div> 286 + <canvas id="percentilesChart"></canvas> 287 + </div> 297 288 298 - <div class="chart-container"> 299 - <div class="chart-title">Top Endpoints</div> 300 - <canvas id="endpointChart"></canvas> 301 - </div> 289 + <div class="chart-container"> 290 + <div class="chart-title">Top Endpoints</div> 291 + <canvas id="endpointChart"></canvas> 292 + </div> 302 293 303 - <div class="chart-container"> 304 - <div class="chart-title">Slowest Endpoints</div> 305 - <canvas id="slowestEndpointsChart"></canvas> 306 - </div> 294 + <div class="chart-container"> 295 + <div class="chart-title">Slowest Endpoints</div> 296 + <canvas id="slowestEndpointsChart"></canvas> 297 + </div> 307 298 308 - <div class="chart-container"> 309 - <div class="chart-title">Status Codes</div> 310 - <canvas id="statusChart"></canvas> 311 - </div> 299 + <div class="chart-container"> 300 + <div class="chart-title">Status Codes</div> 301 + <canvas id="statusChart"></canvas> 302 + </div> 312 303 313 - <div class="chart-container"> 314 - <div class="chart-title">Top User Agents</div> 315 - <canvas id="userAgentChart"></canvas> 316 - </div> 317 - </div> 318 - </div> 304 + <div class="chart-container"> 305 + <div class="chart-title">Top User Agents</div> 306 + <canvas id="userAgentChart"></canvas> 307 + </div> 319 308 </div> 309 + </div> 310 + </div> 320 311 321 - <script> 322 - let charts = {}; 323 - let autoRefreshInterval; 312 + <script> 313 + let charts = {}; 314 + let autoRefreshInterval; 324 315 325 - async function loadData() { 326 - const days = document.getElementById("daysSelect").value; 327 - const loading = document.getElementById("loading"); 328 - const error = document.getElementById("error"); 329 - const content = document.getElementById("content"); 316 + async function loadData() { 317 + const days = document.getElementById("daysSelect").value; 318 + const loading = document.getElementById("loading"); 319 + const error = document.getElementById("error"); 320 + const content = document.getElementById("content"); 330 321 331 - loading.style.display = "block"; 332 - error.style.display = "none"; 333 - content.style.display = "none"; 322 + loading.style.display = "block"; 323 + error.style.display = "none"; 324 + content.style.display = "none"; 334 325 335 - try { 336 - const response = await fetch(`/stats?days=${days}`); 337 - if (!response.ok) 338 - throw new Error(`HTTP ${response.status}`); 326 + try { 327 + const response = await fetch(`/stats?days=${days}`); 328 + if (!response.ok) throw new Error(`HTTP ${response.status}`); 339 329 340 - const data = await response.json(); 341 - updateDashboard(data); 330 + const data = await response.json(); 331 + updateDashboard(data); 342 332 343 - loading.style.display = "none"; 344 - content.style.display = "block"; 345 - } catch (err) { 346 - loading.style.display = "none"; 347 - error.style.display = "block"; 348 - error.textContent = `Failed to load data: ${err.message}`; 349 - } 350 - } 333 + loading.style.display = "none"; 334 + content.style.display = "block"; 335 + } catch (err) { 336 + loading.style.display = "none"; 337 + error.style.display = "block"; 338 + error.textContent = `Failed to load data: ${err.message}`; 339 + } 340 + } 351 341 352 - function updateDashboard(data) { 353 - // Main metrics 354 - document.getElementById("totalRequests").textContent = 355 - data.totalRequests.toLocaleString(); 356 - document.getElementById("avgResponseTime").textContent = 357 - data.averageResponseTime 358 - ? Math.round(data.averageResponseTime) 359 - : "N/A"; 360 - document.getElementById("p95ResponseTime").textContent = data 361 - .latencyAnalytics.percentiles.p95 362 - ? Math.round(data.latencyAnalytics.percentiles.p95) 363 - : "N/A"; 364 - document.getElementById("uniqueEndpoints").textContent = 365 - data.requestsByEndpoint.length; 342 + function updateDashboard(data) { 343 + // Main metrics 344 + document.getElementById("totalRequests").textContent = 345 + data.totalRequests.toLocaleString(); 346 + document.getElementById("avgResponseTime").textContent = 347 + data.averageResponseTime 348 + ? Math.round(data.averageResponseTime) 349 + : "N/A"; 350 + document.getElementById("p95ResponseTime").textContent = data 351 + .latencyAnalytics.percentiles.p95 352 + ? Math.round(data.latencyAnalytics.percentiles.p95) 353 + : "N/A"; 354 + document.getElementById("uniqueEndpoints").textContent = 355 + data.requestsByEndpoint.length; 366 356 367 - const errorRequests = data.requestsByStatus 368 - .filter((s) => s.status >= 400) 369 - .reduce((sum, s) => sum + s.count, 0); 370 - const errorRate = 371 - data.totalRequests > 0 372 - ? ((errorRequests / data.totalRequests) * 100).toFixed( 373 - 1, 374 - ) 375 - : "0.0"; 376 - document.getElementById("errorRate").textContent = errorRate; 357 + const errorRequests = data.requestsByStatus 358 + .filter((s) => s.status >= 400) 359 + .reduce((sum, s) => sum + s.count, 0); 360 + const errorRate = 361 + data.totalRequests > 0 362 + ? ((errorRequests / data.totalRequests) * 100).toFixed(1) 363 + : "0.0"; 364 + document.getElementById("errorRate").textContent = errorRate; 377 365 378 - // Calculate fast requests percentage 379 - const fastRequestsData = data.latencyAnalytics.distribution 380 - .filter( 381 - (d) => d.range === "0-50ms" || d.range === "50-100ms", 382 - ) 383 - .reduce((sum, d) => sum + d.percentage, 0); 384 - document.getElementById("fastRequests").textContent = 385 - fastRequestsData.toFixed(1) + "%"; 366 + // Calculate fast requests percentage 367 + const fastRequestsData = data.latencyAnalytics.distribution 368 + .filter((d) => d.range === "0-50ms" || d.range === "50-100ms") 369 + .reduce((sum, d) => sum + d.percentage, 0); 370 + document.getElementById("fastRequests").textContent = 371 + fastRequestsData.toFixed(1) + "%"; 386 372 387 - // Performance metrics 388 - document.getElementById("uptime").textContent = 389 - data.performanceMetrics.uptime.toFixed(1); 390 - document.getElementById("throughput").textContent = Math.round( 391 - data.performanceMetrics.throughput, 392 - ); 393 - document.getElementById("apdex").textContent = 394 - data.performanceMetrics.apdex.toFixed(2); 395 - document.getElementById("cacheHitRate").textContent = 396 - data.performanceMetrics.cachehitRate.toFixed(1); 373 + // Performance metrics 374 + document.getElementById("uptime").textContent = 375 + data.performanceMetrics.uptime.toFixed(1); 376 + document.getElementById("throughput").textContent = Math.round( 377 + data.performanceMetrics.throughput, 378 + ); 379 + document.getElementById("apdex").textContent = 380 + data.performanceMetrics.apdex.toFixed(2); 381 + document.getElementById("cacheHitRate").textContent = 382 + data.performanceMetrics.cachehitRate.toFixed(1); 397 383 398 - // Peak traffic 399 - document.getElementById("peakHour").textContent = 400 - data.peakTraffic.peakHour; 401 - document.getElementById("peakHourRequests").textContent = 402 - data.peakTraffic.peakRequests.toLocaleString(); 403 - document.getElementById("peakDay").textContent = 404 - data.peakTraffic.peakDay; 405 - document.getElementById("peakDayRequests").textContent = 406 - data.peakTraffic.peakDayRequests.toLocaleString(); 384 + // Peak traffic 385 + document.getElementById("peakHour").textContent = 386 + data.peakTraffic.peakHour; 387 + document.getElementById("peakHourRequests").textContent = 388 + data.peakTraffic.peakRequests.toLocaleString(); 389 + document.getElementById("peakDay").textContent = 390 + data.peakTraffic.peakDay; 391 + document.getElementById("peakDayRequests").textContent = 392 + data.peakTraffic.peakDayRequests.toLocaleString(); 407 393 408 - // Dashboard metrics 409 - document.getElementById("dashboardRequests").textContent = 410 - data.dashboardMetrics.statsRequests.toLocaleString(); 394 + // Dashboard metrics 395 + document.getElementById("dashboardRequests").textContent = 396 + data.dashboardMetrics.statsRequests.toLocaleString(); 411 397 412 - // Determine if we're showing hourly or daily data 413 - const days = parseInt( 414 - document.getElementById("daysSelect").value, 415 - ); 416 - const isHourly = days === 1; 398 + // Determine if we're showing hourly or daily data 399 + const days = parseInt(document.getElementById("daysSelect").value); 400 + const isHourly = days === 1; 417 401 418 - updateTrafficOverviewChart(data.trafficOverview, days); 419 - updateTimeChart(data.requestsByDay, isHourly); 420 - updateLatencyTimeChart( 421 - data.latencyAnalytics.latencyOverTime, 422 - isHourly, 423 - ); 424 - updateLatencyDistributionChart( 425 - data.latencyAnalytics.distribution, 426 - ); 427 - updatePercentilesChart(data.latencyAnalytics.percentiles); 428 - updateEndpointChart(data.requestsByEndpoint.slice(0, 10)); 429 - updateSlowestEndpointsChart( 430 - data.latencyAnalytics.slowestEndpoints, 431 - ); 432 - updateStatusChart(data.requestsByStatus); 433 - updateUserAgentChart(data.topUserAgents.slice(0, 5)); 434 - } 402 + updateTrafficOverviewChart(data.trafficOverview, days); 403 + updateTimeChart(data.requestsByDay, isHourly); 404 + updateLatencyTimeChart(data.latencyAnalytics.latencyOverTime, isHourly); 405 + updateLatencyDistributionChart(data.latencyAnalytics.distribution); 406 + updatePercentilesChart(data.latencyAnalytics.percentiles); 407 + updateEndpointChart(data.requestsByEndpoint.slice(0, 10)); 408 + updateSlowestEndpointsChart(data.latencyAnalytics.slowestEndpoints); 409 + updateStatusChart(data.requestsByStatus); 410 + updateUserAgentChart(data.topUserAgents.slice(0, 5)); 411 + } 435 412 436 - function updateTrafficOverviewChart(data, days) { 437 - const ctx = document 438 - .getElementById("trafficOverviewChart") 439 - .getContext("2d"); 413 + function updateTrafficOverviewChart(data, days) { 414 + const ctx = document 415 + .getElementById("trafficOverviewChart") 416 + .getContext("2d"); 440 417 441 - if (charts.trafficOverview) charts.trafficOverview.destroy(); 418 + if (charts.trafficOverview) charts.trafficOverview.destroy(); 442 419 443 - // Update chart title based on granularity 444 - const chartTitle = document 445 - .querySelector("#trafficOverviewChart") 446 - .parentElement.querySelector(".chart-title"); 447 - let titleText = "Traffic Overview - All Routes Over Time"; 448 - if (days === 1) { 449 - titleText += " (Hourly)"; 450 - } else if (days <= 7) { 451 - titleText += " (4-Hour Intervals)"; 452 - } else { 453 - titleText += " (Daily)"; 454 - } 455 - chartTitle.textContent = titleText; 420 + // Update chart title based on granularity 421 + const chartTitle = document 422 + .querySelector("#trafficOverviewChart") 423 + .parentElement.querySelector(".chart-title"); 424 + let titleText = "Traffic Overview - All Routes Over Time"; 425 + if (days === 1) { 426 + titleText += " (Hourly)"; 427 + } else if (days <= 7) { 428 + titleText += " (4-Hour Intervals)"; 429 + } else { 430 + titleText += " (Daily)"; 431 + } 432 + chartTitle.textContent = titleText; 456 433 457 - // Get all unique routes across all time periods 458 - const allRoutes = new Set(); 459 - data.forEach((timePoint) => { 460 - Object.keys(timePoint.routes).forEach((route) => 461 - allRoutes.add(route), 462 - ); 463 - }); 434 + // Get all unique routes across all time periods 435 + const allRoutes = new Set(); 436 + data.forEach((timePoint) => { 437 + Object.keys(timePoint.routes).forEach((route) => 438 + allRoutes.add(route), 439 + ); 440 + }); 464 441 465 - // Define colors for different route types 466 - const routeColors = { 467 - Dashboard: "#3498db", 468 - "User Data": "#2ecc71", 469 - "User Redirects": "#27ae60", 470 - "Emoji Data": "#e74c3c", 471 - "Emoji Redirects": "#c0392b", 472 - "Emoji List": "#e67e22", 473 - "Health Check": "#f39c12", 474 - "API Documentation": "#9b59b6", 475 - "Cache Management": "#34495e", 476 - }; 442 + // Define colors for different route types 443 + const routeColors = { 444 + Dashboard: "#3498db", 445 + "User Data": "#2ecc71", 446 + "User Redirects": "#27ae60", 447 + "Emoji Data": "#e74c3c", 448 + "Emoji Redirects": "#c0392b", 449 + "Emoji List": "#e67e22", 450 + "Health Check": "#f39c12", 451 + "API Documentation": "#9b59b6", 452 + "Cache Management": "#34495e", 453 + }; 477 454 478 - // Create datasets for each route 479 - const datasets = Array.from(allRoutes).map((route) => { 480 - const color = routeColors[route] || "#95a5a6"; 481 - return { 482 - label: route, 483 - data: data.map( 484 - (timePoint) => timePoint.routes[route] || 0, 485 - ), 486 - borderColor: color, 487 - backgroundColor: color + "20", // Add transparency 488 - tension: 0.4, 489 - fill: false, 490 - pointRadius: 2, 491 - pointHoverRadius: 4, 492 - }; 493 - }); 455 + // Create datasets for each route 456 + const datasets = Array.from(allRoutes).map((route) => { 457 + const color = routeColors[route] || "#95a5a6"; 458 + return { 459 + label: route, 460 + data: data.map((timePoint) => timePoint.routes[route] || 0), 461 + borderColor: color, 462 + backgroundColor: color + "20", // Add transparency 463 + tension: 0.4, 464 + fill: false, 465 + pointRadius: 2, 466 + pointHoverRadius: 4, 467 + }; 468 + }); 494 469 495 - // Format labels based on time granularity 496 - const labels = data.map((timePoint) => { 497 - if (days === 1) { 498 - // Show just hour for 24h view 499 - return timePoint.time.split(" ")[1] || timePoint.time; 500 - } else if (days <= 7) { 501 - // Show day and hour for 7-day view 502 - const parts = timePoint.time.split(" "); 503 - const date = parts[0].split("-")[2]; // Get day 504 - const hour = parts[1] || "00:00"; 505 - return `${date} ${hour}`; 506 - } else { 507 - // Show full date for longer periods 508 - return timePoint.time; 509 - } 510 - }); 470 + // Format labels based on time granularity 471 + const labels = data.map((timePoint) => { 472 + if (days === 1) { 473 + // Show just hour for 24h view 474 + return timePoint.time.split(" ")[1] || timePoint.time; 475 + } else if (days <= 7) { 476 + // Show day and hour for 7-day view 477 + const parts = timePoint.time.split(" "); 478 + const date = parts[0].split("-")[2]; // Get day 479 + const hour = parts[1] || "00:00"; 480 + return `${date} ${hour}`; 481 + } else { 482 + // Show full date for longer periods 483 + return timePoint.time; 484 + } 485 + }); 511 486 512 - charts.trafficOverview = new Chart(ctx, { 513 - type: "line", 514 - data: { 515 - labels: labels, 516 - datasets: datasets, 517 - }, 518 - options: { 519 - responsive: true, 520 - maintainAspectRatio: false, 521 - interaction: { 522 - mode: "index", 523 - intersect: false, 524 - }, 525 - plugins: { 526 - legend: { 527 - position: "top", 528 - labels: { 529 - usePointStyle: true, 530 - padding: 15, 531 - font: { 532 - size: 11, 533 - }, 534 - }, 535 - }, 536 - tooltip: { 537 - mode: "index", 538 - intersect: false, 539 - callbacks: { 540 - afterLabel: function (context) { 541 - const timePoint = 542 - data[context.dataIndex]; 543 - return `Total: ${timePoint.total} requests`; 544 - }, 545 - }, 546 - }, 547 - }, 548 - scales: { 549 - x: { 550 - display: true, 551 - title: { 552 - display: true, 553 - text: 554 - days === 1 555 - ? "Hour" 556 - : days <= 7 557 - ? "Day & Hour" 558 - : "Date", 559 - }, 560 - ticks: { 561 - maxTicksLimit: 20, 562 - }, 563 - }, 564 - y: { 565 - display: true, 566 - title: { 567 - display: true, 568 - text: "Requests", 569 - }, 570 - beginAtZero: true, 571 - }, 572 - }, 573 - elements: { 574 - line: { 575 - tension: 0.4, 576 - }, 577 - }, 578 - }, 579 - }); 580 - } 487 + charts.trafficOverview = new Chart(ctx, { 488 + type: "line", 489 + data: { 490 + labels: labels, 491 + datasets: datasets, 492 + }, 493 + options: { 494 + responsive: true, 495 + maintainAspectRatio: false, 496 + interaction: { 497 + mode: "index", 498 + intersect: false, 499 + }, 500 + plugins: { 501 + legend: { 502 + position: "top", 503 + labels: { 504 + usePointStyle: true, 505 + padding: 15, 506 + font: { 507 + size: 11, 508 + }, 509 + }, 510 + }, 511 + tooltip: { 512 + mode: "index", 513 + intersect: false, 514 + callbacks: { 515 + afterLabel: function (context) { 516 + const timePoint = data[context.dataIndex]; 517 + return `Total: ${timePoint.total} requests`; 518 + }, 519 + }, 520 + }, 521 + }, 522 + scales: { 523 + x: { 524 + display: true, 525 + title: { 526 + display: true, 527 + text: days === 1 ? "Hour" : days <= 7 ? "Day & Hour" : "Date", 528 + }, 529 + ticks: { 530 + maxTicksLimit: 20, 531 + }, 532 + }, 533 + y: { 534 + display: true, 535 + title: { 536 + display: true, 537 + text: "Requests", 538 + }, 539 + beginAtZero: true, 540 + }, 541 + }, 542 + elements: { 543 + line: { 544 + tension: 0.4, 545 + }, 546 + }, 547 + }, 548 + }); 549 + } 581 550 582 - function updateTimeChart(data, isHourly) { 583 - const ctx = document 584 - .getElementById("timeChart") 585 - .getContext("2d"); 551 + function updateTimeChart(data, isHourly) { 552 + const ctx = document.getElementById("timeChart").getContext("2d"); 586 553 587 - if (charts.time) charts.time.destroy(); 554 + if (charts.time) charts.time.destroy(); 588 555 589 - // Update chart title 590 - const chartTitle = document 591 - .querySelector("#timeChart") 592 - .parentElement.querySelector(".chart-title"); 593 - chartTitle.textContent = isHourly 594 - ? "Requests Over Time (Hourly)" 595 - : "Requests Over Time (Daily)"; 556 + // Update chart title 557 + const chartTitle = document 558 + .querySelector("#timeChart") 559 + .parentElement.querySelector(".chart-title"); 560 + chartTitle.textContent = isHourly 561 + ? "Requests Over Time (Hourly)" 562 + : "Requests Over Time (Daily)"; 596 563 597 - charts.time = new Chart(ctx, { 598 - type: "line", 599 - data: { 600 - labels: data.map((d) => 601 - isHourly ? d.date.split(" ")[1] : d.date, 602 - ), 603 - datasets: [ 604 - { 605 - label: "Requests", 606 - data: data.map((d) => d.count), 607 - borderColor: "#3498db", 608 - backgroundColor: "rgba(52, 152, 219, 0.1)", 609 - tension: 0.4, 610 - fill: true, 611 - }, 612 - ], 613 - }, 614 - options: { 615 - responsive: true, 616 - scales: { 617 - y: { 618 - beginAtZero: true, 619 - }, 620 - }, 621 - }, 622 - }); 623 - } 564 + charts.time = new Chart(ctx, { 565 + type: "line", 566 + data: { 567 + labels: data.map((d) => (isHourly ? d.date.split(" ")[1] : d.date)), 568 + datasets: [ 569 + { 570 + label: "Requests", 571 + data: data.map((d) => d.count), 572 + borderColor: "#3498db", 573 + backgroundColor: "rgba(52, 152, 219, 0.1)", 574 + tension: 0.4, 575 + fill: true, 576 + }, 577 + ], 578 + }, 579 + options: { 580 + responsive: true, 581 + scales: { 582 + y: { 583 + beginAtZero: true, 584 + }, 585 + }, 586 + }, 587 + }); 588 + } 624 589 625 - function updateEndpointChart(data) { 626 - const ctx = document 627 - .getElementById("endpointChart") 628 - .getContext("2d"); 590 + function updateEndpointChart(data) { 591 + const ctx = document.getElementById("endpointChart").getContext("2d"); 629 592 630 - if (charts.endpoint) charts.endpoint.destroy(); 593 + if (charts.endpoint) charts.endpoint.destroy(); 631 594 632 - charts.endpoint = new Chart(ctx, { 633 - type: "bar", 634 - data: { 635 - labels: data.map((d) => d.endpoint), 636 - datasets: [ 637 - { 638 - label: "Requests", 639 - data: data.map((d) => d.count), 640 - backgroundColor: "#2ecc71", 641 - }, 642 - ], 643 - }, 644 - options: { 645 - responsive: true, 646 - indexAxis: "y", 647 - scales: { 648 - x: { 649 - beginAtZero: true, 650 - }, 651 - }, 652 - }, 653 - }); 654 - } 595 + charts.endpoint = new Chart(ctx, { 596 + type: "bar", 597 + data: { 598 + labels: data.map((d) => d.endpoint), 599 + datasets: [ 600 + { 601 + label: "Requests", 602 + data: data.map((d) => d.count), 603 + backgroundColor: "#2ecc71", 604 + }, 605 + ], 606 + }, 607 + options: { 608 + responsive: true, 609 + indexAxis: "y", 610 + scales: { 611 + x: { 612 + beginAtZero: true, 613 + }, 614 + }, 615 + }, 616 + }); 617 + } 655 618 656 - function updateStatusChart(data) { 657 - const ctx = document 658 - .getElementById("statusChart") 659 - .getContext("2d"); 619 + function updateStatusChart(data) { 620 + const ctx = document.getElementById("statusChart").getContext("2d"); 660 621 661 - if (charts.status) charts.status.destroy(); 622 + if (charts.status) charts.status.destroy(); 662 623 663 - const colors = data.map((d) => { 664 - if (d.status >= 200 && d.status < 300) return "#2ecc71"; 665 - if (d.status >= 300 && d.status < 400) return "#f39c12"; 666 - if (d.status >= 400 && d.status < 500) return "#e74c3c"; 667 - return "#9b59b6"; 668 - }); 624 + const colors = data.map((d) => { 625 + if (d.status >= 200 && d.status < 300) return "#2ecc71"; 626 + if (d.status >= 300 && d.status < 400) return "#f39c12"; 627 + if (d.status >= 400 && d.status < 500) return "#e74c3c"; 628 + return "#9b59b6"; 629 + }); 669 630 670 - charts.status = new Chart(ctx, { 671 - type: "doughnut", 672 - data: { 673 - labels: data.map((d) => `${d.status}`), 674 - datasets: [ 675 - { 676 - data: data.map((d) => d.count), 677 - backgroundColor: colors, 678 - }, 679 - ], 680 - }, 681 - options: { 682 - responsive: true, 683 - }, 684 - }); 685 - } 631 + charts.status = new Chart(ctx, { 632 + type: "doughnut", 633 + data: { 634 + labels: data.map((d) => `${d.status}`), 635 + datasets: [ 636 + { 637 + data: data.map((d) => d.count), 638 + backgroundColor: colors, 639 + }, 640 + ], 641 + }, 642 + options: { 643 + responsive: true, 644 + }, 645 + }); 646 + } 686 647 687 - function updateUserAgentChart(data) { 688 - const ctx = document 689 - .getElementById("userAgentChart") 690 - .getContext("2d"); 648 + function updateUserAgentChart(data) { 649 + const ctx = document.getElementById("userAgentChart").getContext("2d"); 691 650 692 - if (charts.userAgent) charts.userAgent.destroy(); 651 + if (charts.userAgent) charts.userAgent.destroy(); 693 652 694 - charts.userAgent = new Chart(ctx, { 695 - type: "pie", 696 - data: { 697 - labels: data.map((d) => d.userAgent), 698 - datasets: [ 699 - { 700 - data: data.map((d) => d.count), 701 - backgroundColor: [ 702 - "#3498db", 703 - "#e74c3c", 704 - "#2ecc71", 705 - "#f39c12", 706 - "#9b59b6", 707 - "#34495e", 708 - "#16a085", 709 - "#8e44ad", 710 - "#d35400", 711 - "#7f8c8d", 712 - ], 713 - }, 714 - ], 715 - }, 716 - options: { 717 - responsive: true, 718 - }, 719 - }); 720 - } 653 + charts.userAgent = new Chart(ctx, { 654 + type: "pie", 655 + data: { 656 + labels: data.map((d) => d.userAgent), 657 + datasets: [ 658 + { 659 + data: data.map((d) => d.count), 660 + backgroundColor: [ 661 + "#3498db", 662 + "#e74c3c", 663 + "#2ecc71", 664 + "#f39c12", 665 + "#9b59b6", 666 + "#34495e", 667 + "#16a085", 668 + "#8e44ad", 669 + "#d35400", 670 + "#7f8c8d", 671 + ], 672 + }, 673 + ], 674 + }, 675 + options: { 676 + responsive: true, 677 + }, 678 + }); 679 + } 721 680 722 - function updateLatencyTimeChart(data, isHourly) { 723 - const ctx = document 724 - .getElementById("latencyTimeChart") 725 - .getContext("2d"); 681 + function updateLatencyTimeChart(data, isHourly) { 682 + const ctx = document 683 + .getElementById("latencyTimeChart") 684 + .getContext("2d"); 726 685 727 - if (charts.latencyTime) charts.latencyTime.destroy(); 686 + if (charts.latencyTime) charts.latencyTime.destroy(); 728 687 729 - // Update chart title 730 - const chartTitle = document 731 - .querySelector("#latencyTimeChart") 732 - .parentElement.querySelector(".chart-title"); 733 - chartTitle.textContent = isHourly 734 - ? "Latency Over Time (Hourly)" 735 - : "Latency Over Time (Daily)"; 688 + // Update chart title 689 + const chartTitle = document 690 + .querySelector("#latencyTimeChart") 691 + .parentElement.querySelector(".chart-title"); 692 + chartTitle.textContent = isHourly 693 + ? "Latency Over Time (Hourly)" 694 + : "Latency Over Time (Daily)"; 736 695 737 - charts.latencyTime = new Chart(ctx, { 738 - type: "line", 739 - data: { 740 - labels: data.map((d) => 741 - isHourly ? d.time.split(" ")[1] : d.time, 742 - ), 743 - datasets: [ 744 - { 745 - label: "Average Response Time", 746 - data: data.map((d) => d.averageResponseTime), 747 - borderColor: "#3498db", 748 - backgroundColor: "rgba(52, 152, 219, 0.1)", 749 - tension: 0.4, 750 - yAxisID: "y", 751 - }, 752 - { 753 - label: "P95 Response Time", 754 - data: data.map((d) => d.p95), 755 - borderColor: "#e74c3c", 756 - backgroundColor: "rgba(231, 76, 60, 0.1)", 757 - tension: 0.4, 758 - yAxisID: "y", 759 - }, 760 - ], 761 - }, 762 - options: { 763 - responsive: true, 764 - scales: { 765 - y: { 766 - beginAtZero: true, 767 - title: { 768 - display: true, 769 - text: "Response Time (ms)", 770 - }, 771 - }, 772 - }, 773 - }, 774 - }); 775 - } 696 + charts.latencyTime = new Chart(ctx, { 697 + type: "line", 698 + data: { 699 + labels: data.map((d) => (isHourly ? d.time.split(" ")[1] : d.time)), 700 + datasets: [ 701 + { 702 + label: "Average Response Time", 703 + data: data.map((d) => d.averageResponseTime), 704 + borderColor: "#3498db", 705 + backgroundColor: "rgba(52, 152, 219, 0.1)", 706 + tension: 0.4, 707 + yAxisID: "y", 708 + }, 709 + { 710 + label: "P95 Response Time", 711 + data: data.map((d) => d.p95), 712 + borderColor: "#e74c3c", 713 + backgroundColor: "rgba(231, 76, 60, 0.1)", 714 + tension: 0.4, 715 + yAxisID: "y", 716 + }, 717 + ], 718 + }, 719 + options: { 720 + responsive: true, 721 + scales: { 722 + y: { 723 + beginAtZero: true, 724 + title: { 725 + display: true, 726 + text: "Response Time (ms)", 727 + }, 728 + }, 729 + }, 730 + }, 731 + }); 732 + } 776 733 777 - function updateLatencyDistributionChart(data) { 778 - const ctx = document 779 - .getElementById("latencyDistributionChart") 780 - .getContext("2d"); 734 + function updateLatencyDistributionChart(data) { 735 + const ctx = document 736 + .getElementById("latencyDistributionChart") 737 + .getContext("2d"); 781 738 782 - if (charts.latencyDistribution) 783 - charts.latencyDistribution.destroy(); 739 + if (charts.latencyDistribution) charts.latencyDistribution.destroy(); 784 740 785 - charts.latencyDistribution = new Chart(ctx, { 786 - type: "bar", 787 - data: { 788 - labels: data.map((d) => d.range), 789 - datasets: [ 790 - { 791 - label: "Requests", 792 - data: data.map((d) => d.count), 793 - backgroundColor: "#2ecc71", 794 - }, 795 - ], 796 - }, 797 - options: { 798 - responsive: true, 799 - scales: { 800 - y: { 801 - beginAtZero: true, 802 - }, 803 - }, 804 - }, 805 - }); 806 - } 741 + charts.latencyDistribution = new Chart(ctx, { 742 + type: "bar", 743 + data: { 744 + labels: data.map((d) => d.range), 745 + datasets: [ 746 + { 747 + label: "Requests", 748 + data: data.map((d) => d.count), 749 + backgroundColor: "#2ecc71", 750 + }, 751 + ], 752 + }, 753 + options: { 754 + responsive: true, 755 + scales: { 756 + y: { 757 + beginAtZero: true, 758 + }, 759 + }, 760 + }, 761 + }); 762 + } 807 763 808 - function updatePercentilesChart(percentiles) { 809 - const ctx = document 810 - .getElementById("percentilesChart") 811 - .getContext("2d"); 764 + function updatePercentilesChart(percentiles) { 765 + const ctx = document 766 + .getElementById("percentilesChart") 767 + .getContext("2d"); 812 768 813 - if (charts.percentiles) charts.percentiles.destroy(); 769 + if (charts.percentiles) charts.percentiles.destroy(); 814 770 815 - const data = [ 816 - { label: "P50", value: percentiles.p50 }, 817 - { label: "P75", value: percentiles.p75 }, 818 - { label: "P90", value: percentiles.p90 }, 819 - { label: "P95", value: percentiles.p95 }, 820 - { label: "P99", value: percentiles.p99 }, 821 - ].filter((d) => d.value !== null); 771 + const data = [ 772 + { label: "P50", value: percentiles.p50 }, 773 + { label: "P75", value: percentiles.p75 }, 774 + { label: "P90", value: percentiles.p90 }, 775 + { label: "P95", value: percentiles.p95 }, 776 + { label: "P99", value: percentiles.p99 }, 777 + ].filter((d) => d.value !== null); 822 778 823 - charts.percentiles = new Chart(ctx, { 824 - type: "bar", 825 - data: { 826 - labels: data.map((d) => d.label), 827 - datasets: [ 828 - { 829 - label: "Response Time (ms)", 830 - data: data.map((d) => d.value), 831 - backgroundColor: [ 832 - "#3498db", 833 - "#2ecc71", 834 - "#f39c12", 835 - "#e74c3c", 836 - "#9b59b6", 837 - ], 838 - }, 839 - ], 840 - }, 841 - options: { 842 - responsive: true, 843 - scales: { 844 - y: { 845 - beginAtZero: true, 846 - }, 847 - }, 848 - }, 849 - }); 850 - } 779 + charts.percentiles = new Chart(ctx, { 780 + type: "bar", 781 + data: { 782 + labels: data.map((d) => d.label), 783 + datasets: [ 784 + { 785 + label: "Response Time (ms)", 786 + data: data.map((d) => d.value), 787 + backgroundColor: [ 788 + "#3498db", 789 + "#2ecc71", 790 + "#f39c12", 791 + "#e74c3c", 792 + "#9b59b6", 793 + ], 794 + }, 795 + ], 796 + }, 797 + options: { 798 + responsive: true, 799 + scales: { 800 + y: { 801 + beginAtZero: true, 802 + }, 803 + }, 804 + }, 805 + }); 806 + } 851 807 852 - function updateSlowestEndpointsChart(data) { 853 - const ctx = document 854 - .getElementById("slowestEndpointsChart") 855 - .getContext("2d"); 808 + function updateSlowestEndpointsChart(data) { 809 + const ctx = document 810 + .getElementById("slowestEndpointsChart") 811 + .getContext("2d"); 856 812 857 - if (charts.slowestEndpoints) charts.slowestEndpoints.destroy(); 813 + if (charts.slowestEndpoints) charts.slowestEndpoints.destroy(); 858 814 859 - charts.slowestEndpoints = new Chart(ctx, { 860 - type: "bar", 861 - data: { 862 - labels: data.map((d) => d.endpoint), 863 - datasets: [ 864 - { 865 - label: "Avg Response Time (ms)", 866 - data: data.map((d) => d.averageResponseTime), 867 - backgroundColor: "#e74c3c", 868 - }, 869 - ], 870 - }, 871 - options: { 872 - responsive: true, 873 - indexAxis: "y", 874 - scales: { 875 - x: { 876 - beginAtZero: true, 877 - }, 878 - }, 879 - }, 880 - }); 881 - } 815 + charts.slowestEndpoints = new Chart(ctx, { 816 + type: "bar", 817 + data: { 818 + labels: data.map((d) => d.endpoint), 819 + datasets: [ 820 + { 821 + label: "Avg Response Time (ms)", 822 + data: data.map((d) => d.averageResponseTime), 823 + backgroundColor: "#e74c3c", 824 + }, 825 + ], 826 + }, 827 + options: { 828 + responsive: true, 829 + indexAxis: "y", 830 + scales: { 831 + x: { 832 + beginAtZero: true, 833 + }, 834 + }, 835 + }, 836 + }); 837 + } 882 838 883 - document 884 - .getElementById("autoRefresh") 885 - .addEventListener("change", function () { 886 - if (this.checked) { 887 - autoRefreshInterval = setInterval(loadData, 30000); 888 - } else { 889 - clearInterval(autoRefreshInterval); 890 - } 891 - }); 839 + document 840 + .getElementById("autoRefresh") 841 + .addEventListener("change", function () { 842 + if (this.checked) { 843 + autoRefreshInterval = setInterval(loadData, 30000); 844 + } else { 845 + clearInterval(autoRefreshInterval); 846 + } 847 + }); 892 848 893 - loadData(); 894 - document 895 - .getElementById("daysSelect") 896 - .addEventListener("change", loadData); 897 - </script> 898 - </body> 849 + loadData(); 850 + document 851 + .getElementById("daysSelect") 852 + .addEventListener("change", loadData); 853 + </script> 854 + </body> 899 855 </html>
+1 -2
src/swagger.ts
··· 1 - import { serve } from "bun"; 2 1 import { version } from "../package.json"; 3 2 4 3 // Define the Swagger specification ··· 15 14 }, 16 15 license: { 17 16 name: "AGPL 3.0", 18 - url: "https://github.com/taciturnaxoltol/cachet/blob/master/LICENSE.md", 17 + url: "https://github.com/taciturnaxolotl/cachet/blob/main/LICENSE.md", 19 18 }, 20 19 }, 21 20 tags: [