Enhance variable management and plotting features. Introduced VariableContext for managing variable updates, updated dataset variables and application events, improved plot variable schema, and refined UI components for better usability and performance.
This commit is contained in:
parent
09263d39f8
commit
d0d675d804
|
@ -1,165 +1,5 @@
|
|||
{
|
||||
"events": [
|
||||
{
|
||||
"timestamp": "2025-07-17T18:00:01.179115",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "dar",
|
||||
"variables_count": 4,
|
||||
"streaming_count": 4,
|
||||
"prefix": "dar"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T18:01:12.766609",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "dar",
|
||||
"variables_count": 4,
|
||||
"streaming_count": 4,
|
||||
"prefix": "dar"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T18:01:12.769824",
|
||||
"level": "info",
|
||||
"event_type": "streaming_started",
|
||||
"message": "Multi-dataset streaming started: 1 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 1,
|
||||
"total_datasets": 2,
|
||||
"udp_host": "127.0.0.1",
|
||||
"udp_port": 9870
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T18:01:26.427183",
|
||||
"level": "info",
|
||||
"event_type": "Application started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T18:01:26.451766",
|
||||
"level": "info",
|
||||
"event_type": "plc_connection",
|
||||
"message": "Successfully connected to PLC 10.1.33.11",
|
||||
"details": {
|
||||
"ip": "10.1.33.11",
|
||||
"rack": 0,
|
||||
"slot": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T18:01:26.456954",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "dar",
|
||||
"variables_count": 4,
|
||||
"streaming_count": 4,
|
||||
"prefix": "dar"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T18:01:30.909879",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "dar",
|
||||
"variables_count": 4,
|
||||
"streaming_count": 4,
|
||||
"prefix": "dar"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T18:01:30.911944",
|
||||
"level": "info",
|
||||
"event_type": "streaming_started",
|
||||
"message": "Multi-dataset streaming started: 1 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 1,
|
||||
"total_datasets": 2,
|
||||
"udp_host": "127.0.0.1",
|
||||
"udp_port": 9870
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T18:20:09.887378",
|
||||
"level": "info",
|
||||
"event_type": "Application started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T18:20:09.913286",
|
||||
"level": "info",
|
||||
"event_type": "plc_connection",
|
||||
"message": "Successfully connected to PLC 10.1.33.11",
|
||||
"details": {
|
||||
"ip": "10.1.33.11",
|
||||
"rack": 0,
|
||||
"slot": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-17T18:20:09.917270",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "dar",
|
||||
"variables_count": 4,
|
||||
"streaming_count": 4,
|
||||
"prefix": "dar"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-18T09:39:20.220724",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "dar",
|
||||
"variables_count": 4,
|
||||
"streaming_count": 3,
|
||||
"prefix": "dar"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-18T09:39:20.226708",
|
||||
"level": "info",
|
||||
"event_type": "streaming_started",
|
||||
"message": "Multi-dataset streaming started: 1 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 1,
|
||||
"total_datasets": 2,
|
||||
"udp_host": "127.0.0.1",
|
||||
"udp_port": 9870
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-18T09:40:58.642342",
|
||||
"level": "info",
|
||||
"event_type": "dataset_deactivated",
|
||||
"message": "Dataset deactivated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "dar"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-18T09:40:58.644338",
|
||||
"level": "info",
|
||||
"event_type": "streaming_stopped",
|
||||
"message": "Multi-dataset streaming stopped: 1 datasets deactivated",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-07-18T09:40:58.647328",
|
||||
"level": "info",
|
||||
|
@ -10414,8 +10254,155 @@
|
|||
"trigger_variable": null,
|
||||
"auto_started": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-14T11:42:26.264053",
|
||||
"level": "info",
|
||||
"event_type": "application_started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-14T11:42:26.345728",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"variables_count": 3,
|
||||
"streaming_count": 3,
|
||||
"prefix": "gateway_phoenix"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-14T11:42:26.362513",
|
||||
"level": "info",
|
||||
"event_type": "csv_recording_started",
|
||||
"message": "CSV recording started: 1 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 1,
|
||||
"total_datasets": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-14T11:42:26.424426",
|
||||
"level": "error",
|
||||
"event_type": "csv_cleanup_failed",
|
||||
"message": "CSV cleanup failed: 'max_hours'",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-14T11:49:00.624419",
|
||||
"level": "info",
|
||||
"event_type": "application_started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-14T11:49:00.691022",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "DAR",
|
||||
"variables_count": 3,
|
||||
"streaming_count": 3,
|
||||
"prefix": "gateway_phoenix"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-14T11:49:00.703362",
|
||||
"level": "info",
|
||||
"event_type": "csv_recording_started",
|
||||
"message": "CSV recording started: 1 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 1,
|
||||
"total_datasets": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-14T11:49:00.738130",
|
||||
"level": "error",
|
||||
"event_type": "csv_cleanup_failed",
|
||||
"message": "CSV cleanup failed: 'max_hours'",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-14T12:00:00.237052",
|
||||
"level": "error",
|
||||
"event_type": "csv_cleanup_failed",
|
||||
"message": "CSV cleanup failed: 'max_hours'",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-14T12:03:29.892080",
|
||||
"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": 25,
|
||||
"trigger_variable": null,
|
||||
"auto_started": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-14T12:05:58.231793",
|
||||
"level": "info",
|
||||
"event_type": "application_started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-14T12:05:58.282781",
|
||||
"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-14T12:05:58.294856",
|
||||
"level": "info",
|
||||
"event_type": "csv_recording_started",
|
||||
"message": "CSV recording started: 1 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 1,
|
||||
"total_datasets": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-14T12:05:58.314287",
|
||||
"level": "error",
|
||||
"event_type": "csv_cleanup_failed",
|
||||
"message": "CSV cleanup failed: 'max_hours'",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-14T12:06:22.595559",
|
||||
"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": 25,
|
||||
"trigger_variable": null,
|
||||
"auto_started": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"last_updated": "2025-08-14T11:34:45.734619",
|
||||
"last_updated": "2025-08-14T12:06:22.595559",
|
||||
"total_entries": 1000
|
||||
}
|
|
@ -4,28 +4,20 @@
|
|||
"dataset_id": "DAR",
|
||||
"variables": [
|
||||
{
|
||||
"area": "db",
|
||||
"db": 1011,
|
||||
"name": "UR29_Brix",
|
||||
"offset": 1322,
|
||||
"streaming": true,
|
||||
"type": "real"
|
||||
},
|
||||
{
|
||||
"area": "db",
|
||||
"db": 1011,
|
||||
"offset": 1322,
|
||||
"type": "real",
|
||||
"streaming": true
|
||||
},
|
||||
{
|
||||
"name": "UR29_ma",
|
||||
"offset": 1296,
|
||||
"streaming": true,
|
||||
"type": "real"
|
||||
},
|
||||
{
|
||||
"area": "db",
|
||||
"db": 1011,
|
||||
"name": "fUR29_Brix",
|
||||
"offset": 1322,
|
||||
"streaming": true,
|
||||
"type": "real"
|
||||
"offset": 1296,
|
||||
"type": "real",
|
||||
"streaming": true
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -28,7 +28,12 @@
|
|||
"variable_name": {
|
||||
"type": "string",
|
||||
"title": "Variable Name",
|
||||
"description": "Name of the variable to plot (must exist in dataset variables)"
|
||||
"description": "Name of the variable to plot (selected from dataset variables using search widget)"
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"title": "Display Label",
|
||||
"description": "Label shown in the plot legend"
|
||||
},
|
||||
"color": {
|
||||
"type": "string",
|
||||
|
@ -37,6 +42,21 @@
|
|||
"pattern": "^#[0-9A-Fa-f]{6}$",
|
||||
"default": "#3498db"
|
||||
},
|
||||
"line_width": {
|
||||
"type": "number",
|
||||
"title": "Line Width",
|
||||
"description": "Width of the line in the plot",
|
||||
"default": 2,
|
||||
"minimum": 1,
|
||||
"maximum": 10
|
||||
},
|
||||
"y_axis": {
|
||||
"type": "string",
|
||||
"title": "Y-Axis",
|
||||
"description": "Which Y-axis to use for this variable",
|
||||
"enum": ["left", "right"],
|
||||
"default": "left"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"title": "Enable Plotting",
|
||||
|
@ -46,7 +66,7 @@
|
|||
},
|
||||
"required": [
|
||||
"variable_name",
|
||||
"color"
|
||||
"label"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,29 +27,49 @@
|
|||
"items": {
|
||||
"ui:order": [
|
||||
"variable_name",
|
||||
"label",
|
||||
"color",
|
||||
"line_width",
|
||||
"y_axis",
|
||||
"enabled"
|
||||
],
|
||||
"ui:layout": [
|
||||
[
|
||||
{
|
||||
"name": "variable_name",
|
||||
"width": 6
|
||||
"width": 4
|
||||
},
|
||||
{
|
||||
"name": "label",
|
||||
"width": 2
|
||||
},
|
||||
{
|
||||
"name": "color",
|
||||
"width": 3
|
||||
"width": 2
|
||||
},
|
||||
{
|
||||
"name": "line_width",
|
||||
"width": 2
|
||||
},
|
||||
{
|
||||
"name": "y_axis",
|
||||
"width": 1
|
||||
},
|
||||
{
|
||||
"name": "enabled",
|
||||
"width": 3
|
||||
"width": 1
|
||||
}
|
||||
]
|
||||
],
|
||||
"variable_name": {
|
||||
"ui:widget": "variableSelector",
|
||||
"ui:placeholder": "Search and select variable from datasets...",
|
||||
"ui:help": "🔍 Search and select a variable from the configured datasets (includes live values and metadata)"
|
||||
},
|
||||
"label": {
|
||||
"ui:widget": "text",
|
||||
"ui:placeholder": "UR29_Brix",
|
||||
"ui:help": "<22> Name of the variable to plot (must exist in dataset variables)"
|
||||
"ui:placeholder": "Chart legend label...",
|
||||
"ui:help": "📊 Label shown in the plot legend for this variable"
|
||||
},
|
||||
"color": {
|
||||
"ui:widget": "color",
|
||||
|
@ -72,6 +92,14 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"line_width": {
|
||||
"ui:widget": "updown",
|
||||
"ui:help": "📏 Width of the line in the plot (1-10 pixels)"
|
||||
},
|
||||
"y_axis": {
|
||||
"ui:widget": "select",
|
||||
"ui:help": "📊 Which Y-axis to use for this variable (left or right)"
|
||||
},
|
||||
"enabled": {
|
||||
"ui:widget": "checkbox",
|
||||
"ui:help": "📊 Enable this variable to be displayed in the real-time plot"
|
||||
|
|
|
@ -36,10 +36,12 @@ 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'
|
||||
|
||||
// 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)
|
||||
|
@ -238,6 +240,8 @@ export default function PlotManager() {
|
|||
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',
|
||||
|
@ -411,15 +415,10 @@ export default function PlotManager() {
|
|||
type: "object",
|
||||
title: "Plot Variable",
|
||||
properties: {
|
||||
dataset_id: {
|
||||
type: "string",
|
||||
title: "Dataset Source",
|
||||
description: "Which dataset contains this variable"
|
||||
},
|
||||
variable_name: {
|
||||
type: "string",
|
||||
title: "Variable Name",
|
||||
description: "Name of the variable to plot"
|
||||
description: "Select variable from datasets with search and metadata"
|
||||
},
|
||||
label: {
|
||||
type: "string",
|
||||
|
@ -445,7 +444,7 @@ export default function PlotManager() {
|
|||
default: "left"
|
||||
}
|
||||
},
|
||||
required: ["dataset_id", "variable_name", "label"]
|
||||
required: ["variable_name", "label"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -455,13 +454,26 @@ export default function PlotManager() {
|
|||
variables: {
|
||||
items: {
|
||||
"ui:layout": [[
|
||||
{ "name": "dataset_id", "width": 2 },
|
||||
{ "name": "variable_name", "width": 3 },
|
||||
{ "name": "variable_name", "width": 4 },
|
||||
{ "name": "label", "width": 2 },
|
||||
{ "name": "color", "width": 2 },
|
||||
{ "name": "line_width", "width": 1 },
|
||||
{ "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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,27 @@
|
|||
import React, { useState, useEffect, useMemo, useRef } from 'react'
|
||||
import React, { useState, useEffect, useMemo, useRef, useCallback } from 'react'
|
||||
import {
|
||||
FormControl, FormLabel, FormHelperText, Select, VStack, HStack,
|
||||
Text, Badge, Box, Icon, Input, useColorModeValue, Spinner
|
||||
Text, Badge, Box, Icon, Input, useColorModeValue, Spinner, IconButton, Tooltip
|
||||
} from '@chakra-ui/react'
|
||||
import { SearchIcon } from '@chakra-ui/icons'
|
||||
import { SearchIcon, RepeatIcon } from '@chakra-ui/icons'
|
||||
import { readConfig } from '../../services/api.js'
|
||||
import { useVariableContext } from '../../contexts/VariableContext.jsx'
|
||||
|
||||
// Widget for selecting existing dataset variables with filtering and search
|
||||
export function VariableSelectorWidget(props) {
|
||||
const { id, value, required, disabled, readonly, label, onChange, onBlur, onFocus, rawErrors = [] } = props
|
||||
const { id, value, required, disabled, readonly, label, onChange, onBlur, onFocus, rawErrors = [], options = {} } = props
|
||||
|
||||
// Extract refresh trigger from options if provided
|
||||
const refreshTrigger = options?.refreshTrigger || 0
|
||||
|
||||
// Use variable context if available, fallback to local state
|
||||
let contextTrigger = 0
|
||||
try {
|
||||
const variableContext = useVariableContext()
|
||||
contextTrigger = variableContext?.variableRefreshTrigger || 0
|
||||
} catch {
|
||||
// Context not available, use local trigger only
|
||||
}
|
||||
|
||||
const [datasetVariables, setDatasetVariables] = useState({})
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
@ -16,6 +29,7 @@ export function VariableSelectorWidget(props) {
|
|||
const [selectedDataset, setSelectedDataset] = useState('all')
|
||||
const [liveValue, setLiveValue] = useState(undefined)
|
||||
const [liveStatus, setLiveStatus] = useState('idle')
|
||||
const [refreshing, setRefreshing] = useState(false)
|
||||
const esRef = useRef(null)
|
||||
|
||||
const borderColor = useColorModeValue('gray.300', 'gray.600')
|
||||
|
@ -24,24 +38,67 @@ export function VariableSelectorWidget(props) {
|
|||
const selectedVarBgColor = useColorModeValue('blue.50', 'blue.900')
|
||||
const selectedVarBorderColor = useColorModeValue('blue.200', 'blue.700')
|
||||
|
||||
// Load dataset variables on mount
|
||||
useEffect(() => {
|
||||
const loadDatasetVariables = async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
const response = await readConfig('dataset-variables')
|
||||
setDatasetVariables(response.data?.dataset_variables || {})
|
||||
} catch (error) {
|
||||
console.error('Error loading dataset variables:', error)
|
||||
setDatasetVariables({})
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
// Load dataset variables function (extracted for reuse)
|
||||
const loadDatasetVariables = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
const response = await readConfig('dataset-variables')
|
||||
// Handle the array-based structure: { variables: [{dataset_id, variables: [...]}] }
|
||||
const datasetVariablesArray = response?.variables || []
|
||||
|
||||
// Convert array format to object format for easier processing
|
||||
const datasetVariablesObj = {}
|
||||
datasetVariablesArray.forEach(item => {
|
||||
if (item.dataset_id && item.variables) {
|
||||
datasetVariablesObj[item.dataset_id] = {
|
||||
variables: {}
|
||||
}
|
||||
// Convert variables array to object with variable name as key
|
||||
item.variables.forEach(variable => {
|
||||
if (variable.name) {
|
||||
datasetVariablesObj[item.dataset_id].variables[variable.name] = variable
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
setDatasetVariables(datasetVariablesObj)
|
||||
} catch (error) {
|
||||
console.error('Error loading dataset variables:', error)
|
||||
setDatasetVariables({})
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
loadDatasetVariables()
|
||||
}, [])
|
||||
|
||||
// Manual refresh function
|
||||
const handleRefresh = useCallback(async () => {
|
||||
setRefreshing(true)
|
||||
await loadDatasetVariables()
|
||||
setRefreshing(false)
|
||||
}, [loadDatasetVariables])
|
||||
|
||||
// Load dataset variables on mount and when refresh trigger changes
|
||||
useEffect(() => {
|
||||
loadDatasetVariables()
|
||||
}, [loadDatasetVariables, refreshTrigger, contextTrigger])
|
||||
|
||||
// Auto-refresh when component gains focus (optional behavior)
|
||||
const handleFocusWithRefresh = useCallback((event) => {
|
||||
// Call original onFocus if provided
|
||||
if (onFocus) {
|
||||
onFocus(id, event.target.value)
|
||||
}
|
||||
|
||||
// Auto-refresh data on focus (debounced to avoid excessive calls)
|
||||
const now = Date.now()
|
||||
const lastRefresh = window._lastVariableRefresh || 0
|
||||
if (now - lastRefresh > 30000) { // Refresh at most once every 30 seconds
|
||||
window._lastVariableRefresh = now
|
||||
handleRefresh()
|
||||
}
|
||||
}, [onFocus, id, handleRefresh])
|
||||
|
||||
// Create flattened list of all variables with their metadata
|
||||
const allVariables = useMemo(() => {
|
||||
const variables = []
|
||||
|
@ -190,7 +247,7 @@ export function VariableSelectorWidget(props) {
|
|||
{label && <FormLabel htmlFor={id}>{label}</FormLabel>}
|
||||
|
||||
<VStack spacing={3} align="stretch">
|
||||
{/* Search and Dataset Filter */}
|
||||
{/* Search and Dataset Filter with Refresh */}
|
||||
<HStack spacing={2}>
|
||||
<Box position="relative" flex="1">
|
||||
<Input
|
||||
|
@ -226,6 +283,19 @@ export function VariableSelectorWidget(props) {
|
|||
</option>
|
||||
))}
|
||||
</Select>
|
||||
<Tooltip label="Refresh variables list" placement="top">
|
||||
<IconButton
|
||||
icon={<RepeatIcon />}
|
||||
onClick={handleRefresh}
|
||||
isLoading={refreshing}
|
||||
loadingText="Refreshing..."
|
||||
size="md"
|
||||
variant="outline"
|
||||
colorScheme="blue"
|
||||
aria-label="Refresh variables"
|
||||
isDisabled={loading}
|
||||
/>
|
||||
</Tooltip>
|
||||
</HStack>
|
||||
|
||||
{/* Variable Selection */}
|
||||
|
@ -234,10 +304,11 @@ export function VariableSelectorWidget(props) {
|
|||
value={value || ''}
|
||||
onChange={(e) => onChange(e.target.value === '' ? undefined : e.target.value)}
|
||||
onBlur={onBlur && ((e) => onBlur(id, e.target.value))}
|
||||
onFocus={onFocus && ((e) => onFocus(id, e.target.value))}
|
||||
onFocus={handleFocusWithRefresh}
|
||||
borderColor={borderColor}
|
||||
_focus={{ borderColor: focusBorderColor }}
|
||||
bg={bgColor}
|
||||
isDisabled={disabled || readonly}
|
||||
>
|
||||
<option value="">Select a variable...</option>
|
||||
{filteredVariables.map((variable, index) => (
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { customWidgets } from './CustomWidgets'
|
||||
import { widgets } from '../rjsf/widgets'
|
||||
import VariableSelectorWidget from '../rjsf/VariableSelectorWidget'
|
||||
|
||||
// Comprehensive widget collection that merges all available widgets
|
||||
// for full UI schema support with layouts
|
||||
|
@ -17,9 +18,10 @@ export const allWidgets = {
|
|||
select: widgets.SelectWidget,
|
||||
checkbox: widgets.CheckboxWidget,
|
||||
|
||||
// Variable selector aliases
|
||||
variableSelector: customWidgets.VariableSelectorWidget,
|
||||
'variable-selector': customWidgets.VariableSelectorWidget,
|
||||
// Variable selector aliases - use the advanced version with search and metadata
|
||||
variableSelector: VariableSelectorWidget,
|
||||
'variable-selector': VariableSelectorWidget,
|
||||
VariableSelectorWidget: VariableSelectorWidget,
|
||||
|
||||
// PLC-specific widget aliases (if available)
|
||||
plcArea: widgets.PlcAreaWidget,
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
import React, { createContext, useContext, useState, useCallback } from 'react'
|
||||
|
||||
// Context for managing variable data updates across components
|
||||
const VariableContext = createContext()
|
||||
|
||||
export function VariableProvider({ children }) {
|
||||
const [variableRefreshTrigger, setVariableRefreshTrigger] = useState(0)
|
||||
|
||||
// Function to trigger refresh of all variable selectors
|
||||
const triggerVariableRefresh = useCallback(() => {
|
||||
setVariableRefreshTrigger(prev => prev + 1)
|
||||
}, [])
|
||||
|
||||
const value = {
|
||||
variableRefreshTrigger,
|
||||
triggerVariableRefresh
|
||||
}
|
||||
|
||||
return (
|
||||
<VariableContext.Provider value={value}>
|
||||
{children}
|
||||
</VariableContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useVariableContext() {
|
||||
const context = useContext(VariableContext)
|
||||
if (context === undefined) {
|
||||
throw new Error('useVariableContext must be used within a VariableProvider')
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
export default VariableContext
|
|
@ -44,6 +44,7 @@ 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
|
||||
|
@ -312,6 +313,7 @@ function ConfigurationPanel({ schemaData, formData, onFormChange, onSave, saving
|
|||
|
||||
// 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)
|
||||
|
@ -379,6 +381,8 @@ function DatasetManager() {
|
|||
duration: 2000
|
||||
})
|
||||
setVariablesConfig(formData)
|
||||
// Trigger refresh of all variable selector widgets
|
||||
triggerVariableRefresh()
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: '❌ Failed to save variables',
|
||||
|
@ -599,7 +603,10 @@ function DatasetManager() {
|
|||
templates={{ ObjectFieldTemplate: LayoutObjectFieldTemplate }}
|
||||
onSubmit={({ formData }) => {
|
||||
updateSelectedDatasetVariables(formData)
|
||||
saveVariables(variablesConfig)
|
||||
saveVariables(variablesConfig).then(() => {
|
||||
// Additional trigger after successful save
|
||||
triggerVariableRefresh()
|
||||
})
|
||||
}}
|
||||
onChange={({ formData }) => updateSelectedDatasetVariables(formData)}
|
||||
>
|
||||
|
@ -707,8 +714,17 @@ function EventsDisplay({ events, loading, onRefresh }) {
|
|||
)
|
||||
}
|
||||
|
||||
// Main Dashboard Component - PLC S7-31x Streamer & Logger
|
||||
// Main Dashboard Component - PLC S7-31x Streamer & Logger
|
||||
export default function Dashboard() {
|
||||
return (
|
||||
<VariableProvider>
|
||||
<DashboardContent />
|
||||
</VariableProvider>
|
||||
)
|
||||
}
|
||||
|
||||
// Dashboard Content Component (separated to use context)
|
||||
function DashboardContent() {
|
||||
const [status, setStatus] = useState(null)
|
||||
const [statusLoading, setStatusLoading] = useState(true)
|
||||
const [statusError, setStatusError] = useState('')
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
"should_connect": true,
|
||||
"should_stream": false,
|
||||
"active_datasets": [
|
||||
"Test",
|
||||
"Fast",
|
||||
"DAR",
|
||||
"Test"
|
||||
"DAR"
|
||||
]
|
||||
},
|
||||
"auto_recovery_enabled": true,
|
||||
"last_update": "2025-08-14T11:34:34.761474"
|
||||
"last_update": "2025-08-14T12:05:58.306284"
|
||||
}
|
Loading…
Reference in New Issue