feat: Enhance dataset variable configuration with manual and symbol-based options

- Updated the dataset variables UI schema to support two configuration types: manual and symbol-based.
- Implemented a new DatasetVariableSymbolWidget for symbol selection with auto-fill capabilities.
- Modified the DatasetManager to handle the new schema structure and UI layout.
- Integrated the new widget into the DatasetVariablesRJSF component for improved user experience.
- Updated the SymbolSelectorWidget to support callbacks for symbol selection.
- Refactored the FormTable component to utilize the new allWidgets import for better widget management.
- Added error handling and loading states for improved user feedback during data fetching.
This commit is contained in:
Miguel 2025-08-14 17:34:32 +02:00
parent e3b1b48556
commit 31cb5cc515
10 changed files with 973 additions and 444 deletions

View File

@ -1,257 +1,5 @@
{
"events": [
{
"timestamp": "2025-08-14T16:52:25.768669",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Could not parse PLC address: DB 2121",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:25.776678",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Unrecognized address format: UDT 82",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:25.785672",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Could not parse PLC address: UDT 82",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:25.794673",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Unrecognized address format: UDT 1",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:25.803233",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Could not parse PLC address: UDT 1",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:25.811243",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Unrecognized address format: VAT 22",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:25.819268",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Could not parse PLC address: VAT 22",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:25.826782",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Unrecognized address format: DB 959",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:25.834787",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Could not parse PLC address: DB 959",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:25.841796",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Unrecognized address format: FC 1804",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:25.848796",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Could not parse PLC address: FC 1804",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:25.857991",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Unrecognized address format: DB 972",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:25.864990",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Could not parse PLC address: DB 972",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:25.872997",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Unrecognized address format: DB 930",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:25.881999",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Could not parse PLC address: DB 930",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:25.890523",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Unrecognized address format: FB 1800",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:25.898534",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Could not parse PLC address: FB 1800",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:25.907703",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Unrecognized address format: DB 971",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:25.914685",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Could not parse PLC address: DB 971",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:25.922698",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Unrecognized address format: DB 970",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:25.929693",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Could not parse PLC address: DB 970",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:25.937844",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Unrecognized address format: FC 2000",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:25.944860",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Could not parse PLC address: FC 2000",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:25.954008",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Unrecognized address format: FC 2036",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:25.962012",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Could not parse PLC address: FC 2036",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:25.971003",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Unrecognized address format: FC 2013",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:25.978013",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Could not parse PLC address: FC 2013",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:25.987006",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Unrecognized address format: FC 2001",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:25.994321",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Could not parse PLC address: FC 2001",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:26.002877",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Unrecognized address format: FC 2003",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:26.009876",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Could not parse PLC address: FC 2003",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:26.017866",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Unrecognized address format: FC 2037",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:26.025863",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Could not parse PLC address: FC 2037",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:26.033864",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Unrecognized address format: FC 2033",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:26.041875",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Could not parse PLC address: FC 2033",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:26.049093",
"level": "warning",
"event_type": "symbol_parse_warning",
"message": "Unrecognized address format: FC 2012",
"details": {}
},
{
"timestamp": "2025-08-14T16:52:26.063705",
"level": "warning",
@ -6999,8 +6747,354 @@
"event_type": "symbols_loaded",
"message": "Loaded 2077 symbols from C:/Users/migue/Downloads/symSAE452.asc",
"details": {}
},
{
"timestamp": "2025-08-14T17:00:00.267126",
"level": "error",
"event_type": "csv_cleanup_failed",
"message": "CSV cleanup failed: 'max_hours'",
"details": {}
},
{
"timestamp": "2025-08-14T17:06:22.110502",
"level": "info",
"event_type": "application_started",
"message": "Application initialization completed successfully",
"details": {}
},
{
"timestamp": "2025-08-14T17:06:22.161221",
"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-14T17:06:22.174891",
"level": "info",
"event_type": "csv_recording_started",
"message": "CSV recording started: 1 datasets activated",
"details": {
"activated_datasets": 1,
"total_datasets": 3
}
},
{
"timestamp": "2025-08-14T17:06:22.183906",
"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-14T17:06:22.192523",
"level": "error",
"event_type": "csv_cleanup_failed",
"message": "CSV cleanup failed: 'max_hours'",
"details": {}
},
{
"timestamp": "2025-08-14T17:10:14.827169",
"level": "info",
"event_type": "application_started",
"message": "Application initialization completed successfully",
"details": {}
},
{
"timestamp": "2025-08-14T17:10:14.876601",
"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-14T17:10:14.884605",
"level": "info",
"event_type": "csv_recording_started",
"message": "CSV recording started: 1 datasets activated",
"details": {
"activated_datasets": 1,
"total_datasets": 3
}
},
{
"timestamp": "2025-08-14T17:10:14.892976",
"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-14T17:10:14.908569",
"level": "error",
"event_type": "csv_cleanup_failed",
"message": "CSV cleanup failed: 'max_hours'",
"details": {}
},
{
"timestamp": "2025-08-14T17:10:32.756676",
"level": "info",
"event_type": "application_started",
"message": "Application initialization completed successfully",
"details": {}
},
{
"timestamp": "2025-08-14T17:10:32.805384",
"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-14T17:10:32.814382",
"level": "info",
"event_type": "csv_recording_started",
"message": "CSV recording started: 1 datasets activated",
"details": {
"activated_datasets": 1,
"total_datasets": 3
}
},
{
"timestamp": "2025-08-14T17:10:32.823381",
"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-14T17:10:32.837621",
"level": "error",
"event_type": "csv_cleanup_failed",
"message": "CSV cleanup failed: 'max_hours'",
"details": {}
},
{
"timestamp": "2025-08-14T17:14:00.334194",
"level": "info",
"event_type": "application_started",
"message": "Application initialization completed successfully",
"details": {}
},
{
"timestamp": "2025-08-14T17:14:00.401621",
"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-14T17:14:00.410620",
"level": "info",
"event_type": "csv_recording_started",
"message": "CSV recording started: 1 datasets activated",
"details": {
"activated_datasets": 1,
"total_datasets": 3
}
},
{
"timestamp": "2025-08-14T17:14:00.421480",
"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-14T17:14:00.435620",
"level": "error",
"event_type": "csv_cleanup_failed",
"message": "CSV cleanup failed: 'max_hours'",
"details": {}
},
{
"timestamp": "2025-08-14T17:25:34.393400",
"level": "info",
"event_type": "application_started",
"message": "Application initialization completed successfully",
"details": {}
},
{
"timestamp": "2025-08-14T17:25:34.443828",
"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-14T17:25:34.451828",
"level": "info",
"event_type": "csv_recording_started",
"message": "CSV recording started: 1 datasets activated",
"details": {
"activated_datasets": 1,
"total_datasets": 3
}
},
{
"timestamp": "2025-08-14T17:25:34.464832",
"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-14T17:25:34.477358",
"level": "error",
"event_type": "csv_cleanup_failed",
"message": "CSV cleanup failed: 'max_hours'",
"details": {}
},
{
"timestamp": "2025-08-14T17:31:05.141755",
"level": "info",
"event_type": "application_started",
"message": "Application initialization completed successfully",
"details": {}
},
{
"timestamp": "2025-08-14T17:31:05.207794",
"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-14T17:31:05.219935",
"level": "info",
"event_type": "csv_recording_started",
"message": "CSV recording started: 1 datasets activated",
"details": {
"activated_datasets": 1,
"total_datasets": 3
}
},
{
"timestamp": "2025-08-14T17:31:05.234342",
"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-14T17:31:05.240939",
"level": "error",
"event_type": "csv_cleanup_failed",
"message": "CSV cleanup failed: 'max_hours'",
"details": {}
},
{
"timestamp": "2025-08-14T17:32:09.019907",
"level": "info",
"event_type": "application_started",
"message": "Application initialization completed successfully",
"details": {}
},
{
"timestamp": "2025-08-14T17:32:09.087100",
"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-14T17:32:09.107982",
"level": "info",
"event_type": "csv_recording_started",
"message": "CSV recording started: 1 datasets activated",
"details": {
"activated_datasets": 1,
"total_datasets": 3
}
},
{
"timestamp": "2025-08-14T17:32:09.122999",
"level": "error",
"event_type": "csv_cleanup_failed",
"message": "CSV cleanup failed: 'max_hours'",
"details": {}
},
{
"timestamp": "2025-08-14T17:32:09.144483",
"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
}
}
],
"last_updated": "2025-08-14T16:52:36.475776",
"last_updated": "2025-08-14T17:32:09.144483",
"total_entries": 1000
}ted",
"details": {
"udp_host": "127.0.0.1",
"udp_port": 9870,
"datasets_available": 3
}
}
],
"last_updated": "2025-08-14T17:32:09.123999",
"total_entries": 1000
}

