Add comprehensive sparkline chart support as an alternative to progress bars for CPU, Memory, and Disk metrics across all tables. Features: - Toggle between bars/trends view modes (persisted to localStorage) - 30-second sampling with 2-hour retention window using ring buffer - Canvas-based rendering with shared requestAnimationFrame for efficiency - Hover tooltips showing exact values and timestamps - Threshold reference lines (warning/critical) for context - localStorage persistence survives page refreshes (12-hour max age) - Dynamic width adaptation to column size - Namespaced resource IDs prevent collisions - Lifecycle cleanup prevents memory leaks Performance optimizations: - Decoupled sampling from WebSocket handler (6x reduction in recording) - O(1) ring buffer insertions (no array cloning) - Batched canvas rendering (single rAF for all sparklines) - Debounced localStorage writes - Automatic pruning of removed resources UI improvements: - Consistent radio toggle styling matching other filters - Fixed column widths prevent layout shift during toggle - Fixed row heights prevent vertical size changes - Sparklines fill available column width proportionally
69 lines
1.4 KiB
TypeScript
69 lines
1.4 KiB
TypeScript
/**
|
|
* Canvas Render Queue
|
|
*
|
|
* Shared requestAnimationFrame scheduler for batching canvas redraws.
|
|
* Prevents each sparkline from scheduling its own rAF cycle.
|
|
*/
|
|
|
|
import { logger } from './logger';
|
|
|
|
let rafId: number | null = null;
|
|
const pending = new Set<() => void>();
|
|
|
|
/**
|
|
* Flush all pending render callbacks in a single rAF cycle
|
|
*/
|
|
const flush = (): void => {
|
|
rafId = null;
|
|
const count = pending.size;
|
|
|
|
pending.forEach((draw) => {
|
|
try {
|
|
draw();
|
|
} catch (error) {
|
|
logger.error('[CanvasRenderQueue] Draw callback failed', { error });
|
|
}
|
|
});
|
|
|
|
pending.clear();
|
|
|
|
if (import.meta.env.DEV) {
|
|
logger.debug('[CanvasRenderQueue] Flushed render queue', { count });
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Schedule a canvas redraw
|
|
* Returns an unregister callback for cleanup
|
|
*
|
|
* @param draw - The draw callback to execute on next animation frame
|
|
* @returns Cleanup function to unregister the callback
|
|
*/
|
|
export function scheduleSparkline(draw: () => void): () => void {
|
|
pending.add(draw);
|
|
|
|
if (rafId === null) {
|
|
rafId = requestAnimationFrame(flush);
|
|
}
|
|
|
|
return () => {
|
|
pending.delete(draw);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get render queue stats for debugging
|
|
*/
|
|
export function getRenderQueueStats() {
|
|
return {
|
|
pendingCount: pending.size,
|
|
rafScheduled: rafId !== null,
|
|
};
|
|
}
|
|
|
|
if (import.meta.env.DEV) {
|
|
// Expose for debugging in dev mode
|
|
(window as any).__canvasRenderQueue = {
|
|
getStats: getRenderQueueStats,
|
|
};
|
|
}
|