diff --git a/application_events.json b/application_events.json
index 4eea360..6658233 100644
--- a/application_events.json
+++ b/application_events.json
@@ -4231,8 +4231,83 @@
"trigger_variable": null,
"auto_started": true
}
+ },
+ {
+ "timestamp": "2025-08-15T00:46:30.290892",
+ "level": "info",
+ "event_type": "application_started",
+ "message": "Application initialization completed successfully",
+ "details": {}
+ },
+ {
+ "timestamp": "2025-08-15T00:46:30.340169",
+ "level": "info",
+ "event_type": "dataset_activated",
+ "message": "Dataset activated: DAR",
+ "details": {
+ "dataset_id": "DAR",
+ "variables_count": 2,
+ "streaming_count": 2,
+ "prefix": "gateway_phoenix"
+ }
+ },
+ {
+ "timestamp": "2025-08-15T00:46:30.349167",
+ "level": "info",
+ "event_type": "dataset_activated",
+ "message": "Dataset activated: Fast",
+ "details": {
+ "dataset_id": "Fast",
+ "variables_count": 2,
+ "streaming_count": 1,
+ "prefix": "fast"
+ }
+ },
+ {
+ "timestamp": "2025-08-15T00:46:30.355834",
+ "level": "info",
+ "event_type": "csv_recording_started",
+ "message": "CSV recording started: 2 datasets activated",
+ "details": {
+ "activated_datasets": 2,
+ "total_datasets": 3
+ }
+ },
+ {
+ "timestamp": "2025-08-15T00:49:03.086267",
+ "level": "info",
+ "event_type": "plot_session_created",
+ "message": "Plot session 'UR29' created and started",
+ "details": {
+ "session_id": "plot_1",
+ "variables": [
+ "UR29_Brix",
+ "UR29_ma",
+ "AUX Blink_1.0S"
+ ],
+ "time_window": 3600,
+ "trigger_variable": null,
+ "auto_started": true
+ }
+ },
+ {
+ "timestamp": "2025-08-15T00:50:02.569962",
+ "level": "info",
+ "event_type": "plot_session_created",
+ "message": "Plot session 'UR29' created and started",
+ "details": {
+ "session_id": "plot_1",
+ "variables": [
+ "UR29_Brix",
+ "UR29_ma",
+ "AUX Blink_1.0S"
+ ],
+ "time_window": 3600,
+ "trigger_variable": null,
+ "auto_started": true
+ }
}
],
- "last_updated": "2025-08-15T00:41:32.191310",
- "total_entries": 381
+ "last_updated": "2025-08-15T00:50:02.569962",
+ "total_entries": 387
}
\ No newline at end of file
diff --git a/frontend/src/components/ChartjsPlot.jsx b/frontend/src/components/ChartjsPlot.jsx
index d584638..541a5d0 100644
--- a/frontend/src/components/ChartjsPlot.jsx
+++ b/frontend/src/components/ChartjsPlot.jsx
@@ -70,6 +70,14 @@ const ChartjsPlot = ({ session, height = '400px' }) => {
// Data preservation system for zoom operations
const dataBackupRef = useRef(new Map());
+
+ // Chart health monitoring
+ const chartHealthRef = useRef({
+ lastDataTimestamp: 0,
+ consecutiveErrors: 0,
+ isHealthy: true,
+ lastHealthCheck: 0
+ });
const bgColor = useColorModeValue('white', 'gray.800');
const textColor = useColorModeValue('gray.600', 'gray.300');
@@ -145,6 +153,113 @@ const ChartjsPlot = ({ session, height = '400px' }) => {
return [];
}, [getColor]);
+ // Chart health monitoring and auto-recovery
+ const checkChartHealth = useCallback(() => {
+ if (!chartRef.current) return false;
+
+ const health = chartHealthRef.current;
+ const now = Date.now();
+
+ // Check if chart is receiving data
+ const timeSinceLastData = now - health.lastDataTimestamp;
+ const hasRecentData = timeSinceLastData < 30000; // 30 seconds
+
+ // Check if streaming is properly configured
+ const realtimeOptions = chartRef.current.options?.scales?.x?.realtime;
+ const isStreamingConfigured = !!realtimeOptions;
+
+ // Check if datasets exist and are properly configured
+ const hasDatasets = chartRef.current.data?.datasets?.length > 0;
+
+ const isHealthy = hasRecentData && isStreamingConfigured && hasDatasets;
+
+ if (!isHealthy && health.isHealthy) {
+ console.warn('⚠️ Chart health check failed:', {
+ hasRecentData,
+ isStreamingConfigured,
+ hasDatasets,
+ timeSinceLastData,
+ consecutiveErrors: health.consecutiveErrors
+ });
+ }
+
+ health.isHealthy = isHealthy;
+ health.lastHealthCheck = now;
+
+ return isHealthy;
+ }, []);
+
+ const attemptAutoRecovery = useCallback(async () => {
+ console.log('🔄 Attempting chart auto-recovery...');
+
+ try {
+ // Clear all data directly
+ if (chartRef.current) {
+ chartRef.current.data.datasets.forEach(dataset => {
+ if (dataset.data) {
+ dataset.data.length = 0;
+ }
+ });
+ chartRef.current.update('quiet');
+ setDataPointsCount(0);
+ }
+
+ // Wait a moment
+ await new Promise(resolve => setTimeout(resolve, 100));
+
+ // Recreate chart if session is active
+ if (session?.is_active && !session?.is_paused) {
+ // Trigger a recreation by clearing refs and letting useEffect handle it
+ chartRef.current = null;
+ setTimeout(() => {
+ // The useEffect will recreate the chart
+ console.log('✅ Chart auto-recovery completed');
+ }, 100);
+
+ // Reset health counters
+ chartHealthRef.current.consecutiveErrors = 0;
+ chartHealthRef.current.isHealthy = true;
+
+ return true;
+ }
+ } catch (error) {
+ console.error('❌ Chart auto-recovery failed:', error);
+ chartHealthRef.current.consecutiveErrors++;
+ }
+
+ return false;
+ }, [session]);
+
+ // Diagnostic function to help identify issues
+ const runDiagnostics = useCallback(() => {
+ console.log('🔍 Chart Diagnostics:', {
+ chartExists: !!chartRef.current,
+ canvasExists: !!canvasRef.current,
+ sessionId: session?.session_id,
+ sessionActive: session?.is_active,
+ sessionPaused: session?.is_paused,
+ dataBackupSize: dataBackupRef.current.size,
+ health: chartHealthRef.current,
+ datasets: chartRef.current?.data?.datasets?.length || 0,
+ realtimeConfig: !!chartRef.current?.options?.scales?.x?.realtime
+ });
+
+ // Check for common issues
+ const issues = [];
+ if (!chartRef.current) issues.push('Chart not initialized');
+ if (!session?.is_active) issues.push('Session not active');
+ if (session?.is_paused) issues.push('Session is paused');
+ if (chartHealthRef.current.consecutiveErrors > 5) issues.push(`${chartHealthRef.current.consecutiveErrors} consecutive errors`);
+
+ if (issues.length > 0) {
+ console.warn('⚠️ Issues detected:', issues);
+ } else {
+ console.log('✅ No issues detected');
+ }
+
+ return issues;
+ }, [session]);
+
// Load historical data from CSV files
const loadHistoricalData = useCallback(async (variables, timeWindow) => {
try {
@@ -872,6 +987,10 @@ const ChartjsPlot = ({ session, height = '400px' }) => {
if (pointsAdded > 0) {
chart.update('quiet');
+ // Update health monitoring
+ chartHealthRef.current.lastDataTimestamp = Date.now();
+ chartHealthRef.current.consecutiveErrors = 0;
+
// Backup data periodically when significant data is added
if (pointsAdded > 5 && dataBackupRef.current) {
const backup = chart.data.datasets.map(dataset => ({
@@ -883,6 +1002,20 @@ const ChartjsPlot = ({ session, height = '400px' }) => {
dataBackupRef.current.set('current', backup);
console.log(`📦 Data backed up: ${backup.reduce((total, ds) => total + ds.data.length, 0)} points`);
}
+ } else {
+ // No new data received - increment error counter
+ chartHealthRef.current.consecutiveErrors++;
+
+ // Check if we need auto-recovery after multiple failed attempts
+ if (chartHealthRef.current.consecutiveErrors > 10) {
+ console.warn(`⚠️ No data received for ${chartHealthRef.current.consecutiveErrors} consecutive attempts`);
+
+ // Attempt auto-recovery if too many consecutive errors
+ if (chartHealthRef.current.consecutiveErrors > 20) {
+ console.warn('🚑 Triggering auto-recovery due to consecutive data failures');
+ setTimeout(() => attemptAutoRecovery(), 100);
+ }
+ }
}
// Clear NaN insertion flag
@@ -890,7 +1023,7 @@ const ChartjsPlot = ({ session, height = '400px' }) => {
sessionData.insertNaNOnNextIngest = false;
}
return pointsAdded;
- }, []);
+ }, [attemptAutoRecovery]);
// Fallback cleanup removed per requirement: always realtime; we pause instead when no data
@@ -1235,6 +1368,28 @@ const ChartjsPlot = ({ session, height = '400px' }) => {
}
}, [session?.isFullscreen, createStreamingChart]);
+ // Periodic health monitoring
+ useEffect(() => {
+ if (!session?.session_id || !chartRef.current) return;
+
+ const healthCheckInterval = setInterval(() => {
+ const isHealthy = checkChartHealth();
+
+ // Log health status periodically for debugging
+ if (!isHealthy) {
+ console.warn('📊 Chart health check:', {
+ sessionId: session.session_id,
+ isActive: session.is_active,
+ isPaused: session.is_paused,
+ consecutiveErrors: chartHealthRef.current.consecutiveErrors,
+ timeSinceLastData: Date.now() - chartHealthRef.current.lastDataTimestamp
+ });
+ }
+ }, 15000); // Check every 15 seconds
+
+ return () => clearInterval(healthCheckInterval);
+ }, [session?.session_id, checkChartHealth]);
+
// Initialize chart when config is resolved - simplified approach
useEffect(() => {
console.log(`🔍 useEffect triggered - sessionId: ${session?.session_id}, hasCanvas: ${!!canvasRef.current}, hasChart: ${!!chartRef.current}`);
@@ -1460,6 +1615,8 @@ const ChartjsPlot = ({ session, height = '400px' }) => {
bottom={2}
right={2}
zIndex={10}
+ display="flex"
+ gap={2}
>
+
+ {/* Auto-recovery button when chart health is poor */}
+ {!chartHealthRef.current.isHealthy && (
+ <>
+
+
+
+ >
+ )}
)}
diff --git a/system_state.json b/system_state.json
index 1491628..024e8b7 100644
--- a/system_state.json
+++ b/system_state.json
@@ -9,5 +9,5 @@
]
},
"auto_recovery_enabled": true,
- "last_update": "2025-08-15T00:35:28.773668"
+ "last_update": "2025-08-15T00:46:30.361074"
}
\ No newline at end of file