feat: Update application events and system state management with new plot session events, enhanced plot definitions, and improved PlotManager component for better session control and configuration handling.

This commit is contained in:
Miguel 2025-08-14 16:02:10 +02:00
parent a9396ec309
commit 16355c4106
6 changed files with 879 additions and 96 deletions

View File

@ -911,8 +911,586 @@
"trigger_variable": null,
"auto_started": true
}
},
{
"timestamp": "2025-08-14T15:05:13.082016",
"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:05:19.544702",
"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:05:23.384537",
"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:05:25.630774",
"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:28:19.815284",
"level": "info",
"event_type": "application_started",
"message": "Application initialization completed successfully",
"details": {}
},
{
"timestamp": "2025-08-14T15:28:19.884191",
"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:28:19.888188",
"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:28:19.891188",
"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:28:19.913733",
"level": "error",
"event_type": "csv_cleanup_failed",
"message": "CSV cleanup failed: 'max_hours'",
"details": {}
},
{
"timestamp": "2025-08-14T15:28:27.534594",
"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:28:41.233200",
"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:33:53.298915",
"level": "info",
"event_type": "application_started",
"message": "Application initialization completed successfully",
"details": {}
},
{
"timestamp": "2025-08-14T15:33:53.350464",
"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:33:53.353473",
"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:33:53.357410",
"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:33:53.379922",
"level": "error",
"event_type": "csv_cleanup_failed",
"message": "CSV cleanup failed: 'max_hours'",
"details": {}
},
{
"timestamp": "2025-08-14T15:34:05.357970",
"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:49:43.574411",
"level": "info",
"event_type": "application_started",
"message": "Application initialization completed successfully",
"details": {}
},
{
"timestamp": "2025-08-14T15:49:43.623131",
"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:49:43.626396",
"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:49:43.628399",
"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:49:43.654855",
"level": "error",
"event_type": "csv_cleanup_failed",
"message": "CSV cleanup failed: 'max_hours'",
"details": {}
},
{
"timestamp": "2025-08-14T15:49:51.181481",
"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:50:01.495815",
"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:50:11.269643",
"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:50:25.266473",
"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:51:28.701739",
"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": 40,
"trigger_variable": null,
"auto_started": true
}
},
{
"timestamp": "2025-08-14T15:51:37.177827",
"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": 40,
"trigger_variable": null,
"auto_started": true
}
},
{
"timestamp": "2025-08-14T15:52:00.981320",
"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": 40,
"trigger_variable": null,
"auto_started": true
}
},
{
"timestamp": "2025-08-14T15:54:01.861161",
"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": 40,
"trigger_variable": null,
"auto_started": true
}
},
{
"timestamp": "2025-08-14T15:54:09.237860",
"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:54:21.495214",
"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:58:52.564607",
"level": "info",
"event_type": "application_started",
"message": "Application initialization completed successfully",
"details": {}
},
{
"timestamp": "2025-08-14T15:58:52.631147",
"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:58:52.636144",
"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:58:52.640144",
"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:58:52.661477",
"level": "error",
"event_type": "csv_cleanup_failed",
"message": "CSV cleanup failed: 'max_hours'",
"details": {}
},
{
"timestamp": "2025-08-14T16:00:00.366424",
"level": "error",
"event_type": "csv_cleanup_failed",
"message": "CSV cleanup failed: 'max_hours'",
"details": {}
},
{
"timestamp": "2025-08-14T16:00:38.957148",
"level": "info",
"event_type": "application_started",
"message": "Application initialization completed successfully",
"details": {}
},
{
"timestamp": "2025-08-14T16:00:39.022563",
"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-14T16:00:39.027564",
"level": "info",
"event_type": "csv_recording_started",
"message": "CSV recording started: 1 datasets activated",
"details": {
"activated_datasets": 1,
"total_datasets": 3
}
},
{
"timestamp": "2025-08-14T16:00:39.029563",
"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-14T16:00:39.084155",
"level": "error",
"event_type": "csv_cleanup_failed",
"message": "CSV cleanup failed: 'max_hours'",
"details": {}
},
{
"timestamp": "2025-08-14T16:01:29.356193",
"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-14T16:01:35.624303",
"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-14T16:01:44.863171",
"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-14T16:01:52.736771",
"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-14T15:04:12.217187",
"total_entries": 90
"last_updated": "2025-08-14T16:01:52.736771",
"total_entries": 137
}

