Actualización de application_events.json para incluir nuevos eventos de inicio de aplicación y ajustes en las fechas de última actualización. Se eliminaron archivos obsoletos relacionados con la integración de Chart.js y se reorganizaron las rutas de configuración en el código. Se implementaron mejoras en la gestión de esquemas y se optimizó la carga de recursos estáticos, incluyendo la favicon y logos en la interfaz. Además, se realizaron ajustes en el manejo de errores y se mejoró la estructura de directorios para una mejor organización del proyecto.
This commit is contained in:
parent
593487e52f
commit
0c11ee3ae2
|
@ -1,3 +1,19 @@
|
|||
2025-08-11 - Favicon y logos
|
||||
|
||||
- Resumen petición usuario: Cambiar el logo por `record.png` y que también se vea en la pestaña del navegador (favicon).
|
||||
|
||||
- Cambios clave:
|
||||
- `frontend/index.html`: `<link rel="icon" type="image/png" href="/static/icons/record.png?v=2" />` y `shortcut icon` para forzar recarga.
|
||||
- `main.py`: Ruta `/favicon.ico` que sirve `static/icons/record.png` para cubrir la petición automática del navegador.
|
||||
- `frontend/src/App.jsx`: Reemplazo de logos visuales por `/static/icons/record.png` en header y navbar.
|
||||
|
||||
- Decisiones/Notas:
|
||||
- Estándar adoptado para estáticos en React:
|
||||
- `frontend/public/`: archivos públicos sin import (favicon, robots.txt, imágenes públicas). Servidos desde la raíz: `/favicon.ico`, `/record.png`.
|
||||
- `frontend/src/assets/`: assets usados por componentes React, importados con `import img from '...';` para que Vite haga hashing y optimización.
|
||||
- Para favicon no usar archivos bajo `frontend/src/…`. Debe estar en `frontend/public` y enlazarse como `/favicon.ico`.
|
||||
- Se añadió `?v=2` para evitar caché del navegador.
|
||||
|
||||
# Project Evolution Memory
|
||||
|
||||
## PLC S7-315 Streamer & Logger
|
||||
|
|
|
@ -9238,8 +9238,22 @@
|
|||
"slot": 2,
|
||||
"error": "b' ISO : An error occurred during recv TCP : Connection timed out'"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-11T15:17:54.090498",
|
||||
"level": "info",
|
||||
"event_type": "application_started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-11T16:21:42.173167",
|
||||
"level": "info",
|
||||
"event_type": "application_started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
}
|
||||
],
|
||||
"last_updated": "2025-08-11T13:53:59.849319",
|
||||
"total_entries": 859
|
||||
"last_updated": "2025-08-11T16:21:42.173167",
|
||||
"total_entries": 861
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"plc_config": {
|
||||
"ip": "10.1.33.12",
|
||||
"rack": 0,
|
||||
"slot": 2
|
||||
},
|
||||
"udp_config": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 9870
|
||||
},
|
||||
"sampling_interval": 0.1,
|
||||
"csv_config": {
|
||||
"records_directory": "records",
|
||||
"rotation_enabled": true,
|
||||
"max_size_mb": 1000,
|
||||
"max_days": 30,
|
||||
"max_hours": null,
|
||||
"cleanup_interval_hours": 24,
|
||||
"last_cleanup": "2025-08-09T22:43:54.224975"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"datasets": {
|
||||
"DAR": {
|
||||
"name": "DAR",
|
||||
"prefix": "gateway_phoenix",
|
||||
"variables": {
|
||||
"UR29_Brix": {
|
||||
"area": "db",
|
||||
"offset": 1322,
|
||||
"type": "real",
|
||||
"streaming": true,
|
||||
"db": 1011
|
||||
},
|
||||
"UR29_ma": {
|
||||
"area": "db",
|
||||
"offset": 1296,
|
||||
"type": "real",
|
||||
"streaming": true,
|
||||
"db": 1011
|
||||
},
|
||||
"fUR29_Brix": {
|
||||
"area": "db",
|
||||
"offset": 1322,
|
||||
"type": "real",
|
||||
"streaming": false,
|
||||
"db": 1011
|
||||
}
|
||||
},
|
||||
"streaming_variables": [
|
||||
"UR29_Brix",
|
||||
"UR29_ma"
|
||||
],
|
||||
"sampling_interval": 1.0,
|
||||
"enabled": true,
|
||||
"created": "2025-08-08T15:47:18.566053"
|
||||
},
|
||||
"Fast": {
|
||||
"name": "Fast",
|
||||
"prefix": "fast",
|
||||
"variables": {
|
||||
"fUR29_Brix": {
|
||||
"area": "db",
|
||||
"offset": 1322,
|
||||
"type": "real",
|
||||
"streaming": false,
|
||||
"db": 1011
|
||||
},
|
||||
"fUR29_ma": {
|
||||
"area": "db",
|
||||
"offset": 1296,
|
||||
"type": "real",
|
||||
"streaming": false,
|
||||
"db": 1011
|
||||
}
|
||||
},
|
||||
"streaming_variables": [],
|
||||
"sampling_interval": 0.1,
|
||||
"enabled": true,
|
||||
"created": "2025-08-09T02:06:26.840011"
|
||||
}
|
||||
},
|
||||
"active_datasets": [
|
||||
"Fast",
|
||||
"DAR"
|
||||
],
|
||||
"current_dataset_id": "Fast",
|
||||
"version": "1.0",
|
||||
"last_update": "2025-08-10T01:45:12.551768"
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"plots": {
|
||||
"plot_1": {
|
||||
"name": "UR29",
|
||||
"variables": [
|
||||
"UR29_Brix",
|
||||
"UR29_ma",
|
||||
"fUR29_Brix",
|
||||
"fUR29_ma"
|
||||
],
|
||||
"time_window": 75,
|
||||
"y_min": null,
|
||||
"y_max": null,
|
||||
"trigger_variable": null,
|
||||
"trigger_enabled": false,
|
||||
"trigger_on_true": true,
|
||||
"session_id": "plot_1"
|
||||
}
|
||||
},
|
||||
"session_counter": 2,
|
||||
"last_saved": "2025-08-10T00:37:46.525175",
|
||||
"version": "1.0"
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "datasets.schema.json",
|
||||
"title": "Datasets Configuration",
|
||||
"description": "Esquema para editar plc_datasets.json (múltiples datasets y variables)",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"datasets": {
|
||||
"type": "object",
|
||||
"title": "Datasets",
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"title": "Dataset Name",
|
||||
"description": "Nombre legible del dataset",
|
||||
"minLength": 1,
|
||||
"maxLength": 60
|
||||
},
|
||||
"prefix": {
|
||||
"type": "string",
|
||||
"title": "CSV Prefix",
|
||||
"description": "Prefijo para archivos CSV",
|
||||
"pattern": "^[a-zA-Z0-9_-]+$",
|
||||
"minLength": 1,
|
||||
"maxLength": 20
|
||||
},
|
||||
"variables": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"area": {
|
||||
"type": "string",
|
||||
"title": "Memory Area",
|
||||
"enum": [
|
||||
"db",
|
||||
"mw",
|
||||
"m",
|
||||
"pew",
|
||||
"pe",
|
||||
"paw",
|
||||
"pa",
|
||||
"e",
|
||||
"a",
|
||||
"mb"
|
||||
]
|
||||
},
|
||||
"db": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"title": "DB Number",
|
||||
"minimum": 1,
|
||||
"maximum": 9999
|
||||
},
|
||||
"offset": {
|
||||
"type": "integer",
|
||||
"title": "Offset",
|
||||
"minimum": 0,
|
||||
"maximum": 8191
|
||||
},
|
||||
"bit": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"title": "Bit Position",
|
||||
"minimum": 0,
|
||||
"maximum": 7
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"title": "Data Type",
|
||||
"enum": [
|
||||
"real",
|
||||
"int",
|
||||
"bool",
|
||||
"dint",
|
||||
"word",
|
||||
"byte",
|
||||
"uint",
|
||||
"udint",
|
||||
"sint",
|
||||
"usint"
|
||||
]
|
||||
},
|
||||
"streaming": {
|
||||
"type": "boolean",
|
||||
"title": "Stream to PlotJuggler",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"area",
|
||||
"offset",
|
||||
"type"
|
||||
]
|
||||
}
|
||||
},
|
||||
"streaming_variables": {
|
||||
"type": "array",
|
||||
"title": "Streaming Variables",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"sampling_interval": {
|
||||
"type": [
|
||||
"number",
|
||||
"null"
|
||||
],
|
||||
"title": "Sampling Interval (s)",
|
||||
"description": "Vacío para usar el intervalo global",
|
||||
"minimum": 0.01,
|
||||
"maximum": 10
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"title": "Dataset Enabled",
|
||||
"default": false,
|
||||
"enum": [
|
||||
true,
|
||||
false
|
||||
],
|
||||
"options": {
|
||||
"enum_titles": [
|
||||
"Activate",
|
||||
"Deactivate"
|
||||
]
|
||||
}
|
||||
},
|
||||
"created": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"title": "Created"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"prefix",
|
||||
"variables",
|
||||
"streaming_variables"
|
||||
]
|
||||
}
|
||||
},
|
||||
"active_datasets": {
|
||||
"type": "array",
|
||||
"title": "Active Datasets",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"current_dataset_id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"title": "Current Dataset Id"
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"title": "Version"
|
||||
},
|
||||
"last_update": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"title": "Last Update"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"datasets"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "plc.schema.json",
|
||||
"title": "PLC & UDP Configuration",
|
||||
"description": "Esquema para editar plc_config.json",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"plc_config": {
|
||||
"type": "object",
|
||||
"title": "PLC Configuration",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ip": {
|
||||
"type": "string",
|
||||
"title": "PLC IP",
|
||||
"description": "Dirección IP del PLC (S7-31x)",
|
||||
"format": "ipv4",
|
||||
"pattern": "^.+$"
|
||||
},
|
||||
"rack": {
|
||||
"type": "integer",
|
||||
"title": "Rack",
|
||||
"description": "Número de rack (0-7)",
|
||||
"minimum": 0,
|
||||
"maximum": 7,
|
||||
"default": 0
|
||||
},
|
||||
"slot": {
|
||||
"type": "integer",
|
||||
"title": "Slot",
|
||||
"description": "Número de slot (generalmente 2)",
|
||||
"minimum": 0,
|
||||
"maximum": 31,
|
||||
"default": 2
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"ip",
|
||||
"rack",
|
||||
"slot"
|
||||
]
|
||||
},
|
||||
"udp_config": {
|
||||
"type": "object",
|
||||
"title": "UDP Configuration",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"host": {
|
||||
"type": "string",
|
||||
"title": "UDP Host",
|
||||
"pattern": "^.+$",
|
||||
"default": "127.0.0.1"
|
||||
},
|
||||
"port": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 65535,
|
||||
"default": 9870
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"host",
|
||||
"port"
|
||||
]
|
||||
},
|
||||
"sampling_interval": {
|
||||
"type": "number",
|
||||
"minimum": 0.01,
|
||||
"maximum": 10,
|
||||
"title": "Sampling Interval (s)",
|
||||
"description": "Intervalo global de muestreo en segundos",
|
||||
"default": 0.1
|
||||
},
|
||||
"csv_config": {
|
||||
"type": "object",
|
||||
"title": "CSV Recording",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"records_directory": {
|
||||
"type": "string",
|
||||
"title": "Records Directory",
|
||||
"default": "records"
|
||||
},
|
||||
"rotation_enabled": {
|
||||
"type": "boolean",
|
||||
"title": "Rotation",
|
||||
"default": true,
|
||||
"enum": [
|
||||
true,
|
||||
false
|
||||
],
|
||||
"options": {
|
||||
"enum_titles": [
|
||||
"Activate",
|
||||
"Deactivate"
|
||||
]
|
||||
}
|
||||
},
|
||||
"max_size_mb": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"minimum": 1,
|
||||
"title": "Max Size (MB)",
|
||||
"default": 1000
|
||||
},
|
||||
"max_days": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"minimum": 1,
|
||||
"title": "Max Days",
|
||||
"default": 30
|
||||
},
|
||||
"max_hours": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"minimum": 1,
|
||||
"title": "Max Hours",
|
||||
"default": null
|
||||
},
|
||||
"cleanup_interval_hours": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"title": "Cleanup Interval (h)",
|
||||
"default": 24
|
||||
},
|
||||
"last_cleanup": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"title": "Last Cleanup"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"records_directory",
|
||||
"rotation_enabled",
|
||||
"cleanup_interval_hours"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"plc_config",
|
||||
"udp_config",
|
||||
"sampling_interval",
|
||||
"csv_config"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "plots.schema.json",
|
||||
"title": "Plot Sessions",
|
||||
"description": "Esquema para editar plot_sessions.json (sesiones de gráfica)",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"plots": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"title": "Plot Name",
|
||||
"description": "Nombre de la sesión de gráfica"
|
||||
},
|
||||
"variables": {
|
||||
"type": "array",
|
||||
"title": "Variables",
|
||||
"description": "Variables a graficar",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
"time_window": {
|
||||
"type": "integer",
|
||||
"title": "Time Window (s)",
|
||||
"description": "Ventana temporal en segundos",
|
||||
"minimum": 5,
|
||||
"maximum": 3600,
|
||||
"default": 60
|
||||
},
|
||||
"y_min": {
|
||||
"type": [
|
||||
"number",
|
||||
"null"
|
||||
],
|
||||
"title": "Y Min",
|
||||
"description": "Vacío para auto"
|
||||
},
|
||||
"y_max": {
|
||||
"type": [
|
||||
"number",
|
||||
"null"
|
||||
],
|
||||
"title": "Y Max",
|
||||
"description": "Vacío para auto"
|
||||
},
|
||||
"trigger_variable": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"title": "Trigger Variable"
|
||||
},
|
||||
"trigger_enabled": {
|
||||
"type": "boolean",
|
||||
"title": "Enable Trigger",
|
||||
"default": false
|
||||
},
|
||||
"trigger_on_true": {
|
||||
"type": "boolean",
|
||||
"title": "Trigger on True",
|
||||
"default": true
|
||||
},
|
||||
"session_id": {
|
||||
"type": "string",
|
||||
"title": "Session Id"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"variables",
|
||||
"time_window"
|
||||
]
|
||||
}
|
||||
},
|
||||
"session_counter": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"default": 0
|
||||
},
|
||||
"last_saved": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"default": "1.0"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"plots"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"datasets": {
|
||||
"ui:description": "Define datasets, their variables and streaming flags.",
|
||||
"items": {
|
||||
"name": {
|
||||
"ui:placeholder": "Temperature Sensors"
|
||||
},
|
||||
"prefix": {
|
||||
"ui:placeholder": "temp"
|
||||
},
|
||||
"sampling_interval": {
|
||||
"ui:widget": "UpDownWidget"
|
||||
},
|
||||
"enabled": {
|
||||
"ui:widget": "CheckboxWidget"
|
||||
},
|
||||
"variables": {
|
||||
"ui:description": "Variables inside this dataset",
|
||||
"items": {
|
||||
"area": {
|
||||
"ui:widget": "SelectWidget"
|
||||
},
|
||||
"db": {
|
||||
"ui:widget": "UpDownWidget"
|
||||
},
|
||||
"offset": {
|
||||
"ui:widget": "UpDownWidget"
|
||||
},
|
||||
"bit": {
|
||||
"ui:widget": "UpDownWidget"
|
||||
},
|
||||
"type": {
|
||||
"ui:widget": "SelectWidget"
|
||||
},
|
||||
"streaming": {
|
||||
"ui:widget": "CheckboxWidget"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"plc_config": {
|
||||
"ip": {
|
||||
"ui:placeholder": "192.168.1.100"
|
||||
},
|
||||
"rack": {
|
||||
"ui:widget": "UpDownWidget"
|
||||
},
|
||||
"slot": {
|
||||
"ui:widget": "UpDownWidget"
|
||||
}
|
||||
},
|
||||
"udp_config": {
|
||||
"host": {
|
||||
"ui:placeholder": "127.0.0.1"
|
||||
},
|
||||
"port": {
|
||||
"ui:widget": "UpDownWidget"
|
||||
}
|
||||
},
|
||||
"sampling_interval": {
|
||||
"ui:widget": "UpDownWidget"
|
||||
},
|
||||
"csv_config": {
|
||||
"records_directory": {
|
||||
"ui:placeholder": "records"
|
||||
},
|
||||
"rotation_enabled": {
|
||||
"ui:widget": "CheckboxWidget"
|
||||
},
|
||||
"max_size_mb": {
|
||||
"ui:widget": "UpDownWidget"
|
||||
},
|
||||
"max_days": {
|
||||
"ui:widget": "UpDownWidget"
|
||||
},
|
||||
"max_hours": {
|
||||
"ui:widget": "UpDownWidget"
|
||||
},
|
||||
"cleanup_interval_hours": {
|
||||
"ui:widget": "UpDownWidget"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"plots": {
|
||||
"items": {
|
||||
"time_window": {
|
||||
"ui:widget": "UpDownWidget"
|
||||
},
|
||||
"y_min": {
|
||||
"ui:widget": "UpDownWidget"
|
||||
},
|
||||
"y_max": {
|
||||
"ui:widget": "UpDownWidget"
|
||||
},
|
||||
"trigger_enabled": {
|
||||
"ui:widget": "CheckboxWidget"
|
||||
},
|
||||
"trigger_on_true": {
|
||||
"ui:widget": "CheckboxWidget"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,9 +24,11 @@ class ConfigManager:
|
|||
"""Initialize configuration manager"""
|
||||
self.logger = logger
|
||||
|
||||
# Configuration file paths
|
||||
self.config_file = resource_path("plc_config.json")
|
||||
self.datasets_file = resource_path("plc_datasets.json")
|
||||
# Configuration file paths (reorganized under config/data)
|
||||
data_dir = os.path.join("config", "data")
|
||||
os.makedirs(resource_path(data_dir), exist_ok=True)
|
||||
self.config_file = resource_path(os.path.join(data_dir, "plc_config.json"))
|
||||
self.datasets_file = resource_path(os.path.join(data_dir, "plc_datasets.json"))
|
||||
self.state_file = resource_path("system_state.json")
|
||||
|
||||
# Default configurations
|
||||
|
|
|
@ -227,8 +227,10 @@ class PlotManager:
|
|||
self.event_logger = event_logger
|
||||
self.logger = logger
|
||||
|
||||
# Persistent storage
|
||||
self.plots_file = resource_path("plot_sessions.json")
|
||||
# Persistent storage (reorganized under config/data)
|
||||
self.plots_file = resource_path(
|
||||
os.path.join("config", "data", "plot_sessions.json")
|
||||
)
|
||||
|
||||
# Load existing plots from disk
|
||||
self.load_plots()
|
||||
|
|
|
@ -36,14 +36,18 @@ class ConfigSchemaManager:
|
|||
self.plot_manager = plot_manager
|
||||
self.logger = logger
|
||||
|
||||
self.schemas_dir = resource_path("schemas")
|
||||
# Reorganized schema directories
|
||||
self.schemas_dir = resource_path(os.path.join("config", "schema"))
|
||||
self.ui_schemas_dir = resource_path(os.path.join("config", "schema", "ui"))
|
||||
self.schemas_index: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
# Mapa de id -> ruta de archivo real de configuración
|
||||
data_dir = resource_path(os.path.join("config", "data"))
|
||||
os.makedirs(data_dir, exist_ok=True)
|
||||
self.config_files: Dict[str, str] = {
|
||||
"plc": resource_path("plc_config.json"),
|
||||
"datasets": resource_path("plc_datasets.json"),
|
||||
"plots": resource_path("plot_sessions.json"),
|
||||
"plc": os.path.join(data_dir, "plc_config.json"),
|
||||
"datasets": os.path.join(data_dir, "plc_datasets.json"),
|
||||
"plots": os.path.join(data_dir, "plot_sessions.json"),
|
||||
}
|
||||
|
||||
self._load_all_schemas()
|
||||
|
@ -83,6 +87,18 @@ class ConfigSchemaManager:
|
|||
raise ValueError(f"Schema '{schema_id}' not found")
|
||||
return self.schemas_index[schema_id]
|
||||
|
||||
def get_ui_schema(self, schema_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""Load optional RJSF UI schema if present."""
|
||||
try:
|
||||
path = os.path.join(self.ui_schemas_dir, f"{schema_id}.uischema.json")
|
||||
if os.path.exists(path):
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
if self.logger:
|
||||
self.logger.warning(f"Error loading UI schema for '{schema_id}': {e}")
|
||||
return None
|
||||
|
||||
def read_config(self, config_id: str) -> Dict[str, Any]:
|
||||
if config_id == "plc":
|
||||
# Construir desde ConfigManager para asegurar consistencia
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>PLC S7-31x Streamer & Logger - React</title>
|
||||
<link rel="icon" href="/images/SIDEL.png" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>PLC S7-31x Streamer & Logger - React</title>
|
||||
<link rel="icon" type="image/png" href="/favicon.ico?v=5" />
|
||||
<link rel="shortcut icon" type="image/png" href="/favicon.ico?v=5" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
|
@ -1,4 +1,5 @@
|
|||
import React from 'react'
|
||||
import recLogo from './assets/logo/record.png'
|
||||
import { Routes, Route, Link } from 'react-router-dom'
|
||||
import StatusPage from './pages/Status.jsx'
|
||||
import EventsPage from './pages/Events.jsx'
|
||||
|
@ -12,7 +13,7 @@ function Home() {
|
|||
<div className="container py-3">
|
||||
<header className="mb-3">
|
||||
<h1 className="h3 d-flex align-items-center gap-2">
|
||||
<img src="/images/SIDEL.png" alt="SIDEL" style={{ height: 28 }} />
|
||||
<img src={recLogo} alt="REC" style={{ height: 28 }} />
|
||||
PLC S7-31x Streamer & Logger (React)
|
||||
</h1>
|
||||
<p className="text-muted mb-0">React base ready. We will migrate views incrementally.</p>
|
||||
|
@ -39,7 +40,7 @@ function NavBar() {
|
|||
<nav className="navbar navbar-expand-lg bg-light border-bottom">
|
||||
<div className="container">
|
||||
<Link to="/" className="navbar-brand d-flex align-items-center gap-2">
|
||||
<img src="/images/SIDEL.png" alt="SIDEL" style={{ height: 24 }} />
|
||||
<img src={recLogo} alt="REC" style={{ height: 24 }} />
|
||||
<span className="fw-semibold">PLC Streamer</span>
|
||||
</Link>
|
||||
<div className="d-flex gap-2">
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
|
@ -44,6 +44,9 @@ export default function ConfigPage() {
|
|||
const [message, setMessage] = useState('')
|
||||
|
||||
const available = useMemo(() => {
|
||||
if (!schemas) return []
|
||||
if (Array.isArray(schemas.schemas)) return schemas.schemas.map(s => s.id)
|
||||
if (schemas.schemas && typeof schemas.schemas === 'object') return Object.keys(schemas.schemas)
|
||||
const ids = []
|
||||
if (schemas?.plc) ids.push('plc')
|
||||
if (schemas?.datasets) ids.push('datasets')
|
||||
|
@ -60,7 +63,7 @@ export default function ConfigPage() {
|
|||
readConfig(id),
|
||||
])
|
||||
setSchema(schemaResp.schema)
|
||||
setUiSchema(buildUiSchema(schemaResp.schema))
|
||||
setUiSchema(schemaResp.ui_schema || buildUiSchema(schemaResp.schema))
|
||||
setFormData(dataResp.data)
|
||||
} catch (e) {
|
||||
setMessage(e.message || 'Error loading schema/config')
|
||||
|
|
|
@ -108,7 +108,7 @@ export default function DashboardPage() {
|
|||
const available = useMemo(() => {
|
||||
if (!schemas) return []
|
||||
// Accept multiple shapes from API
|
||||
if (Array.isArray(schemas.schemas)) return schemas.schemas
|
||||
if (Array.isArray(schemas.schemas)) return schemas.schemas.map(s => s.id || s)
|
||||
if (schemas.schemas && typeof schemas.schemas === 'object') return Object.keys(schemas.schemas)
|
||||
const ids = []
|
||||
if (schemas?.plc) ids.push('plc')
|
||||
|
@ -170,7 +170,7 @@ export default function DashboardPage() {
|
|||
readConfig(id),
|
||||
])
|
||||
setSchema(schemaResp.schema)
|
||||
setUiSchema(buildUiSchema(schemaResp.schema))
|
||||
setUiSchema(schemaResp.ui_schema || buildUiSchema(schemaResp.schema))
|
||||
setFormData(dataResp.data)
|
||||
} catch (e) {
|
||||
setMessage(e.message || 'Error loading config')
|
||||
|
|
|
@ -15,6 +15,14 @@ export default defineConfig({
|
|||
'/images': {
|
||||
target: 'http://localhost:5050',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/static': {
|
||||
target: 'http://localhost:5050',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/favicon.ico': {
|
||||
target: 'http://localhost:5050',
|
||||
changeOrigin: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
61
main.py
61
main.py
|
@ -44,6 +44,12 @@ def resource_path(relative_path):
|
|||
return os.path.join(base_path, relative_path)
|
||||
|
||||
|
||||
def project_path(*parts: str) -> str:
|
||||
"""Build absolute path from the project root (based on this file)."""
|
||||
base_dir = os.path.abspath(os.path.dirname(__file__))
|
||||
return os.path.join(base_dir, *parts)
|
||||
|
||||
|
||||
# Global streamer instance (will be initialized in main)
|
||||
streamer = None
|
||||
|
||||
|
@ -67,6 +73,50 @@ def serve_static(filename):
|
|||
return send_from_directory("static", filename)
|
||||
|
||||
|
||||
@app.route("/favicon.ico")
|
||||
def serve_favicon():
|
||||
"""Serve application favicon from robust locations.
|
||||
|
||||
Priority:
|
||||
1) frontend/public/favicon.ico
|
||||
2) frontend/public/record.png
|
||||
3) static/icons/record.png
|
||||
"""
|
||||
# Use absolute paths for reliability (works in dev and bundled)
|
||||
public_dir = project_path("frontend", "public")
|
||||
public_favicon = os.path.join(public_dir, "favicon.ico")
|
||||
public_record = os.path.join(public_dir, "record.png")
|
||||
|
||||
if os.path.exists(public_favicon):
|
||||
return send_from_directory(public_dir, "favicon.ico")
|
||||
if os.path.exists(public_record):
|
||||
return send_from_directory(public_dir, "record.png")
|
||||
|
||||
# Fallback: static/icons
|
||||
static_icons_dir = project_path("static", "icons")
|
||||
if os.path.exists(os.path.join(static_icons_dir, "record.png")):
|
||||
return send_from_directory(static_icons_dir, "record.png")
|
||||
|
||||
# Final fallback: 404
|
||||
return Response("Favicon not found", status=404, mimetype="text/plain")
|
||||
|
||||
|
||||
@app.route("/record.png")
|
||||
def serve_public_record_png():
|
||||
"""Serve /record.png from the React public folder with fallbacks."""
|
||||
public_dir = project_path("frontend", "public")
|
||||
public_record = os.path.join(public_dir, "record.png")
|
||||
|
||||
if os.path.exists(public_record):
|
||||
return send_from_directory(public_dir, "record.png")
|
||||
|
||||
static_icons_dir = project_path("static", "icons")
|
||||
if os.path.exists(os.path.join(static_icons_dir, "record.png")):
|
||||
return send_from_directory(static_icons_dir, "record.png")
|
||||
|
||||
return Response("record.png not found", status=404, mimetype="text/plain")
|
||||
|
||||
|
||||
# ==============================
|
||||
# Frontend (React SPA)
|
||||
# ==============================
|
||||
|
@ -125,7 +175,16 @@ def get_config_schema(schema_id):
|
|||
|
||||
try:
|
||||
schema = streamer.schema_manager.get_schema(schema_id)
|
||||
return jsonify({"success": True, "schema": schema})
|
||||
ui_schema = None
|
||||
# Try load optional UI schema
|
||||
try:
|
||||
ui_schema = streamer.schema_manager.get_ui_schema(schema_id)
|
||||
except Exception:
|
||||
ui_schema = None
|
||||
resp = {"success": True, "schema": schema}
|
||||
if ui_schema is not None:
|
||||
resp["ui_schema"] = ui_schema
|
||||
return jsonify(resp)
|
||||
except ValueError as e:
|
||||
return jsonify({"success": False, "error": str(e)}), 404
|
||||
except Exception as e:
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
Loading…
Reference in New Issue