View File

@ -4,6 +4,7 @@
"dataset_id": "DAR",
"variables": [
{
"configType": "manual",
"area": "db",
"db": 1011,
"name": "UR29_Brix",
@ -12,6 +13,7 @@
"type": "real"
},
{
"configType": "manual",
"area": "db",
"db": 1011,
"name": "UR29_ma",
@ -23,7 +25,15 @@
},
{
"dataset_id": "Fast",
"variables": []
"variables": [
{
"configType": "symbol",
"area": "db",
"type": "real",
"streaming": false,
"symbol": "FTP302_Brix"
}
]
}
]
}

View File

@ -25,167 +25,215 @@
"removable": true
},
"items": {
"ui:order": [
"name",
"area",
"db",
"offset",
"bit",
"type",
"streaming"
],
"ui:layout": [
[
{
"name": "name",
"width": 4
"oneOf": [
{
"ui:title": "Manual Configuration",
"ui:description": "Configure PLC variable parameters manually",
"ui:order": [
"name",
"area",
"db",
"offset",
"bit",
"type",
"streaming"
],
"ui:layout": [
[
{
"name": "name",
"width": 4
},
{
"name": "area",
"width": 2
},
{
"name": "db",
"width": 2
},
{
"name": "offset",
"width": 2
},
{
"name": "type",
"width": 2
}
],
[
{
"name": "bit",
"width": 3
},
{
"name": "streaming",
"width": 9
}
]
],
"name": {
"ui:widget": "text",
"ui:placeholder": "Variable name",
"ui:help": "📝 Human-readable name for this variable"
},
{
"name": "area",
"width": 2
},
{
"name": "db",
"width": 2
},
{
"name": "offset",
"width": 2
},
{
"name": "type",
"width": 2
}
],
[
{
"name": "bit",
"width": 3
},
{
"name": "streaming",
"width": 9
}
]
],
"name": {
"ui:widget": "text",
"ui:placeholder": "Variable name",
"ui:help": "📝 Human-readable name for this variable"
},
"area": {
"ui:widget": "select",
"ui:help": "PLC memory area (DB=DataBlock, MW=MemoryWord, etc.)",
"ui:options": {
"enumOptions": [
{
"value": "db",
"label": "🗃️ DB (Data Block)"
},
{
"value": "mw",
"label": "📊 MW (Memory Word)"
},
{
"value": "m",
"label": "💾 M (Memory)"
},
{
"value": "pew",
"label": "📥 PEW (Process Input Word)"
},
{
"value": "pe",
"label": "📥 PE (Process Input)"
},
{
"value": "paw",
"label": "📤 PAW (Process Output Word)"
},
{
"value": "pa",
"label": "📤 PA (Process Output)"
},
{
"value": "e",
"label": "🔌 E (Input)"
},
{
"value": "a",
"label": "🔌 A (Output)"
},
{
"value": "mb",
"label": "💾 MB (Memory Byte)"
"area": {
"ui:widget": "select",
"ui:help": "PLC memory area (DB=DataBlock, MW=MemoryWord, etc.)",
"ui:options": {
"enumOptions": [
{
"value": "db",
"label": "🗃️ DB (Data Block)"
},
{
"value": "mw",
"label": "📊 MW (Memory Word)"
},
{
"value": "m",
"label": "💾 M (Memory)"
},
{
"value": "pew",
"label": "📥 PEW (Process Input Word)"
},
{
"value": "pe",
"label": "📥 PE (Process Input)"
},
{
"value": "paw",
"label": "📤 PAW (Process Output Word)"
},
{
"value": "pa",
"label": "📤 PA (Process Output)"
},
{
"value": "e",
"label": "🔌 E (Input)"
},
{
"value": "a",
"label": "🔌 A (Output)"
},
{
"value": "mb",
"label": "💾 MB (Memory Byte)"
}
]
}
]
}
},
"db": {
"ui:widget": "updown",
"ui:help": "Data Block number (required for DB area)",
"ui:placeholder": "1011"
},
"offset": {
"ui:widget": "updown",
"ui:help": "Byte offset within the memory area"
},
"bit": {
"ui:widget": "updown",
"ui:help": "Bit position (0-7) for bit-addressable areas"
},
"type": {
"ui:widget": "select",
"ui:help": "PLC data type",
"ui:options": {
"enumOptions": [
{
"value": "real",
"label": "🔢 REAL (32-bit float)"
},
{
"value": "int",
"label": "🔢 INT (16-bit signed)"
},
{
"value": "bool",
"label": "✅ BOOL (1-bit boolean)"
},
{
"value": "dint",
"label": "🔢 DINT (32-bit signed)"
},
{
"value": "word",
"label": "🔢 WORD (16-bit unsigned)"
},
{
"value": "byte",
"label": "🔢 BYTE (8-bit unsigned)"
},
{
"value": "uint",
"label": "🔢 UINT (16-bit unsigned)"
},
{
"value": "udint",
"label": "🔢 UDINT (32-bit unsigned)"
},
{
"value": "sint",
"label": "🔢 SINT (8-bit signed)"
},
{
"value": "usint",
"label": "🔢 USINT (8-bit unsigned)"
},
"db": {
"ui:widget": "updown",
"ui:help": "Data Block number (required for DB area)",
"ui:placeholder": "1011"
},
"offset": {
"ui:widget": "updown",
"ui:help": "Byte offset within the memory area"
},
"bit": {
"ui:widget": "updown",
"ui:help": "Bit position (0-7) for bit-addressable areas"
},
"type": {
"ui:widget": "select",
"ui:help": "PLC data type",
"ui:options": {
"enumOptions": [
{
"value": "real",
"label": "🔢 REAL (32-bit float)"
},
{
"value": "int",
"label": "🔢 INT (16-bit signed)"
},
{
"value": "bool",
"label": "✅ BOOL (1-bit boolean)"
},
{
"value": "dint",
"label": "🔢 DINT (32-bit signed)"
},
{
"value": "word",
"label": "🔢 WORD (16-bit unsigned)"
},
{
"value": "byte",
"label": "🔢 BYTE (8-bit unsigned)"
},
{
"value": "uint",
"label": "🔢 UINT (16-bit unsigned)"
},
{
"value": "udint",
"label": "🔢 UDINT (32-bit unsigned)"
},
{
"value": "sint",
"label": "🔢 SINT (8-bit signed)"
},
{
"value": "usint",
"label": "🔢 USINT (8-bit unsigned)"
}
]
}
]
},
"streaming": {
"ui:widget": "checkbox",
"ui:help": "📡 Enable real-time streaming to PlotJuggler for visualization"
}
},
{
"ui:title": "Symbol-based Configuration",
"ui:description": "Use a symbol from the loaded ASC file",
"ui:order": [
"name",
"symbol",
"streaming"
],
"ui:layout": [
[
{
"name": "name",
"width": 6
},
{
"name": "symbol",
"width": 6
}
],
[
{
"name": "streaming",
"width": 12
}
]
],
"name": {
"ui:widget": "text",
"ui:placeholder": "Variable name (auto-filled from symbol)",
"ui:help": "📝 Human-readable name for this variable",
"ui:readonly": true
},
"symbol": {
"ui:widget": "dataset-variable-symbol",
"ui:placeholder": "Select a PLC symbol...",
"ui:help": "🔍 Search and select a symbol from the loaded ASC file"
},
"streaming": {
"ui:widget": "checkbox",
"ui:help": "📡 Enable real-time streaming to PlotJuggler for visualization"
}
}
},
"streaming": {
"ui:widget": "checkbox",
"ui:help": "📡 Enable real-time streaming to PlotJuggler for visualization"
}
]
}
}
}

View File

@ -0,0 +1,195 @@
import React, { useState, useEffect } from 'react'
import {
Box,
VStack,
HStack,
Card,
CardBody,
CardHeader,
Heading,
Text,
Select,
Alert,
AlertIcon,
useColorModeValue,
Spinner
} from '@chakra-ui/react'
import Form from '@rjsf/chakra-ui'
import validator from '@rjsf/validator-ajv8'
import LayoutObjectFieldTemplate from './rjsf/LayoutObjectFieldTemplate.jsx'
import { allWidgets } from './widgets/AllWidgets.jsx'
/**
* DatasetVariablesRJSF - Maneja variables de dataset usando RJSF Type 3 pattern
*/
export default function DatasetVariablesRJSF({
datasets = {},
selectedDatasetId,
onSelectDataset,
onVariablesUpdate
}) {
const [schema, setSchema] = useState(null)
const [uiSchema, setUiSchema] = useState(null)
const [variables, setVariables] = useState({ variables: [] })
const [loading, setLoading] = useState(true)
const [saving, setSaving] = useState(false)
const [message, setMessage] = useState('')
const muted = useColorModeValue('gray.600', 'gray.300')
// Load schema and data
useEffect(() => {
loadSchemaAndData()
}, [])
const loadSchemaAndData = async () => {
setLoading(true)
try {
// Load schema
const schemaResp = await fetch('/api/config/schema/dataset-variables')
const schemaData = await schemaResp.json()
if (schemaData.success) {
setSchema(schemaData.schema)
setUiSchema(schemaData.ui_schema || {})
}
// Load data
const dataResp = await fetch('/api/config/dataset-variables')
const data = await dataResp.json()
if (data.success) {
setVariables(data.data || { variables: [] })
}
} catch (error) {
console.error('Error loading schema/data:', error)
setMessage(`Error loading: ${error.message}`)
} finally {
setLoading(false)
}
}
const saveVariables = async (formData) => {
setSaving(true)
try {
const response = await fetch('/api/config/dataset-variables', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData)
})
const result = await response.json()
if (result.success) {
setVariables(formData)
setMessage('Variables saved successfully')
setTimeout(() => setMessage(''), 3000)
if (onVariablesUpdate) {
onVariablesUpdate(formData)
}
} else {
throw new Error(result.error || 'Failed to save variables')
}
} catch (error) {
console.error('Error saving variables:', error)
setMessage(`Error saving: ${error.message}`)
} finally {
setSaving(false)
}
}
// Get available datasets for selector
const datasetOptions = Object.entries(datasets).map(([id, dataset]) => ({
value: id,
label: `${dataset.name || id} (${id})`
}))
if (loading) {
return (
<Card>
<CardBody>
<VStack spacing={4}>
<Spinner />
<Text>Loading dataset variables configuration...</Text>
</VStack>
</CardBody>
</Card>
)
}
if (!schema) {
return (
<Card>
<CardBody>
<Alert status="error">
<AlertIcon />
Failed to load schema for dataset variables
</Alert>
</CardBody>
</Card>
)
}
return (
<Card>
<CardHeader>
<VStack align="start" spacing={2}>
<Heading size="sm">🔧 Dataset Variables</Heading>
<Text fontSize="sm" color={muted}>
Configure PLC variables for each dataset - use symbols or manual configuration
</Text>
{datasetOptions.length > 0 && (
<HStack>
<Text fontSize="sm" color={muted}>Reference Dataset:</Text>
<Select
size="sm"
value={selectedDatasetId || ''}
onChange={(e) => onSelectDataset && onSelectDataset(e.target.value)}
placeholder="Select dataset for reference"
width="250px"
>
{datasetOptions.map(option => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</Select>
</HStack>
)}
</VStack>
</CardHeader>
<CardBody>
{message && (
<Alert status={message.includes('Error') ? 'error' : 'success'} mb={4}>
<AlertIcon />
{message}
</Alert>
)}
<Form
schema={schema}
uiSchema={uiSchema}
formData={variables}
validator={validator}
onChange={({ formData }) => setVariables(formData)}
onSubmit={({ formData }) => saveVariables(formData)}
templates={{ ObjectFieldTemplate: LayoutObjectFieldTemplate }}
widgets={allWidgets}
showErrorList={false}
disabled={saving}
>
<HStack mt={4}>
<button type="submit" disabled={saving}>
{saving ? 'Saving...' : 'Save Variables'}
</button>
</HStack>
</Form>
</CardBody>
</Card>
)
}

View File

@ -21,7 +21,7 @@ import { AddIcon, DeleteIcon, EditIcon } from '@chakra-ui/icons'
import Form from '@rjsf/chakra-ui'
import validator from '@rjsf/validator-ajv8'
import LayoutObjectFieldTemplate from './rjsf/LayoutObjectFieldTemplate.jsx'
import { widgets } from './rjsf/widgets.jsx'
import { allWidgets } from './widgets/AllWidgets.jsx'
/**
* FormTable - Muestra objetos como filas de formularios usando schemas RJSF
@ -152,7 +152,7 @@ export default function FormTable({
validator={validator}
onSubmit={({ formData }) => handleAdd(formData)}
templates={{ ObjectFieldTemplate: LayoutObjectFieldTemplate }}
widgets={widgets}
widgets={allWidgets}
showErrorList={false}
>
<HStack mt={3}>
@ -231,7 +231,7 @@ export default function FormTable({
onChange={editingKey === key ? ({ formData }) => setEditingFormData(formData) : () => { }}
onSubmit={editingKey === key ? ({ formData }) => handleEdit(key, formData) : undefined}
templates={{ ObjectFieldTemplate: LayoutObjectFieldTemplate }}
widgets={widgets}
widgets={allWidgets}
readonly={editingKey !== key}
showErrorList={false}
>

View File

@ -3,6 +3,7 @@ import { widgets } from '../rjsf/widgets'
import VariableSelectorWidget from '../rjsf/VariableSelectorWidget'
import FilePathWidget from './FilePathWidget'
import SymbolSelectorWidget from './SymbolSelectorWidget'
import DatasetVariableSymbolWidget from './DatasetVariableSymbolWidget'
// Comprehensive widget collection that merges all available widgets
// for full UI schema support with layouts
@ -35,6 +36,11 @@ export const allWidgets = {
'symbol-selector': SymbolSelectorWidget,
SymbolSelectorWidget: SymbolSelectorWidget,
// Dataset variable symbol widget with auto-fill
datasetVariableSymbol: DatasetVariableSymbolWidget,
'dataset-variable-symbol': DatasetVariableSymbolWidget,
DatasetVariableSymbolWidget: DatasetVariableSymbolWidget,
// PLC-specific widget aliases (if available)
plcArea: widgets.PlcAreaWidget,
plcDataType: widgets.PlcDataTypeWidget,

View File

@ -0,0 +1,100 @@
import React, { useState, useEffect } from 'react'
import {
Box,
VStack,
Text,
Badge,
useToast
} from '@chakra-ui/react'
import SymbolSelectorWidget from './SymbolSelectorWidget'
const DatasetVariableSymbolWidget = ({ value, onChange, label, disabled, readonly, required, placeholder, formContext }) => {
const [selectedSymbol, setSelectedSymbol] = useState(null)
const toast = useToast()
// Load symbol details when value changes
useEffect(() => {
if (value && !selectedSymbol) {
loadSymbolDetails(value)
}
}, [value])
const loadSymbolDetails = async (symbolName) => {
try {
const response = await fetch('/api/symbols')
const data = await response.json()
if (data.success && data.symbols) {
const symbol = data.symbols.find(s => s.name === symbolName)
if (symbol) {
setSelectedSymbol(symbol)
}
}
} catch (error) {
console.error('Error loading symbol details:', error)
}
}
const handleSymbolSelect = (symbolName) => {
// Update the symbol field
onChange(symbolName)
// Show success message
toast({
title: 'Symbol Selected',
description: `Selected: ${symbolName}`,
status: 'success',
duration: 2000,
isClosable: true,
})
}
const symbolOptions = {
onSymbolSelect: (symbol) => {
setSelectedSymbol(symbol)
handleSymbolSelect(symbol.name)
}
}
return (
<Box>
<SymbolSelectorWidget
value={value}
onChange={handleSymbolSelect}
label={label}
disabled={disabled}
readonly={readonly}
required={required}
placeholder={placeholder}
formContext={formContext}
options={symbolOptions}
/>
{selectedSymbol && (
<Box mt={2} p={2} border="1px" borderColor="green.200" borderRadius="md" bg="green.50">
<Text fontSize="xs" color="green.700" fontWeight="medium">
Symbol Information:
</Text>
<VStack align="start" spacing={1} mt={1}>
<Text fontSize="xs" color="green.600">
📝 Name: {selectedSymbol.description || selectedSymbol.name}
</Text>
<Text fontSize="xs" color="green.600" fontFamily="mono">
📍 Address: {selectedSymbol.plc_address}
</Text>
<Text fontSize="xs" color="green.600">
🔧 Area: {selectedSymbol.area?.toUpperCase()}, Offset: {selectedSymbol.offset}
{selectedSymbol.db && `, DB: ${selectedSymbol.db}`}
{selectedSymbol.bit !== null && selectedSymbol.bit !== undefined && `, Bit: ${selectedSymbol.bit}`}
</Text>
<Badge colorScheme="green" fontSize="xs">
{selectedSymbol.data_type}
</Badge>
</VStack>
</Box>
)}
</Box>
)
}
export default DatasetVariableSymbolWidget

View File

@ -26,7 +26,7 @@ import {
} from '@chakra-ui/react'
import { FiSearch, FiX, FiList, FiInfo } from 'react-icons/fi'
const SymbolSelectorWidget = ({ value, onChange, label, disabled, readonly, required, placeholder }) => {
const SymbolSelectorWidget = ({ value, onChange, label, disabled, readonly, required, placeholder, formContext, options }) => {
const [symbols, setSymbols] = useState([])
const [searchQuery, setSearchQuery] = useState('')
const [isLoading, setIsLoading] = useState(false)
@ -92,6 +92,12 @@ const SymbolSelectorWidget = ({ value, onChange, label, disabled, readonly, requ
const handleSymbolSelect = (symbol) => {
setSelectedSymbol(symbol)
onChange(symbol.name)
// If we have formContext and the widget options include callbacks for auto-fill
if (formContext && options && options.onSymbolSelect) {
options.onSymbolSelect(symbol)
}
onClose()
toast({

View File

@ -783,25 +783,60 @@ function DatasetManager() {
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 }
configType: {
type: "string",
title: "Configuration Type",
enum: ["manual", "symbol"],
default: "manual"
}
},
required: ["name", "area", "offset", "type"]
allOf: [
{
if: { properties: { configType: { const: "manual" } } },
then: {
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"]
}
},
{
if: { properties: { configType: { const: "symbol" } } },
then: {
properties: {
name: {
type: "string",
title: "Variable Name",
description: "Auto-filled from symbol",
readOnly: true
},
symbol: {
type: "string",
title: "PLC Symbol",
description: "Select a symbol from loaded ASC file"
},
streaming: { type: "boolean", title: "Stream to UDP", default: false }
},
required: ["symbol"]
}
}
]
}
}
}
@ -810,14 +845,49 @@ function DatasetManager() {
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 }
]]
"ui:order": ["configType", "name", "symbol", "area", "db", "offset", "bit", "type", "streaming"],
"ui:layout": [
[
{ "name": "configType", "width": 3 }
],
[
{ "name": "name", "width": 4 },
{ "name": "symbol", "width": 8 }
],
[
{ "name": "area", "width": 2 },
{ "name": "db", "width": 2 },
{ "name": "offset", "width": 2 },
{ "name": "bit", "width": 2 },
{ "name": "type", "width": 2 },
{ "name": "streaming", "width": 2 }
]
],
"configType": {
"ui:widget": "select",
"ui:help": "Choose between manual configuration or symbol-based setup",
"ui:enumNames": ["Manual Configuration", "Symbol-based Configuration"]
},
"symbol": {
"ui:widget": "symbol-selector",
"ui:placeholder": "Select a PLC symbol...",
"ui:help": "🔍 Search and select a symbol from the loaded ASC file"
},
"name": {
"ui:help": "Human-readable name for this variable"
},
"area": {
"ui:widget": "select",
"ui:help": "PLC memory area"
},
"type": {
"ui:widget": "select",
"ui:help": "PLC data type"
},
"streaming": {
"ui:widget": "checkbox",
"ui:help": "Enable UDP streaming to PlotJuggler"
}
}
}
}

View File

@ -3,11 +3,11 @@
"should_connect": true,
"should_stream": true,
"active_datasets": [
"Fast",
"Test",
"DAR",
"Fast"
"DAR"
]
},
"auto_recovery_enabled": true,
"last_update": "2025-08-14T16:52:19.757206"
"last_update": "2025-08-14T17:32:09.169720"
}