|
201 | 201 | .progress-fill.danger { background: linear-gradient(90deg, var(--red), var(--red-dim)); } |
202 | 202 | .progress-fill.info { background: linear-gradient(90deg, var(--purple), var(--purple-dim)); } |
203 | 203 |
|
| 204 | + /* Sparkline Chart Styles */ |
| 205 | + .sparkline-container { |
| 206 | + margin: 15px 0; |
| 207 | + background: var(--bg2); |
| 208 | + border-radius: 8px; |
| 209 | + padding: 12px; |
| 210 | + border: 1px solid var(--bg3); |
| 211 | + } |
| 212 | + |
| 213 | + .sparkline-chart { |
| 214 | + width: 100%; |
| 215 | + height: 40px; |
| 216 | + display: block; |
| 217 | + } |
| 218 | + |
| 219 | + .sparkline-info { |
| 220 | + display: flex; |
| 221 | + justify-content: space-between; |
| 222 | + align-items: center; |
| 223 | + margin-top: 8px; |
| 224 | + font-size: 0.8rem; |
| 225 | + } |
| 226 | + |
| 227 | + .sparkline-trend { |
| 228 | + font-weight: 600; |
| 229 | + color: var(--green); |
| 230 | + } |
| 231 | + |
| 232 | + .sparkline-trend.up { |
| 233 | + color: var(--red); |
| 234 | + } |
| 235 | + |
| 236 | + .sparkline-trend.down { |
| 237 | + color: var(--green); |
| 238 | + } |
| 239 | + |
| 240 | + .sparkline-period { |
| 241 | + color: var(--fg3); |
| 242 | + font-style: italic; |
| 243 | + } |
| 244 | + |
204 | 245 | .status-indicator { |
205 | 246 | display: inline-flex; |
206 | 247 | align-items: center; |
@@ -1538,8 +1579,12 @@ <h2>π§ Production AI Memory System Performance</h2> |
1538 | 1579 | <h3>P95 Latency</h3> |
1539 | 1580 | <div class="metric-value success" id="p95Latency">38ms</div> |
1540 | 1581 | <div class="metric-target">SLA: <50ms</div> |
1541 | | - <div class="progress-bar"> |
1542 | | - <div class="progress-fill" style="width: 76%" id="p95Progress"></div> |
| 1582 | + <div class="sparkline-container"> |
| 1583 | + <canvas id="p95Sparkline" class="sparkline-chart"></canvas> |
| 1584 | + <div class="sparkline-info"> |
| 1585 | + <span class="sparkline-trend" id="p95Trend">β 2.3ms</span> |
| 1586 | + <span class="sparkline-period">Last 24h</span> |
| 1587 | + </div> |
1543 | 1588 | </div> |
1544 | 1589 | <div class="status-indicator success"> |
1545 | 1590 | <i class="fas fa-check-circle"></i> Within SLA |
@@ -2413,6 +2458,110 @@ <h4>π Production Features:</h4> |
2413 | 2458 | document.body.style.overflow = 'auto'; |
2414 | 2459 | } |
2415 | 2460 |
|
| 2461 | + // P95 Latency Sparkline Data and Functions |
| 2462 | + let p95LatencyData = []; |
| 2463 | + const MAX_SPARKLINE_POINTS = 48; // 24 hours worth of data (30min intervals) |
| 2464 | + |
| 2465 | + // Initialize with sample data |
| 2466 | + function initializeSparklineData() { |
| 2467 | + p95LatencyData = []; |
| 2468 | + for (let i = 0; i < MAX_SPARKLINE_POINTS; i++) { |
| 2469 | + // Generate realistic latency data with some variation |
| 2470 | + const baseLatency = 38; |
| 2471 | + const variation = (Math.random() - 0.5) * 10; // Β±5ms variation |
| 2472 | + const value = Math.max(25, Math.min(50, baseLatency + variation)); |
| 2473 | + p95LatencyData.push(value); |
| 2474 | + } |
| 2475 | + } |
| 2476 | + |
| 2477 | + function updateSparklineData(newValue) { |
| 2478 | + if (p95LatencyData.length >= MAX_SPARKLINE_POINTS) { |
| 2479 | + p95LatencyData.shift(); // Remove oldest point |
| 2480 | + } |
| 2481 | + p95LatencyData.push(newValue); |
| 2482 | + drawSparkline(); |
| 2483 | + } |
| 2484 | + |
| 2485 | + function drawSparkline() { |
| 2486 | + const canvas = document.getElementById('p95Sparkline'); |
| 2487 | + if (!canvas) return; |
| 2488 | + |
| 2489 | + const ctx = canvas.getContext('2d'); |
| 2490 | + const rect = canvas.getBoundingClientRect(); |
| 2491 | + canvas.width = rect.width * window.devicePixelRatio; |
| 2492 | + canvas.height = rect.height * window.devicePixelRatio; |
| 2493 | + ctx.scale(window.devicePixelRatio, window.devicePixelRatio); |
| 2494 | + |
| 2495 | + const width = rect.width; |
| 2496 | + const height = rect.height; |
| 2497 | + |
| 2498 | + // Clear canvas |
| 2499 | + ctx.clearRect(0, 0, width, height); |
| 2500 | + |
| 2501 | + if (p95LatencyData.length < 2) return; |
| 2502 | + |
| 2503 | + // Calculate min/max for scaling |
| 2504 | + const min = Math.min(...p95LatencyData); |
| 2505 | + const max = Math.max(...p95LatencyData); |
| 2506 | + const range = max - min || 1; |
| 2507 | + |
| 2508 | + // Draw sparkline |
| 2509 | + ctx.beginPath(); |
| 2510 | + ctx.strokeStyle = '#b8bb26'; // Gruvbox green |
| 2511 | + ctx.lineWidth = 2; |
| 2512 | + |
| 2513 | + const stepX = width / (p95LatencyData.length - 1); |
| 2514 | + |
| 2515 | + p95LatencyData.forEach((value, index) => { |
| 2516 | + const x = index * stepX; |
| 2517 | + const y = height - ((value - min) / range) * height; |
| 2518 | + |
| 2519 | + if (index === 0) { |
| 2520 | + ctx.moveTo(x, y); |
| 2521 | + } else { |
| 2522 | + ctx.lineTo(x, y); |
| 2523 | + } |
| 2524 | + }); |
| 2525 | + |
| 2526 | + ctx.stroke(); |
| 2527 | + |
| 2528 | + // Draw SLA line at 50ms |
| 2529 | + const slaY = height - ((50 - min) / range) * height; |
| 2530 | + if (slaY >= 0 && slaY <= height) { |
| 2531 | + ctx.beginPath(); |
| 2532 | + ctx.strokeStyle = '#fb4934'; // Gruvbox red |
| 2533 | + ctx.lineWidth = 1; |
| 2534 | + ctx.setLineDash([5, 5]); |
| 2535 | + ctx.moveTo(0, slaY); |
| 2536 | + ctx.lineTo(width, slaY); |
| 2537 | + ctx.stroke(); |
| 2538 | + ctx.setLineDash([]); |
| 2539 | + } |
| 2540 | + |
| 2541 | + // Update trend indicator |
| 2542 | + updateTrendIndicator(); |
| 2543 | + } |
| 2544 | + |
| 2545 | + function updateTrendIndicator() { |
| 2546 | + const trendElement = document.getElementById('p95Trend'); |
| 2547 | + if (!trendElement || p95LatencyData.length < 2) return; |
| 2548 | + |
| 2549 | + const current = p95LatencyData[p95LatencyData.length - 1]; |
| 2550 | + const previous = p95LatencyData[p95LatencyData.length - 2]; |
| 2551 | + const change = current - previous; |
| 2552 | + |
| 2553 | + if (Math.abs(change) < 0.1) { |
| 2554 | + trendElement.textContent = 'β Stable'; |
| 2555 | + trendElement.className = 'sparkline-trend'; |
| 2556 | + } else if (change > 0) { |
| 2557 | + trendElement.textContent = `β +${change.toFixed(1)}ms`; |
| 2558 | + trendElement.className = 'sparkline-trend up'; |
| 2559 | + } else { |
| 2560 | + trendElement.textContent = `β ${change.toFixed(1)}ms`; |
| 2561 | + trendElement.className = 'sparkline-trend down'; |
| 2562 | + } |
| 2563 | + } |
| 2564 | + |
2416 | 2565 | // Close modal when clicking outside |
2417 | 2566 | window.onclick = function(event) { |
2418 | 2567 | const modals = document.querySelectorAll('.branch-modal'); |
@@ -2441,10 +2590,21 @@ <h4>π Production Features:</h4> |
2441 | 2590 |
|
2442 | 2591 | // Initialize dashboard with production features |
2443 | 2592 | document.addEventListener('DOMContentLoaded', function() { |
| 2593 | + initializeSparklineData(); |
2444 | 2594 | initializeCharts(); |
2445 | 2595 | startRealTimeUpdates(); |
2446 | 2596 | setupProductionFeatures(); |
2447 | 2597 | animateOnLoad(); |
| 2598 | + |
| 2599 | + // Initial sparkline draw |
| 2600 | + setTimeout(() => { |
| 2601 | + drawSparkline(); |
| 2602 | + }, 100); |
| 2603 | + }); |
| 2604 | + |
| 2605 | + // Redraw sparkline on window resize |
| 2606 | + window.addEventListener('resize', () => { |
| 2607 | + setTimeout(drawSparkline, 100); |
2448 | 2608 | }); |
2449 | 2609 |
|
2450 | 2610 | function initializeCharts() { |
@@ -3152,7 +3312,6 @@ <h4>π Production Features:</h4> |
3152 | 3312 | updateElement('systemUptime', '99.8%'); |
3153 | 3313 |
|
3154 | 3314 | // Update progress bars |
3155 | | - updateProgressBar('p95Progress', 76); |
3156 | 3315 | updateProgressBar('cognitiveProgress', 78); |
3157 | 3316 |
|
3158 | 3317 | // Update live system metrics with simulated values |
@@ -3226,7 +3385,6 @@ <h4>π Production Features:</h4> |
3226 | 3385 | updateElement('consolidationRatioLatency', '67ms'); |
3227 | 3386 | updateElement('consolidationRatioError', '0.02%'); |
3228 | 3387 |
|
3229 | | - updateProgressBar('p95Progress', 76); |
3230 | 3388 | updateProgressBar('cognitiveProgress', 78); |
3231 | 3389 | } |
3232 | 3390 | } |
@@ -3317,14 +3475,9 @@ <h4>π Production Features:</h4> |
3317 | 3475 | element.textContent = Math.round(newValue) + el.suffix; |
3318 | 3476 | } |
3319 | 3477 |
|
3320 | | - // Update progress bar for P95 latency (only one with progress bar) |
| 3478 | + // Update sparkline for P95 latency |
3321 | 3479 | if (el.id === 'p95Latency') { |
3322 | | - const progressBar = document.getElementById('p95Progress'); |
3323 | | - if (progressBar) { |
3324 | | - // Calculate percentage based on SLA of 50ms |
3325 | | - const percentage = Math.min(100, (50 - newValue) / 50 * 100); |
3326 | | - progressBar.style.width = Math.max(0, percentage) + '%'; |
3327 | | - } |
| 3480 | + updateSparklineData(newValue); |
3328 | 3481 | } |
3329 | 3482 | } |
3330 | 3483 | }); |
|
0 commit comments