View File

@ -5,7 +5,7 @@
"line_tension": 0,
"name": "UR29",
"point_hover_radius": 4,
"point_radius": 1,
"point_radius": 4,
"stepped": true,
"time_window": 20,
"trigger_enabled": false,

View File

@ -4,20 +4,20 @@
"plot_id": "plot_1",
"variables": [
{
"color": "#3498db",
"enabled": true,
"label": "Brix",
"line_width": 2,
"variable_name": "UR29_Brix",
"y_axis": "left"
"label": "Brix",
"color": "#3498db",
"line_width": 2,
"y_axis": "left",
"enabled": true
},
{
"color": "#e74c3c",
"enabled": true,
"label": "ma",
"line_width": 2,
"variable_name": "UR29_ma",
"y_axis": "left"
"label": "ma",
"color": "#dce740",
"line_width": 2,
"y_axis": "left",
"enabled": true
}
]
}

View File

@ -35,13 +35,16 @@ import { useVariableContext } from '../contexts/VariableContext'
import * as api from '../services/api'
// Collapsible Plot Items Form - Each item in the array is individually collapsible
function CollapsiblePlotItemsForm({ data, schema, uiSchema, onSave, title, icon, getItemLabel }) {
function CollapsiblePlotItemsForm({ data, schema, uiSchema, onSave, title, icon, getItemLabel, isExpanded, onToggleExpansion }) {
const [formData, setFormData] = useState(data)
const [expandedItems, setExpandedItems] = useState(new Set())
useEffect(() => {
setFormData(data)
}, [data])
// Solo actualizar formData si data realmente cambió en contenido
if (JSON.stringify(data) !== JSON.stringify(formData)) {
setFormData(data)
}
}, [data]) // Removed formData from dependencies to avoid infinite loop
if (!schema || !formData) {
return (
@ -97,8 +100,13 @@ function CollapsiblePlotItemsForm({ data, schema, uiSchema, onSave, title, icon,
setExpandedItems(newExpanded)
}
const saveChanges = () => {
onSave(formData)
const saveChanges = async () => {
try {
await onSave(formData)
// No hacer nada con la expansión aquí - será manejado por el componente padre
} catch (error) {
console.error('Error saving:', error)
}
}
// Get item schema from the array schema
@ -119,6 +127,17 @@ function CollapsiblePlotItemsForm({ data, schema, uiSchema, onSave, title, icon,
</Text>
</Box>
<HStack spacing={2}>
{/* Si se proporciona toggle de expansión externa, agregar botón de colapso */}
{onToggleExpansion && (
<Button
size="sm"
variant="outline"
onClick={onToggleExpansion}
rightIcon={isExpanded ? <ChevronUpIcon /> : <ChevronDownIcon />}
>
{isExpanded ? 'Collapse' : 'Expand'}
</Button>
)}
<Button size="sm" colorScheme="green" onClick={addItem}>
Add Item
</Button>
@ -129,76 +148,144 @@ function CollapsiblePlotItemsForm({ data, schema, uiSchema, onSave, title, icon,
</Flex>
</CardHeader>
<CardBody>
{items.length === 0 ? (
<Box textAlign="center" py={8}>
<Text color="gray.500" mb={4}>
No items configured yet
</Text>
<Button colorScheme="green" onClick={addItem}>
Add First Item
</Button>
</Box>
) : (
<VStack spacing={3} align="stretch">
{items.map((item, index) => {
const isExpanded = expandedItems.has(index)
const itemLabel = getItemLabel ? getItemLabel(item) : (item.name || item.id || `Item ${index + 1}`)
return (
<Card key={index} variant="outline" size="sm">
<CardHeader py={2}>
<Flex align="center" justify="space-between">
<HStack spacing={2}>
{/* Usar Collapse si se proporciona estado de expansión externo */}
{onToggleExpansion ? (
<Collapse in={isExpanded}>
<CardBody>
{items.length === 0 ? (
<Box textAlign="center" py={8}>
<Text color="gray.500" mb={4}>
No items configured yet
</Text>
<Button colorScheme="green" onClick={addItem}>
Add First Item
</Button>
</Box>
) : (
<VStack spacing={3} align="stretch">
{items.map((item, index) => {
const isItemExpanded = expandedItems.has(index)
const itemLabel = getItemLabel ? getItemLabel(item) : (item.name || item.id || `Item ${index + 1}`)
return (
<Card key={index} variant="outline" size="sm">
<CardHeader py={2}>
<Flex align="center" justify="space-between">
<HStack spacing={2}>
<Button
size="xs"
variant="ghost"
onClick={() => toggleItemExpansion(index)}
rightIcon={isItemExpanded ? <ChevronUpIcon /> : <ChevronDownIcon />}
>
{itemLabel}
</Button>
<Badge colorScheme="green" size="sm">#{index + 1}</Badge>
</HStack>
<Button
size="xs"
colorScheme="red"
variant="ghost"
onClick={() => removeItem(index)}
>
🗑
</Button>
</Flex>
</CardHeader>
<Collapse in={isItemExpanded}>
<CardBody pt={0}>
<Form
schema={itemSchema}
uiSchema={itemUiSchema}
formData={item}
validator={validator}
widgets={allWidgets}
templates={{ ObjectFieldTemplate: LayoutObjectFieldTemplate }}
onChange={({ formData: newItemData }) => updateItem(index, newItemData)}
>
<div></div> {/* Prevents form buttons from showing */}
</Form>
</CardBody>
</Collapse>
</Card>
)
})}
</VStack>
)}
</CardBody>
</Collapse>
) : (
<CardBody>
{items.length === 0 ? (
<Box textAlign="center" py={8}>
<Text color="gray.500" mb={4}>
No items configured yet
</Text>
<Button colorScheme="green" onClick={addItem}>
Add First Item
</Button>
</Box>
) : (
<VStack spacing={3} align="stretch">
{items.map((item, index) => {
const isItemExpanded = expandedItems.has(index)
const itemLabel = getItemLabel ? getItemLabel(item) : (item.name || item.id || `Item ${index + 1}`)
return (
<Card key={index} variant="outline" size="sm">
<CardHeader py={2}>
<Flex align="center" justify="space-between">
<HStack spacing={2}>
<Button
size="xs"
variant="ghost"
onClick={() => toggleItemExpansion(index)}
rightIcon={isItemExpanded ? <ChevronUpIcon /> : <ChevronDownIcon />}
>
{itemLabel}
</Button>
<Badge colorScheme="green" size="sm">#{index + 1}</Badge>
</HStack>
<Button
size="xs"
colorScheme="red"
variant="ghost"
onClick={() => toggleItemExpansion(index)}
rightIcon={isExpanded ? <ChevronUpIcon /> : <ChevronDownIcon />}
onClick={() => removeItem(index)}
>
{itemLabel}
🗑
</Button>
<Badge colorScheme="green" size="sm">#{index + 1}</Badge>
</HStack>
<Button
size="xs"
colorScheme="red"
variant="ghost"
onClick={() => removeItem(index)}
>
🗑
</Button>
</Flex>
</CardHeader>
<Collapse in={isExpanded}>
<CardBody pt={0}>
<Form
schema={itemSchema}
uiSchema={itemUiSchema}
formData={item}
validator={validator}
widgets={allWidgets}
templates={{ ObjectFieldTemplate: LayoutObjectFieldTemplate }}
onChange={({ formData: newItemData }) => updateItem(index, newItemData)}
>
<div></div> {/* Prevents form buttons from showing */}
</Form>
</CardBody>
</Collapse>
</Card>
)
})}
</VStack>
)}
</CardBody>
</Flex>
</CardHeader>
<Collapse in={isItemExpanded}>
<CardBody pt={0}>
<Form
schema={itemSchema}
uiSchema={itemUiSchema}
formData={item}
validator={validator}
widgets={allWidgets}
templates={{ ObjectFieldTemplate: LayoutObjectFieldTemplate }}
onChange={({ formData: newItemData }) => updateItem(index, newItemData)}
>
<div></div> {/* Prevents form buttons from showing */}
</Form>
</CardBody>
</Collapse>
</Card>
)
})}
</VStack>
)}
</CardBody>
)}
</Card>
)
}
// Collapsible Plot Component
function CollapsiblePlotChart({ plotDefinition, plotVariables, onConfigUpdate, onRemove }) {
const [isOpen, setIsOpen] = useState(false)
function CollapsiblePlotChart({ plotDefinition, plotVariables, onConfigUpdate, onReloadConfig, onRemove, isExpanded, onToggleExpansion }) {
return (
<Card>
@ -214,10 +301,10 @@ function CollapsiblePlotChart({ plotDefinition, plotVariables, onConfigUpdate, o
<Button
size="xs"
variant="outline"
onClick={() => setIsOpen(!isOpen)}
rightIcon={isOpen ? <ChevronUpIcon /> : <ChevronDownIcon />}
onClick={() => onToggleExpansion(plotDefinition.id)}
rightIcon={isExpanded ? <ChevronUpIcon /> : <ChevronDownIcon />}
>
{isOpen ? 'Hide' : 'Show'} Chart
{isExpanded ? 'Hide' : 'Show'} Chart
</Button>
<Button size="xs" colorScheme="red" variant="outline" onClick={() => onRemove(plotDefinition.id)}>
@ -226,12 +313,13 @@ function CollapsiblePlotChart({ plotDefinition, plotVariables, onConfigUpdate, o
</Flex>
</CardHeader>
<Collapse in={isOpen}>
<Collapse in={isExpanded}>
<CardBody pt={0}>
<PlotRealtimeSession
plotDefinition={plotDefinition}
plotVariables={plotVariables}
onConfigUpdate={onConfigUpdate}
onReloadConfig={onReloadConfig}
onRemove={onRemove}
isCollapsed={true}
/>
@ -250,6 +338,10 @@ export default function PlotManager() {
const [plotVariablesSchemaData, setPlotVariablesSchemaData] = useState(null)
const [selectedPlotId, setSelectedPlotId] = useState('')
const [loading, setLoading] = useState(true)
// Estado para preservar qué plots están expandidos/colapsados
const [expandedPlots, setExpandedPlots] = useState(new Set())
// Estado para preservar si la configuración de plot definitions está expandida
const [configExpanded, setConfigExpanded] = useState(false)
const toast = useToast()
const loadPlotData = useCallback(async () => {
@ -288,10 +380,38 @@ export default function PlotManager() {
}
}, [selectedPlotId, toast])
// Función para actualizar configuración de un plot específico sin recargar todo
const updatePlotConfig = async (plotId, newConfig) => {
try {
// Actualizar solo el plot específico en la configuración local
const updatedPlots = plotsConfig?.plots?.map(plot =>
plot.id === plotId ? { ...plot, ...newConfig } : plot
) || []
const updatedConfig = {
...plotsConfig,
plots: updatedPlots
}
// Guardar en el backend
await api.writeConfig('plot-definitions', updatedConfig)
// Actualizar estado local
setPlotsConfig(updatedConfig)
console.log(`✅ Plot ${plotId} configuration updated locally`)
} catch (error) {
console.error(`❌ Failed to update plot ${plotId} config:`, error)
throw error
}
}
const savePlotsConfig = async (formData) => {
try {
await api.writeConfig('plot-definitions', formData)
setPlotsConfig(formData)
// Mantener la configuración expandida después de Apply
setConfigExpanded(true)
toast({
title: '✅ Plot definitions saved',
status: 'success',
@ -350,6 +470,26 @@ export default function PlotManager() {
return plotVars?.variables || []
}, [plotVariablesConfig])
// Functions to handle plot expansion state
const togglePlotExpansion = (plotId) => {
const newExpanded = new Set(expandedPlots)
if (newExpanded.has(plotId)) {
newExpanded.delete(plotId)
} else {
newExpanded.add(plotId)
}
setExpandedPlots(newExpanded)
}
const isPlotExpanded = (plotId) => {
return expandedPlots.has(plotId)
}
// Function to handle configuration expansion toggle
const toggleConfigExpansion = () => {
setConfigExpanded(!configExpanded)
}
// Helper functions for Type 3 form pattern (Plot Variables)
const getSelectedPlotVariables = () => {
if (!selectedPlotId || !plotVariablesConfig?.variables) return { variables: [] }
@ -367,7 +507,7 @@ export default function PlotManager() {
variables: [{ plot_id: selectedPlotId, ...formData }]
}
setPlotVariablesConfig(newConfig)
return
return newConfig
}
const existingIndex = plotVariablesConfig.variables.findIndex(
@ -383,10 +523,13 @@ export default function PlotManager() {
updatedVars.push(newVarData)
}
setPlotVariablesConfig({
const updatedConfig = {
...plotVariablesConfig,
variables: updatedVars
})
}
setPlotVariablesConfig(updatedConfig)
return updatedConfig
}
useEffect(() => {
@ -436,8 +579,11 @@ export default function PlotManager() {
key={plotDef.id}
plotDefinition={plotDef}
plotVariables={getPlotVariables(plotDef.id)}
onConfigUpdate={loadPlotData}
onConfigUpdate={updatePlotConfig}
onReloadConfig={loadPlotData}
onRemove={() => {}}
isExpanded={isPlotExpanded(plotDef.id)}
onToggleExpansion={togglePlotExpansion}
/>
))}
</VStack>
@ -456,6 +602,8 @@ export default function PlotManager() {
title="Plot Definitions"
icon="📋"
getItemLabel={(item) => `${item.name || item.id} (${item.time_window || 60}s)`}
isExpanded={configExpanded}
onToggleExpansion={toggleConfigExpansion}
/>
{/* Plot Variables Configuration - Type 3 Form Pattern */}
@ -611,8 +759,8 @@ export default function PlotManager() {
widgets={allWidgets}
templates={{ ObjectFieldTemplate: LayoutObjectFieldTemplate }}
onSubmit={({ formData }) => {
updateSelectedPlotVariables(formData)
savePlotVariables(plotVariablesConfig).then(() => {
const updatedConfig = updateSelectedPlotVariables(formData)
savePlotVariables(updatedConfig).then(() => {
// Additional trigger after successful save
triggerVariableRefresh?.()
})

View File

@ -44,7 +44,8 @@ export default function PlotRealtimeSession({
plotDefinition,
plotVariables = [],
onRemove,
onConfigUpdate
onConfigUpdate,
onReloadConfig // Nueva prop para recargar configuración desde backend
}) {
const [session, setSession] = useState({
session_id: plotDefinition.id,
@ -74,6 +75,28 @@ export default function PlotRealtimeSession({
const intervalRef = useRef(null)
const toast = useToast()
// Track if we're in the middle of applying changes to avoid conflicts
const applyingChangesRef = useRef(false)
// Update localConfig when plotDefinition changes (but not during our own updates)
useEffect(() => {
if (!applyingChangesRef.current) {
setLocalConfig({
time_window: plotDefinition.time_window || 60,
y_min: plotDefinition.y_min,
y_max: plotDefinition.y_max,
trigger_enabled: plotDefinition.trigger_enabled || false,
trigger_variable: plotDefinition.trigger_variable,
trigger_on_true: plotDefinition.trigger_on_true || true,
// Visual style properties
line_tension: plotDefinition.line_tension !== undefined ? plotDefinition.line_tension : 0.4,
stepped: plotDefinition.stepped || false,
point_radius: plotDefinition.point_radius !== undefined ? plotDefinition.point_radius : 1,
point_hover_radius: plotDefinition.point_hover_radius !== undefined ? plotDefinition.point_hover_radius : 4
})
}
}, [plotDefinition])
const cardBg = useColorModeValue('white', 'gray.700')
const borderColor = useColorModeValue('gray.200', 'gray.600')
const muted = useColorModeValue('gray.600', 'gray.300')
@ -231,8 +254,17 @@ export default function PlotRealtimeSession({
}, [plotDefinition, plotVariables, localConfig, refreshSessionStatus, toast])
// Apply configuration changes
const applyConfigChanges = async () => {
const applyConfigChanges = useCallback(async () => {
applyingChangesRef.current = true
// Remember the current active state before applying changes
const wasActive = session.is_active
const wasPaused = session.is_paused
try {
console.log(`🔄 Applying configuration changes for plot ${plotDefinition.id}...`)
console.log(`📊 Plot was active: ${wasActive}, paused: ${wasPaused}`)
// Update backend configuration
await onConfigUpdate?.(plotDefinition.id, localConfig)
@ -241,13 +273,26 @@ export default function PlotRealtimeSession({
chartControlsRef.current.updateConfig(localConfig)
}
// Wait a moment for the configuration to be applied
await new Promise(resolve => setTimeout(resolve, 500))
// Refresh session status to get the latest state
await refreshSessionStatus()
// If the plot was active before, restart it
if (wasActive && !wasPaused) {
console.log(`🔄 Restarting plot session that was active before Apply...`)
await handleControlClick('start')
}
toast({
title: '✅ Configuration updated',
status: 'success',
duration: 2000
})
setShowSettings(false)
// No cerrar settings automáticamente - mantener abierto para continuar editando
// setShowSettings(false)
} catch (error) {
toast({
title: '❌ Failed to update configuration',
@ -255,8 +300,13 @@ export default function PlotRealtimeSession({
status: 'error',
duration: 3000
})
} finally {
// Reset the flag after a short delay to allow the update to propagate
setTimeout(() => {
applyingChangesRef.current = false
}, 1000)
}
}
}, [plotDefinition.id, localConfig, onConfigUpdate, session.is_active, session.is_paused, refreshSessionStatus, handleControlClick, toast])
const resetConfigChanges = () => {
setLocalConfig({
@ -286,6 +336,13 @@ export default function PlotRealtimeSession({
try {
console.log(`🔄 Refreshing configuration for plot ${plotDefinition.id}...`)
// First, reload configuration from backend if the function is available
if (onReloadConfig) {
console.log(`📥 Reloading plot configuration from backend...`)
await onReloadConfig()
console.log(`✅ Configuration reloaded from backend`)
}
// Trigger chart configuration refresh if available
if (chartControlsRef.current?.refreshConfiguration) {
await chartControlsRef.current.refreshConfiguration()
@ -338,7 +395,7 @@ export default function PlotRealtimeSession({
} finally {
setIsRefreshing(false)
}
}, [plotDefinition.id, refreshSessionStatus, handleControlClick, session.is_active, session.is_paused, toast])
}, [plotDefinition.id, refreshSessionStatus, handleControlClick, session.is_active, session.is_paused, onReloadConfig, toast])
// Auto-refresh session status
useEffect(() => {

View File

@ -4,10 +4,10 @@
"should_stream": true,
"active_datasets": [
"DAR",
"Test",
"Fast"
"Fast",
"Test"
]
},
"auto_recovery_enabled": true,
"last_update": "2025-08-14T15:03:55.394271"
"last_update": "2025-08-14T16:00:39.031573"
}