diff --git a/RJSF_IMPLEMENTATION_SUMMARY.md b/RJSF_IMPLEMENTATION_SUMMARY.md deleted file mode 100644 index 5febe94..0000000 --- a/RJSF_IMPLEMENTATION_SUMMARY.md +++ /dev/null @@ -1,129 +0,0 @@ -# 🏭 PLC S7-31x Streamer & Logger - Pure RJSF Dashboard - -## 🚀 Mejoras Implementadas - -### ✅ Dashboard Completamente Nuevo con RJSF Puro -- **Archivo:** `frontend/src/pages/DashboardNew.jsx` -- Implementado con React JSON Schema Form (RJSF) y tema Chakra UI -- Elimina todos los wrappers innecesarios y usa widgets puros y extensibles -- Todas las configuraciones se manejan directamente con esquemas JSON - -### 🔧 StatusBar Mejorado con Control Robusto -- **Problema resuelto:** Botones de conexión/desconexión ahora funcionan correctamente -- Agregados estados de carga individuales para cada acción -- Manejo adecuado de errores con toast notifications -- Actualización automática del estado después de las acciones - -### 📊 Dataset Manager con RJSF Puro -- Formularios completamente basados en esquemas JSON -- Edición directa de configuraciones sin wrappers -- Dos pestañas: Dataset Definitions y Dataset Variables -- Validación automática mediante esquemas JSON - -### 📈 Plot Manager Completamente Funcional -- **Archivo:** `frontend/src/components/PlotManager.jsx` -- Control completo de sesiones de plotting (start/stop/clear/delete) -- **Problema resuelto:** Botones de charts ahora funcionan correctamente -- Configuración de plots mediante RJSF puro con esquemas -- Vista de sesiones activas con controles en tiempo real - -### 🔌 APIs de Plotting Añadidas -- **Archivo:** `frontend/src/services/api.js` -- Funciones completas para manejo de plots: - - `getPlots()`, `createPlot()`, `deletePlot()` - - `controlPlot()` (start/stop/clear) - - `getPlotData()`, `getPlotConfig()`, `updatePlotConfig()` - - `getPlotVariables()` - -### 🎯 Arquitectura RJSF Pura -- **Sin wrappers innecesarios:** Solo widgets extensibles y reutilizables -- **Esquemas JSON:** Toda la configuración basada en `/config/schema/` -- **UI Schemas:** Layout y configuración visual mediante esquemas UI -- **Validación:** Automática con `@rjsf/validator-ajv8` - -### 🗑️ Sistema Legacy Preparado para Eliminación -- **Archivo de notas:** `main_cleanup_notes.py` -- Identificadas todas las rutas legacy a eliminar -- APIs esenciales documentadas para mantener -- `templates/index.html` puede ser eliminado -- JavaScript legacy en `/static/js/` puede ser eliminado - -## 🔄 Uso de la Aplicación - -### 1. StatusBar (Control Principal) -```jsx -// Conexión PLC con estado de carga - - -// UDP Streaming con feedback - -``` - -### 2. Configuración RJSF -```jsx -// Formularios puros sin wrappers -
saveConfig(formData)} -/> -``` - -### 3. Control de Plots -```jsx -// Botones funcionales para charts - - -``` - -## 📁 Estructura de Archivos Actualizada - -``` -frontend/src/ -├── pages/ -│ ├── Dashboard.jsx (old - puede ser eliminado) -│ └── DashboardNew.jsx ⭐ (nuevo, RJSF puro) -├── components/ -│ ├── DatasetManager.jsx (mejorado con RJSF puro) -│ └── PlotManager.jsx ⭐ (nuevo, control completo de charts) -└── services/ - └── api.js (APIs de plotting añadidas) -``` - -## 🚦 Rutas de la Aplicación - -- **`/`** → React SPA principal -- **`/app`** → Dashboard principal -- **`/app/*`** → Rutas internas de React - -## 🎨 Tema Chakra UI Completo - -- Todos los componentes usan tema Chakra UI consistente -- Color modes (light/dark) funcionales -- Cards, Buttons, Tables con estilos uniformes -- Toast notifications para feedback - -## ✅ Problemas Resueltos - -1. **✅ Botón connect/disconnect:** Ahora funciona correctamente con estados de carga -2. **✅ Edición JSON:** RJSF puro permite edición completa de configuraciones -3. **✅ Botones de charts:** Plot Manager implementado con controles funcionales -4. **✅ RJSF puro:** Sin wrappers, solo widgets extensibles -5. **✅ Layout y esquemas:** Configuración dual (schema + uiSchema) implementada - -## 🚀 Listo para Producción - -- Build exitoso: ✅ -- RJSF + Chakra UI: ✅ -- APIs funcionionales: ✅ -- Sistema legacy preparado para eliminación: ✅ -- Dashboard completamente funcional: ✅ diff --git a/UI_SCHEMA_LAYOUT_ENHANCEMENT.md b/UI_SCHEMA_LAYOUT_ENHANCEMENT.md deleted file mode 100644 index 93c2bda..0000000 --- a/UI_SCHEMA_LAYOUT_ENHANCEMENT.md +++ /dev/null @@ -1,115 +0,0 @@ -# UI Schema Layout Support Enhancement Summary - -## Changes Made - -### 1. Enhanced `DashboardNew.jsx` -- **Added imports**: `LayoutObjectFieldTemplate` and comprehensive widget collection -- **Updated Form components**: All RJSF Form components now use: - - `widgets={allWidgets}` - Comprehensive widget collection - - `templates={{ ObjectFieldTemplate: LayoutObjectFieldTemplate }}` - Layout support -- **Enhanced Configuration Panel**: Now supports full UI schema features -- **Updated Dataset Manager**: Both definitions and variables forms support layouts -- **Added comprehensive documentation**: Detailed comments explaining UI schema features - -### 2. Created `AllWidgets.jsx` -- **Comprehensive widget collection**: Merges all available widgets -- **Widget aliases**: Support for different naming conventions in UI schemas -- **Custom widget integration**: Includes VariableSelectorWidget and PLC widgets -- **Backward compatibility**: Ensures existing UI schemas continue to work - -### 3. Enhanced `CustomWidgets.jsx` -- **Added widget aliases**: `variableSelector` for UI schema compatibility -- **Maintained existing functionality**: VariableSelectorWidget continues to work - -### 4. Updated `PlotManager.jsx` -- **Enhanced Form components**: Added layout template and comprehensive widgets -- **Consistent widget usage**: Uses same widget collection as DashboardNew - -### 5. Created Demo Files -- **`layout-demo.schema.json`**: Example schema for testing layouts -- **`layout-demo.uischema.json`**: Comprehensive UI schema example - -## UI Schema Features Now Supported - -### Layout Management -```json -{ - "ui:layout": [ - [ - { "name": "field1", "width": 6 }, - { "name": "field2", "width": 6 } - ], - [ - { "name": "field3", "width": 12 } - ] - ] -} -``` - -### Widget Types -- `updown` - Number input with +/- buttons -- `checkbox` - Boolean checkbox -- `text` - Text input -- `textarea` - Multi-line text -- `select` - Dropdown selection -- `VariableSelectorWidget` - Custom PLC variable selector - -### Field Properties -- `ui:help` - Help text for fields -- `ui:placeholder` - Placeholder text -- `ui:readonly` - Read-only fields -- `ui:order` - Field ordering -- `ui:column` - Column width (1-12 grid) - -### Examples from Existing Schemas - -#### PLC Configuration with Layout -```json -{ - "plc_config": { - "ui:layout": [ - [ - { "name": "ip", "width": 6 }, - { "name": "rack", "width": 3 }, - { "name": "slot", "width": 3 } - ] - ] - } -} -``` - -#### Dataset Definitions with Responsive Layout -```json -{ - "datasets": { - "ui:layout": [ - [ - { "name": "name", "width": 3 }, - { "name": "prefix", "width": 3 }, - { "name": "sampling_interval", "width": 3 }, - { "name": "enabled", "width": 3 } - ] - ] - } -} -``` - -## Benefits - -1. **Responsive Design**: 12-column grid system adapts to different screen sizes -2. **Better UX**: Logical field grouping and intuitive layouts -3. **Consistent Styling**: All forms use the same Chakra UI components -4. **Extensible**: Easy to add new widgets and layout patterns -5. **Backward Compatible**: Existing configurations continue to work -6. **Documentation**: Clear examples and comprehensive comments - -## Testing - -The enhanced UI schema support can be tested by: -1. Loading any existing configuration (PLC, datasets, plots) -2. Observing the improved layout with proper field grouping -3. Testing different screen sizes for responsive behavior -4. Adding new configurations with custom layouts -5. Using the demo schema files for comprehensive testing - -All existing functionality is preserved while adding powerful new layout capabilities. diff --git a/application_events.json b/application_events.json index 1753a39..55c0ddc 100644 --- a/application_events.json +++ b/application_events.json @@ -825,8 +825,94 @@ "event_type": "csv_cleanup_failed", "message": "CSV cleanup failed: 'max_hours'", "details": {} + }, + { + "timestamp": "2025-08-14T15:00:00.054694", + "level": "error", + "event_type": "csv_cleanup_failed", + "message": "CSV cleanup failed: 'max_hours'", + "details": {} + }, + { + "timestamp": "2025-08-14T15:03:55.324074", + "level": "info", + "event_type": "application_started", + "message": "Application initialization completed successfully", + "details": {} + }, + { + "timestamp": "2025-08-14T15:03:55.388267", + "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-14T15:03:55.390258", + "level": "info", + "event_type": "csv_recording_started", + "message": "CSV recording started: 1 datasets activated", + "details": { + "activated_datasets": 1, + "total_datasets": 3 + } + }, + { + "timestamp": "2025-08-14T15:03:55.393257", + "level": "info", + "event_type": "udp_streaming_started", + "message": "UDP streaming to PlotJuggler started", + "details": { + "udp_host": "127.0.0.1", + "udp_port": 9870, + "datasets_available": 3 + } + }, + { + "timestamp": "2025-08-14T15:03:55.436491", + "level": "error", + "event_type": "csv_cleanup_failed", + "message": "CSV cleanup failed: 'max_hours'", + "details": {} + }, + { + "timestamp": "2025-08-14T15:04:08.406394", + "level": "info", + "event_type": "plot_session_created", + "message": "Plot session 'UR29' created and started", + "details": { + "session_id": "plot_1", + "variables": [ + "UR29_Brix", + "UR29_ma" + ], + "time_window": 20, + "trigger_variable": null, + "auto_started": true + } + }, + { + "timestamp": "2025-08-14T15:04:12.217187", + "level": "info", + "event_type": "plot_session_created", + "message": "Plot session 'UR29' created and started", + "details": { + "session_id": "plot_1", + "variables": [ + "UR29_Brix", + "UR29_ma" + ], + "time_window": 20, + "trigger_variable": null, + "auto_started": true + } } ], - "last_updated": "2025-08-14T14:47:13.244962", - "total_entries": 82 + "last_updated": "2025-08-14T15:04:12.217187", + "total_entries": 90 } \ No newline at end of file diff --git a/frontend/src/components/PlotManager.jsx.backup b/frontend/src/components/PlotManager.jsx.backup deleted file mode 100644 index 63231da..0000000 --- a/frontend/src/components/PlotManager.jsx.backup +++ /dev/null @@ -1,611 +0,0 @@ -import React, { useState, useEffect, useCallback } from 'react' -import { - Box, - Card, - CardBody, - CardHeader, - Button, - Text, - Grid, - Flex, - Spacer, - HStack, - VStack, - useColorModeValue, - useToast, - Heading, - Table, - Thead, - Tbody, - Tr, - Th, - Td, - TableContainer, - Badge, - IconButton, - Tabs, - TabList, - TabPanels, - Tab, - TabPanel, - Divider, - Select, - Accordion, - AccordionItem, - AccordionButton, - AccordionPanel, - AccordionIcon, - Collapse, - useDisclosure, - Spinner -} from '@chakra-ui/react' -import Form from '@rjsf/chakra-ui' -import validator from '@rjsf/validator-ajv8' -import allWidgets from './widgets/AllWidgets' -import LayoutObjectFieldTemplate from './rjsf/LayoutObjectFieldTemplate' -import PlotRealtimeSession from './PlotRealtimeSession' -import { useVariableContext } from '../contexts/VariableContext' -import * as api from '../services/api' - -// Collapsible Form Component for Plot Definitions -function CollapsiblePlotForm({ data, schema, uiSchema, onSave, title, icon, getItemLabel }) { - const [isOpen, setIsOpen] = useState(false) - const [formData, setFormData] = useState(data) - - useEffect(() => { - setFormData(data) - }, [data]) - - if (!schema || !formData) { - return ( - - - - - Loading {title}... - - - - ) - } - - const items = formData[Object.keys(formData)[0]] || [] - - return ( - - - - - {icon} {title} - - {items.length} item{items.length !== 1 ? 's' : ''} configured - - - - - - {/* Show summary when collapsed */} - {!isOpen && items.length > 0 && ( - - Quick Overview: - - {items.slice(0, 5).map((item, index) => ( - - {getItemLabel ? getItemLabel(item) : (item.name || item.id || `Item ${index + 1}`)} - - ))} - {items.length > 5 && ( - - +{items.length - 5} more... - - )} - - - )} - - - - - onSave(formData)} - onChange={({ formData }) => setFormData(formData)} - > - - - - - - - -
- ) -} - -// Pure RJSF Plot Manager Component -export default function PlotManager() { - const { triggerVariableRefresh } = useVariableContext() - const [plots, setPlots] = useState({}) - const [plotsSchemaData, setPlotsSchemaData] = useState(null) - const [plotsVariablesSchemaData, setPlotsVariablesSchemaData] = useState(null) - const [plotsConfig, setPlotsConfig] = useState(null) - const [plotsVariablesConfig, setPlotsVariablesConfig] = useState(null) - const [selectedPlotId, setSelectedPlotId] = useState('') - const [loading, setLoading] = useState(true) - const [actionLoading, setActionLoading] = useState({}) - - const toast = useToast() - const cardBg = useColorModeValue('white', 'gray.700') - const borderColor = useColorModeValue('gray.200', 'gray.600') - - const setActionState = (key, loading) => { - setActionLoading(prev => ({ ...prev, [key]: loading })) - } - - const loadPlotData = useCallback(async () => { - try { - setLoading(true) - const [ - plotsData, - plotsSchemaResponse, - plotsVariablesSchemaResponse, - plotsConfigData, - plotsVariablesConfigData - ] = await Promise.all([ - api.getPlots(), - api.getSchema('plot-definitions'), - api.getSchema('plot-variables'), - api.readConfig('plot-definitions'), - api.readConfig('plot-variables') - ]) - - setPlots(plotsData?.plots || {}) - setPlotsSchemaData(plotsSchemaResponse) - setPlotsVariablesSchemaData(plotsVariablesSchemaResponse) - setPlotsConfig(plotsConfigData) - setPlotsVariablesConfig(plotsVariablesConfigData) - - // Auto-select first plot if none selected - if (!selectedPlotId && plotsConfigData?.plots?.length > 0) { - setSelectedPlotId(plotsConfigData.plots[0].id) - } - } catch (error) { - toast({ - title: '❌ Failed to load plot data', - description: error.message, - status: 'error', - duration: 3000 - }) - } finally { - setLoading(false) - } - }, [toast]) - - // Helper function to get plot definitions from config - const getPlotDefinitions = () => { - return plotsConfig?.plots || [] - } - - // Helper function to get variables for a specific plot - const getPlotVariables = (plotId) => { - const plotVarsConfig = plotsVariablesConfig?.variables || [] - const plotVarEntry = plotVarsConfig.find(entry => entry.plot_id === plotId) - return plotVarEntry?.variables || [] - } - - // Type 3 Pattern Helper Functions - // Get filtered variables for selected plot - const getSelectedPlotVariables = () => { - if (!plotsVariablesConfig?.variables || !selectedPlotId) { - return { variables: [] } - } - - const plotVars = plotsVariablesConfig.variables.find(v => v.plot_id === selectedPlotId) - return plotVars || { variables: [] } - } - - // Update variables for selected plot - const updateSelectedPlotVariables = (newVariableData) => { - if (!plotsVariablesConfig?.variables || !selectedPlotId) return - - const updatedVariables = plotsVariablesConfig.variables.map(v => - v.plot_id === selectedPlotId - ? { ...v, ...newVariableData } - : v - ) - - // If plot not found, add new entry - if (!plotsVariablesConfig.variables.find(v => v.plot_id === selectedPlotId)) { - updatedVariables.push({ - plot_id: selectedPlotId, - ...newVariableData - }) - } - - const updatedConfig = { ...plotsVariablesConfig, variables: updatedVariables } - setPlotsVariablesConfig(updatedConfig) - } - - // Available plots for combo selector - const availablePlots = plotsConfig?.plots || [] - - // Handle plot configuration updates - const handlePlotConfigUpdate = async (plotId, newConfig) => { - try { - // Update the plot definition in local state - const updatedPlots = getPlotDefinitions().map(plot => - plot.id === plotId ? { ...plot, ...newConfig } : plot - ) - - const updatedConfig = { ...plotsConfig, plots: updatedPlots } - await savePlotsConfig(updatedConfig) - - // Reload data to get fresh state - await loadPlotData() - } catch (error) { - throw new Error(`Failed to update plot configuration: ${error.message}`) - } - } - - // Handle plot removal - const handlePlotRemove = async (plotId) => { - try { - // Remove from plot definitions - const updatedPlots = getPlotDefinitions().filter(plot => plot.id !== plotId) - const updatedPlotsConfig = { ...plotsConfig, plots: updatedPlots } - - // Remove from plot variables - const updatedPlotVars = (plotsVariablesConfig?.variables || []).filter( - entry => entry.plot_id !== plotId - ) - const updatedVarsConfig = { ...plotsVariablesConfig, variables: updatedPlotVars } - - // Save both configurations - await Promise.all([ - savePlotsConfig(updatedPlotsConfig), - savePlotsVariablesConfig(updatedVarsConfig) - ]) - - // Stop the plot session in backend - try { - await api.controlPlotSession(plotId, 'stop') - } catch (error) { - // Plot session may not exist, that's OK - } - - // Reload data - await loadPlotData() - - toast({ - title: '✅ Plot removed successfully', - status: 'success', - duration: 2000 - }) - } catch (error) { - toast({ - title: '❌ Failed to remove plot', - description: error.message, - status: 'error', - duration: 3000 - }) - } - } - - const savePlotsConfig = async (formData) => { - try { - setActionState('savePlots', true) - await api.writeConfig('plot-definitions', formData) - toast({ - title: '✅ Plot definitions saved', - status: 'success', - duration: 2000 - }) - setPlotsConfig(formData) - } catch (error) { - toast({ - title: '❌ Failed to save plot definitions', - description: error.message, - status: 'error', - duration: 3000 - }) - } finally { - setActionState('savePlots', false) - } - } - - const savePlotsVariablesConfig = async (formData) => { - try { - setActionState('savePlotsVariables', true) - await api.writeConfig('plot-variables', formData) - toast({ - title: '✅ Plot variables saved', - status: 'success', - duration: 2000 - }) - setPlotsVariablesConfig(formData) - // Trigger refresh of variable selectors (though they don't depend on plot vars directly) - triggerVariableRefresh() - } catch (error) { - toast({ - title: '❌ Failed to save plot variables', - description: error.message, - status: 'error', - duration: 3000 - }) - } finally { - setActionState('savePlotsVariables', false) - } - } - - useEffect(() => { - loadPlotData() - }, [loadPlotData]) - - if (loading) { - return ( - - - Loading plot configurations... - - - ) - } - - return ( - - - 📈 Plot Manager - - - - - {/* Active Plot Sessions with Real Chart.js Plots */} - - - 🎛️ Active Plot Sessions - - Real-time Chart.js plots with streaming data from PLC - - - - {getPlotDefinitions().length === 0 ? ( - - No plot sessions configured. Create plot definitions below to get started. - - ) : ( - - {getPlotDefinitions().map((plotDef) => ( - - ))} - - )} - - - - - - {/* RJSF Configuration Forms */} - - {/* Plot Definitions - Collapsible */} - `${item.name || item.id} (${item.time_window || 60}s)`} - /> - - {/* Plot Variables Configuration */} - - - ⚙️ Plot Variables Configuration - - Select a plot session, then configure its variables and visual settings - - - - - - {/* Step 1: Plot Selector (Combo) */} - - - - 🎯 Select Plot Session - - - {availablePlots.length === 0 && ( - - ⚠️ No plot sessions available. Configure plot definitions first in the "Plot Definitions" tab. - - )} - - - {/* Variables Configuration Form */} - {selectedPlotId && ( - - - - ⚙️ Configure Variables for Plot "{selectedPlotId}" - - - {/* Simplified schema for selected plot variables */} - {(() => { - const selectedPlotVars = getSelectedPlotVariables() - - // Schema for this plot's variables - const singlePlotSchema = { - type: "object", - properties: { - variables: { - type: "array", - title: "Variables", - description: `Variables to display in plot ${selectedPlotId}`, - items: { - type: "object", - title: "Plot Variable", - properties: { - variable_name: { - type: "string", - title: "Variable Name", - description: "Select variable from datasets with search and metadata" - }, - label: { - type: "string", - title: "Display Label", - description: "Label shown in the plot legend" - }, - color: { - type: "string", - title: "Line Color", - default: "#3182CE" - }, - line_width: { - type: "number", - title: "Line Width", - default: 2, - minimum: 1, - maximum: 10 - }, - y_axis: { - type: "string", - title: "Y-Axis", - enum: ["left", "right"], - default: "left" - } - }, - required: ["variable_name", "label"] - } - } - } - } - - const singlePlotUiSchema = { - variables: { - items: { - "ui:layout": [[ - { "name": "variable_name", "width": 4 }, - { "name": "label", "width": 2 }, - { "name": "color", "width": 2 }, - { "name": "line_width", "width": 2 }, - { "name": "y_axis", "width": 2 } - ]], - variable_name: { - "ui:widget": "variableSelector", - "ui:placeholder": "Search and select variable from datasets...", - "ui:help": "🔍 Search variables from configured datasets with live values and metadata" - }, - label: { - "ui:placeholder": "Chart legend label..." - }, - color: { - "ui:widget": "color" - }, - line_width: { - "ui:widget": "updown" - } - } - } - } - - return ( -
{ - updateSelectedPlotVariables(formData) - // Create updated config and save it - const updatedVariables = plotsVariablesConfig.variables?.map(v => - v.plot_id === selectedPlotId - ? { ...v, ...formData } - : v - ) || [] - - // If plot not found, add new entry - if (!plotsVariablesConfig.variables?.find(v => v.plot_id === selectedPlotId)) { - updatedVariables.push({ - plot_id: selectedPlotId, - ...formData - }) - } - - const updatedConfig = { ...plotsVariablesConfig, variables: updatedVariables } - savePlotsVariablesConfig(updatedConfig) - }} - onChange={({ formData }) => updateSelectedPlotVariables(formData)} - > - - - - -
- ) - })()} -
- )} - - {!selectedPlotId && availablePlots.length > 0 && ( - - - 👆 Select a plot session above to configure its variables - - - )} -
-
-
- - - -
- ) -} diff --git a/frontend/src/components/PlotManagerSimple.jsx b/frontend/src/components/PlotManagerSimple.jsx index fdfbb41..420610c 100644 --- a/frontend/src/components/PlotManagerSimple.jsx +++ b/frontend/src/components/PlotManagerSimple.jsx @@ -207,7 +207,7 @@ function CollapsiblePlotChart({ plotDefinition, plotVariables, onConfigUpdate, o 📈 {plotDefinition.name || plotDefinition.id} - {plotDefinition.time_window}s window • {plotVariables?.variables?.length || 0} variables + {plotDefinition.time_window}s window • {plotVariables?.length || 0} variables @@ -271,7 +271,11 @@ export default function PlotManager() { if (!selectedPlotId && plotsData?.plots?.length > 0) { setSelectedPlotId(plotsData.plots[0].id) } - setPlotsSchemaData(plotsSchemaResponse) + + console.log('✅ Plot data loaded:', { + plots: plotsData?.plots?.length || 0, + plotVariables: plotVariablesData?.variables?.length || 0 + }) } catch (error) { toast({ title: '❌ Failed to load plot configurations', @@ -282,7 +286,7 @@ export default function PlotManager() { } finally { setLoading(false) } - }, [toast]) + }, [selectedPlotId, toast]) const savePlotsConfig = async (formData) => { try { @@ -335,6 +339,17 @@ export default function PlotManager() { return ['DAR', 'Fast', 'Slow'] // TODO: Get from actual dataset definitions } + // Helper to get plot variables for any plot ID + const getPlotVariables = useCallback((plotId) => { + if (!plotId || !plotVariablesConfig?.variables) return [] + + const plotVars = plotVariablesConfig.variables.find( + item => item.plot_id === plotId + ) + // Return the variables array directly, not the wrapper object + return plotVars?.variables || [] + }, [plotVariablesConfig]) + // Helper functions for Type 3 form pattern (Plot Variables) const getSelectedPlotVariables = () => { if (!selectedPlotId || !plotVariablesConfig?.variables) return { variables: [] } @@ -420,8 +435,8 @@ export default function PlotManager() { {}} + plotVariables={getPlotVariables(plotDef.id)} + onConfigUpdate={loadPlotData} onRemove={() => {}} /> ))} diff --git a/frontend/src/components/PlotRealtimeSession.jsx b/frontend/src/components/PlotRealtimeSession.jsx index bf11b43..ff7daff 100644 --- a/frontend/src/components/PlotRealtimeSession.jsx +++ b/frontend/src/components/PlotRealtimeSession.jsx @@ -154,7 +154,7 @@ export default function PlotRealtimeSession({ }, [plotDefinition, plotVariables]) // Control plot session (start, pause, stop, clear) - const handleControlClick = async (action) => { + const handleControlClick = useCallback(async (action) => { // Send command to backend first try { // For 'start' action, create the plot session first if it doesn't exist @@ -228,7 +228,7 @@ export default function PlotRealtimeSession({ // Revert local state on error await refreshSessionStatus() } - } + }, [plotDefinition, plotVariables, localConfig, refreshSessionStatus, toast]) // Apply configuration changes const applyConfigChanges = async () => { @@ -278,6 +278,11 @@ export default function PlotRealtimeSession({ // Refresh plot configuration and recreate chart const refreshPlotConfiguration = useCallback(async () => { setIsRefreshing(true) + + // Remember the current active state before refresh + const wasActive = session.is_active + const wasPaused = session.is_paused + try { console.log(`🔄 Refreshing configuration for plot ${plotDefinition.id}...`) @@ -289,6 +294,31 @@ export default function PlotRealtimeSession({ // Also refresh session status await refreshSessionStatus() + // If the plot was active before refresh, try to restart it + if (wasActive && !wasPaused) { + console.log(`🔄 Plot was active before refresh, attempting to restart...`) + try { + // Wait a bit to ensure the session status has been updated + await new Promise(resolve => setTimeout(resolve, 500)) + + // Try to restart the plot session + await handleControlClick('start') + + console.log(`✅ Plot ${plotDefinition.id} restarted after refresh`) + } catch (restartError) { + console.warn(`⚠️ Could not restart plot ${plotDefinition.id} after refresh:`, restartError) + toast({ + title: '⚠️ Plot was stopped during refresh', + description: 'Please click Start to resume plotting', + status: 'warning', + duration: 4000 + }) + } + } else if (wasActive && wasPaused) { + console.log(`🔄 Plot was paused before refresh, maintaining paused state`) + // The session should maintain its paused state + } + toast({ title: '🔄 Configuration refreshed', description: 'Plot configuration and variables have been updated', @@ -308,7 +338,7 @@ export default function PlotRealtimeSession({ } finally { setIsRefreshing(false) } - }, [plotDefinition.id, refreshSessionStatus, toast]) + }, [plotDefinition.id, refreshSessionStatus, handleControlClick, session.is_active, session.is_paused, toast]) // Auto-refresh session status useEffect(() => { diff --git a/frontend/src/pages/DashboardNew.jsx b/frontend/src/pages/DashboardNew.jsx deleted file mode 100644 index 5c59bcf..0000000 --- a/frontend/src/pages/DashboardNew.jsx +++ /dev/null @@ -1,876 +0,0 @@ -import React, { useState, useEffect, useRef, useCallback } from 'react' -import { - Box, - Container, - VStack, - Heading, - Card, - CardBody, - CardHeader, - Button, - Text, - Flex, - Spacer, - HStack, - useColorModeValue, - useToast, - Alert, - AlertIcon, - Spinner, - Badge, - SimpleGrid, - Stat, - StatLabel, - StatNumber, - StatHelpText, - Divider, - Table, - Thead, - Tbody, - Tr, - Th, - Td, - TableContainer, - Code, - Select -} from '@chakra-ui/react' -import Form from '@rjsf/chakra-ui' -import validator from '@rjsf/validator-ajv8' -import PlotManager from '../components/PlotManager' -import allWidgets from '../components/widgets/AllWidgets' -import LayoutObjectFieldTemplate from '../components/rjsf/LayoutObjectFieldTemplate' -import { VariableProvider, useVariableContext } from '../contexts/VariableContext' -import * as api from '../services/api' - -// StatusBar Component - Real-time PLC status with action buttons -function StatusBar({ status, onRefresh }) { - const plcConnected = !!status?.plc_connected - const streaming = !!status?.streaming - const csvRecording = !!status?.csv_recording - const [actionLoading, setActionLoading] = useState({}) - const toast = useToast() - - const setLoading = (action, loading) => { - setActionLoading(prev => ({ ...prev, [action]: loading })) - } - - const handleConnectPlc = async () => { - setLoading('connect', true) - try { - const result = await api.connectPlc() - toast({ - title: '🔗 PLC Connection', - description: result.message || 'Connection initiated', - status: 'info', - duration: 2000 - }) - setTimeout(() => onRefresh?.(), 1000) - } catch (error) { - toast({ - title: '❌ Failed to connect PLC', - description: error.message, - status: 'error', - duration: 3000 - }) - } finally { - setLoading('connect', false) - } - } - - const handleDisconnectPlc = async () => { - setLoading('disconnect', true) - try { - const result = await api.disconnectPlc() - toast({ - title: '❌ PLC Disconnection', - description: result.message || 'Disconnection initiated', - status: 'info', - duration: 2000 - }) - setTimeout(() => onRefresh?.(), 1000) - } catch (error) { - toast({ - title: '❌ Failed to disconnect PLC', - description: error.message, - status: 'error', - duration: 3000 - }) - } finally { - setLoading('disconnect', false) - } - } - - const handleStartStreaming = async () => { - setLoading('startStream', true) - try { - const result = await api.startUdpStreaming() - toast({ - title: '📡 UDP Streaming started', - description: result.message || 'Streaming initiated', - status: 'success', - duration: 2000 - }) - setTimeout(() => onRefresh?.(), 1000) - } catch (error) { - toast({ - title: '❌ Failed to start streaming', - description: error.message, - status: 'error', - duration: 3000 - }) - } finally { - setLoading('startStream', false) - } - } - - const handleStopStreaming = async () => { - setLoading('stopStream', true) - try { - const result = await api.stopUdpStreaming() - toast({ - title: '⏹️ UDP Streaming stopped', - description: result.message || 'Streaming stopped', - status: 'info', - duration: 2000 - }) - setTimeout(() => onRefresh?.(), 1000) - } catch (error) { - toast({ - title: '❌ Failed to stop streaming', - description: error.message, - status: 'error', - duration: 3000 - }) - } finally { - setLoading('stopStream', false) - } - } - - return ( - - - - - 🔌 PLC Connection - - {plcConnected ? 'Connected' : 'Disconnected'} - - {status?.plc_reconnection?.enabled && ( - - 🔄 Auto-reconnection: {status?.plc_reconnection?.active ? 'reconnecting…' : 'enabled'} - - )} - - {plcConnected ? ( - - ) : ( - - )} - - - - - - - - - 📡 UDP Streaming - - {streaming ? 'Active' : 'Inactive'} - - - {streaming ? ( - - ) : ( - - )} - - - - - - - - - 💾 CSV Recording - - {csvRecording ? 'Recording' : 'Inactive'} - - {status?.disk_space_info && ( - - 💽 {status.disk_space_info.free_space} free
- ⏱️ ~{status.disk_space_info.recording_time_left} -
- )} -
-
-
-
- ) -} - -// PLC Configuration Panel - Fixed to PLC & UDP settings only -function ConfigurationPanel({ schemaData, formData, onFormChange, onSave, saving, message }) { - const cardBg = useColorModeValue('white', 'gray.700') - const borderColor = useColorModeValue('gray.200', 'gray.600') - - if (!schemaData?.schema || !formData) { - return ( - - - Loading PLC configuration... - - - ) - } - - return ( - - - - 🔧 PLC & UDP Configuration - - Configure PLC connection settings and UDP streaming parameters - - - {message && ( - - - {message} - - )} - - -
onFormChange(formData)} - onSubmit={({ formData }) => onSave(formData)} - > - - - - -
-
-
- ) -} - -// Dataset Manager - Type 3 Form Pattern implementation -function DatasetManager() { - const { triggerVariableRefresh } = useVariableContext() - const [datasetsConfig, setDatasetsConfig] = useState(null) - const [variablesConfig, setVariablesConfig] = useState(null) - const [datasetsSchemaData, setDatasetsSchemaData] = useState(null) - const [variablesSchemaData, setVariablesSchemaData] = useState(null) - const [selectedDatasetId, setSelectedDatasetId] = useState('') - const [loading, setLoading] = useState(true) - const toast = useToast() - - const loadDatasetData = async () => { - try { - setLoading(true) - const [datasetsData, variablesData, datasetsSchemaResponse, variablesSchemaResponse] = await Promise.all([ - api.readConfig('dataset-definitions'), - api.readConfig('dataset-variables'), - api.getSchema('dataset-definitions'), - api.getSchema('dataset-variables') - ]) - - setDatasetsConfig(datasetsData) - setVariablesConfig(variablesData) - setDatasetsSchemaData(datasetsSchemaResponse) - setVariablesSchemaData(variablesSchemaResponse) - - // Auto-select first dataset if none selected - if (!selectedDatasetId && datasetsData?.datasets?.length > 0) { - setSelectedDatasetId(datasetsData.datasets[0].id) - } - } catch (error) { - toast({ - title: '❌ Failed to load dataset data', - description: error.message, - status: 'error', - duration: 3000 - }) - } finally { - setLoading(false) - } - } - - const saveDatasets = async (formData) => { - try { - await api.writeConfig('dataset-definitions', formData) - toast({ - title: '✅ Dataset definitions saved', - status: 'success', - duration: 2000 - }) - setDatasetsConfig(formData) - } catch (error) { - toast({ - title: '❌ Failed to save datasets', - description: error.message, - status: 'error', - duration: 3000 - }) - } - } - - const saveVariables = async (formData) => { - try { - await api.writeConfig('dataset-variables', formData) - toast({ - title: '✅ Dataset variables saved', - status: 'success', - duration: 2000 - }) - setVariablesConfig(formData) - // Trigger refresh of all variable selector widgets - triggerVariableRefresh() - } catch (error) { - toast({ - title: '❌ Failed to save variables', - description: error.message, - status: 'error', - duration: 3000 - }) - } - } - - // Get filtered variables for selected dataset - const getSelectedDatasetVariables = () => { - if (!variablesConfig?.variables || !selectedDatasetId) { - return { variables: [] } - } - - const datasetVars = variablesConfig.variables.find(v => v.dataset_id === selectedDatasetId) - return datasetVars || { variables: [] } - } - - // Update variables for selected dataset - const updateSelectedDatasetVariables = (newVariableData) => { - if (!variablesConfig?.variables || !selectedDatasetId) return - - const updatedVariables = variablesConfig.variables.map(v => - v.dataset_id === selectedDatasetId - ? { ...v, ...newVariableData } - : v - ) - - // If dataset not found, add new entry - if (!variablesConfig.variables.find(v => v.dataset_id === selectedDatasetId)) { - updatedVariables.push({ - dataset_id: selectedDatasetId, - ...newVariableData - }) - } - - const updatedConfig = { ...variablesConfig, variables: updatedVariables } - setVariablesConfig(updatedConfig) - } - - // Available datasets for combo selector - const availableDatasets = datasetsConfig?.datasets || [] - - useEffect(() => { - loadDatasetData() - }, []) - - if (loading) { - return ( - - - - - Loading dataset configurations... - - - - ) - } - - return ( - - - 📊 Dataset Manager - - - - - - - 📋 Dataset Definitions - ⚙️ Dataset Variables - - - - - {datasetsSchemaData?.schema && datasetsConfig && ( - - - Dataset Metadata Configuration - - Configure dataset names, prefixes, sampling intervals and enable/disable datasets - - - -
saveDatasets(formData)} - onChange={({ formData }) => setDatasetsConfig(formData)} - > - - - - -
-
-
- )} -
- - - {/* Dataset Variables Configuration with Combo Selector */} - - - Dataset Variables Configuration - - Select a dataset, then configure its PLC variables and streaming settings - - - - {/* Step 1: Dataset Selector (Combo) */} - - - - 🎯 Select Dataset - - - {availableDatasets.length === 0 && ( - - ⚠️ No datasets available. Configure datasets first in the "Dataset Definitions" tab. - - )} - - - {/* Variables Configuration Form */} - {selectedDatasetId && ( - - - - ⚙️ Configure Variables for Dataset "{selectedDatasetId}" - - - {/* Simplified schema for selected dataset variables */} - {(() => { - const selectedDatasetVars = getSelectedDatasetVariables() - - // Schema for this dataset's variables - const singleDatasetSchema = { - type: "object", - properties: { - variables: { - type: "array", - title: "Variables", - description: `PLC variables to record in dataset ${selectedDatasetId}`, - items: { - type: "object", - properties: { - name: { type: "string", title: "Variable Name" }, - area: { - type: "string", - title: "Memory Area", - enum: ["db", "mw", "m", "pew", "pe", "paw", "pa", "e", "a", "mb"], - default: "db" - }, - db: { type: "integer", title: "DB Number", minimum: 1, maximum: 9999 }, - offset: { type: "integer", title: "Offset", minimum: 0, maximum: 8191 }, - bit: { type: "integer", title: "Bit Position", minimum: 0, maximum: 7 }, - type: { - type: "string", - title: "Data Type", - enum: ["real", "int", "dint", "bool", "word", "byte"], - default: "real" - }, - streaming: { type: "boolean", title: "Stream to UDP", default: false } - }, - required: ["name", "area", "offset", "type"] - } - } - } - } - - const singleDatasetUiSchema = { - variables: { - items: { - "ui:layout": [[ - { "name": "name", "width": 3 }, - { "name": "area", "width": 2 }, - { "name": "db", "width": 1 }, - { "name": "offset", "width": 2 }, - { "name": "type", "width": 2 }, - { "name": "streaming", "width": 2 } - ]] - } - } - } - - return ( -
{ - updateSelectedDatasetVariables(formData) - saveVariables(variablesConfig).then(() => { - // Additional trigger after successful save - triggerVariableRefresh() - }) - }} - onChange={({ formData }) => updateSelectedDatasetVariables(formData)} - > - - - - -
- ) - })()} -
- )} - - {!selectedDatasetId && availableDatasets.length > 0 && ( - - - 👆 Select a dataset above to configure its variables - - - )} -
-
-
-
-
-
-
- ) -} - -// Events Display Component -function EventsDisplay({ events, loading, onRefresh }) { - const cardBg = useColorModeValue('white', 'gray.700') - - if (loading) { - return ( - - - - - Loading events... - - - - ) - } - - return ( - - - - 📋 Recent Events - - - - - - - - - - - - - - - - {events?.map((event, index) => ( - - - - - - ))} - -
TimeTypeMessage
- - {new Date(event.timestamp).toLocaleString()} - - - - {event.level} - - {event.message}
-
- {(!events || events.length === 0) && ( - - No events found - - )} -
-
- ) -} - -// Main Dashboard Component - PLC S7-31x Streamer & Logger -export default function Dashboard() { - return ( - - - - ) -} - -// Dashboard Content Component (separated to use context) -function DashboardContent() { - const [status, setStatus] = useState(null) - const [statusLoading, setStatusLoading] = useState(true) - const [statusError, setStatusError] = useState('') - - const [schemaData, setSchemaData] = useState(null) - const [formData, setFormData] = useState(null) - const [saving, setSaving] = useState(false) - const [message, setMessage] = useState('') - - const [events, setEvents] = useState([]) - const [eventsLoading, setEventsLoading] = useState(false) - - // Load status once - const loadStatus = useCallback(async () => { - try { - setStatusLoading(true) - setStatusError('') - const statusData = await api.getStatus() - setStatus(statusData) - } catch (error) { - setStatusError(error.message) - } finally { - setStatusLoading(false) - } - }, []) - - // Real-time status updates via polling - const subscribeSSE = useCallback(() => { - // Use polling for real-time updates (every 5 seconds) - const interval = setInterval(async () => { - try { - const statusData = await api.getStatus() - setStatus(statusData) - setStatusError('') - } catch (error) { - console.error('Status polling error:', error) - } - }, 5000) - - return () => { - clearInterval(interval) - } - }, []) - - // Load PLC config - const loadConfig = useCallback(async () => { - try { - const [schemaResponse, configData] = await Promise.all([ - api.getSchema('plc'), - api.readConfig('plc') - ]) - setSchemaData(schemaResponse) - setFormData(configData) - setMessage('') - } catch (error) { - console.error('Failed to load PLC config:', error) - } - }, []) - - // Save config - const saveConfig = useCallback(async (data) => { - try { - setSaving(true) - await api.writeConfig('plc', data) - setMessage(`✅ PLC configuration saved successfully`) - setTimeout(() => setMessage(''), 3000) - setFormData(data) - } catch (error) { - setMessage(`❌ Failed to save: ${error.message}`) - } finally { - setSaving(false) - } - }, []) - - // Load events - const loadEvents = useCallback(async () => { - try { - setEventsLoading(true) - const eventsData = await api.getEvents(50) - setEvents(eventsData.events || []) - } catch (error) { - console.error('Failed to load events:', error) - } finally { - setEventsLoading(false) - } - }, []) - - // Effects - useEffect(() => { - loadStatus() - loadConfig() - loadEvents() - - const cleanup = subscribeSSE() - return cleanup - }, [loadStatus, loadConfig, loadEvents, subscribeSSE]) - - if (statusLoading) { - return ( - - - - Loading dashboard... - - - ) - } - - return ( - - - - 🏭 PLC S7-31x Streamer & Logger - - - - - {statusError && ( - - - Failed to load status: {statusError} - - )} - - - - {/* PLC Configuration Section */} - - - {/* Dataset Management Section */} - - - {/* Plot Management Section */} - - - {/* Events Section */} - - - - ) -} diff --git a/system_state.json b/system_state.json index 3c507fa..384c86b 100644 --- a/system_state.json +++ b/system_state.json @@ -3,11 +3,11 @@ "should_connect": true, "should_stream": true, "active_datasets": [ - "Fast", + "DAR", "Test", - "DAR" + "Fast" ] }, "auto_recovery_enabled": true, - "last_update": "2025-08-14T14:47:13.218027" + "last_update": "2025-08-14T15:03:55.394271" } \ No newline at end of file diff --git a/validate_schema.py b/validate_schema.py deleted file mode 100644 index dd2dd77..0000000 --- a/validate_schema.py +++ /dev/null @@ -1,41 +0,0 @@ -import json -import jsonschema - -# Cargar esquema y datos para dataset-definitions -with open("config/schema/dataset-definitions.schema.json", "r") as f: - schema = json.load(f) - -with open("config/data/dataset_definitions.json", "r") as f: - data = json.load(f) - -# Validar -try: - jsonschema.validate(data, schema) - print("✅ Dataset definitions validation successful!") -except jsonschema.ValidationError as e: - print("❌ Dataset definitions validation error:") - print(f'Property path: {".".join(str(x) for x in e.absolute_path)}') - print(f"Message: {e.message}") - print(f"Failed value: {e.instance}") -except Exception as e: - print(f"❌ Other error: {e}") - -print("\n" + "=" * 50 + "\n") - -# También validar PLC config -with open("config/schema/plc.schema.json", "r") as f: - plc_schema = json.load(f) - -with open("config/data/plc_config.json", "r") as f: - plc_data = json.load(f) - -try: - jsonschema.validate(plc_data, plc_schema) - print("✅ PLC config validation successful!") -except jsonschema.ValidationError as e: - print("❌ PLC config validation error:") - print(f'Property path: {".".join(str(x) for x in e.absolute_path)}') - print(f"Message: {e.message}") - print(f"Failed value: {e.instance}") -except Exception as e: - print(f"❌ Other error: {e}")