Eliminación de archivos obsoletos como plc_config.json, plc_datasets.json y plot_sessions.json. Se actualizó application_events.json para incluir un nuevo evento de inicio de aplicación y se ajustaron las fechas de última actualización. Se realizaron cambios en los esquemas de configuración para mejorar la gestión de datos y se actualizaron las dependencias en package.json para incluir nuevas bibliotecas. Además, se modificó la interfaz de usuario para utilizar el nuevo paquete @rjsf/fluent-ui.

This commit is contained in:
Miguel 2025-08-12 09:16:46 +02:00
parent 0c11ee3ae2
commit 1833fff18f
9 changed files with 310 additions and 365 deletions

View File

@ -9252,8 +9252,15 @@
"event_type": "application_started",
"message": "Application initialization completed successfully",
"details": {}
},
{
"timestamp": "2025-08-12T09:13:36.619106",
"level": "info",
"event_type": "application_started",
"message": "Application initialization completed successfully",
"details": {}
}
],
"last_updated": "2025-08-11T16:21:42.173167",
"total_entries": 861
"last_updated": "2025-08-12T09:13:36.619106",
"total_entries": 862
}

View File

@ -1,154 +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"
]
"$id": "plc.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false,
"description": "Esquema para editar plc_config.json",
"properties": {
"csv_config": {
"additionalProperties": false,
"properties": {
"cleanup_interval_hours": {
"default": 24,
"minimum": 1,
"title": "Cleanup Interval (h)",
"type": "integer"
},
"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"
]
"last_cleanup": {
"title": "Last Cleanup",
"type": [
"string",
"null"
]
},
"sampling_interval": {
"type": "number",
"minimum": 0.01,
"maximum": 10,
"title": "Sampling Interval (s)",
"description": "Intervalo global de muestreo en segundos",
"default": 0.1
"max_days": {
"default": 30,
"minimum": 1,
"title": "Max Days",
"type": [
"integer",
"null"
]
},
"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"
"max_hours": {
"default": null,
"minimum": 1,
"title": "Max Hours",
"type": [
"integer",
"null"
]
},
"max_size_mb": {
"default": 1000,
"minimum": 1,
"title": "Max Size (MB)",
"type": [
"integer",
"null"
]
},
"records_directory": {
"default": "records",
"title": "Records Directory",
"type": "string"
},
"rotation_enabled": {
"default": true,
"enum": [
true,
false
],
"options": {
"enum_titles": [
"Activate",
"Deactivate"
]
},
"title": "Rotation",
"type": "boolean"
}
},
"required": [
"records_directory",
"rotation_enabled",
"cleanup_interval_hours"
],
"title": "CSV Recording",
"type": "object"
},
"required": [
"plc_config",
"udp_config",
"sampling_interval",
"csv_config"
]
"plc_config": {
"additionalProperties": false,
"properties": {
"ip": {
"description": "Dirección IP del PLC (S7-31x)",
"format": "ipv4",
"pattern": "^.+$",
"title": "PLC IP",
"type": "string"
},
"rack": {
"default": 0,
"description": "Número de rack (0-7)",
"maximum": 7,
"minimum": 0,
"title": "Rack",
"type": "integer"
},
"slot": {
"default": 2,
"description": "Número de slot (generalmente 2)",
"maximum": 31,
"minimum": 0,
"title": "Slot",
"type": "integer"
}
},
"required": [
"ip",
"rack",
"slot"
],
"title": "PLC Configuration",
"type": "object"
},
"sampling_interval": {
"default": 0.1,
"description": "Intervalo global de muestreo en segundos",
"maximum": 10,
"minimum": 0.01,
"title": "Sampling Interval (s)",
"type": "number"
},
"udp_config": {
"additionalProperties": false,
"properties": {
"host": {
"default": "127.0.0.1",
"pattern": "^.+$",
"title": "UDP Host",
"type": "string"
},
"port": {
"default": 9870,
"maximum": 65535,
"minimum": 1,
"type": "integer"
}
},
"required": [
"host",
"port"
],
"title": "UDP Configuration",
"type": "object"
}
},
"required": [
"plc_config",
"udp_config",
"sampling_interval",
"csv_config"
],
"title": "PLC & UDP Configuration",
"type": "object"
}

View File

@ -1,44 +1,60 @@
{
"plc_config": {
"ip": {
"ui:placeholder": "192.168.1.100"
},
"rack": {
"ui:widget": "UpDownWidget"
},
"slot": {
"ui:widget": "UpDownWidget"
}
"csv_config": {
"cleanup_interval_hours": {
"ui:widget": "UpDownWidget"
},
"udp_config": {
"host": {
"ui:placeholder": "127.0.0.1"
},
"port": {
"ui:widget": "UpDownWidget"
}
"max_days": {
"ui:widget": "UpDownWidget"
},
"sampling_interval": {
"ui:widget": "UpDownWidget"
"max_hours": {
"ui:widget": "UpDownWidget"
},
"csv_config": {
"records_directory": {
"ui:placeholder": "records"
"max_size_mb": {
"ui:widget": "UpDownWidget"
},
"records_directory": {
"ui:placeholder": "records"
},
"rotation_enabled": {
"ui:widget": "CheckboxWidget"
},
"ui:layout": [
[
{
"name": "cleanup_interval_hours",
"width": 3
},
"rotation_enabled": {
"ui:widget": "CheckboxWidget"
{
"name": "last_cleanup",
"width": 3
},
"max_size_mb": {
"ui:widget": "UpDownWidget"
},
"max_days": {
"ui:widget": "UpDownWidget"
},
"max_hours": {
"ui:widget": "UpDownWidget"
},
"cleanup_interval_hours": {
"ui:widget": "UpDownWidget"
{
"name": "max_days",
"width": 3
}
]
]
},
"plc_config": {
"ip": {
"ui:placeholder": "192.168.1.100"
},
"rack": {
"ui:widget": "UpDownWidget"
},
"slot": {
"ui:widget": "UpDownWidget"
}
},
"sampling_interval": {
"ui:widget": "UpDownWidget"
},
"udp_config": {
"host": {
"ui:placeholder": "127.0.0.1"
},
"port": {
"ui:widget": "UpDownWidget"
}
}
}

View File

@ -10,7 +10,9 @@
},
"dependencies": {
"@rjsf/core": "^5.24.12",
"@rjsf/fluent-ui": "^5.24.12",
"@rjsf/validator-ajv8": "^5.24.12",
"@fluentui/react": "^8.120.2",
"bootstrap": "^5.3.3",
"react": "^18.2.0",
"react-bootstrap": "^2.10.4",

View File

@ -1,6 +1,6 @@
import React, { useEffect, useMemo, useState } from 'react'
import { Container, Row, Col, Button, ButtonGroup, Dropdown, DropdownButton } from 'react-bootstrap'
import Form from '@rjsf/core'
import Form from '@rjsf/fluent-ui'
import validator from '@rjsf/validator-ajv8'
import { listSchemas, getSchema, readConfig, writeConfig } from '../services/api.js'
import { widgets } from '../components/rjsf/widgets.jsx'
@ -154,7 +154,6 @@ export default function ConfigPage() {
validator={validator}
onSubmit={handleSave}
onChange={({ formData }) => setFormData(formData)}
widgets={widgets}
uiSchema={uiSchema}
>
<div className="d-flex gap-2">

View File

@ -1,7 +1,6 @@
import React, { useEffect, useMemo, useRef, useState } from 'react'
import Form from '@rjsf/core'
import Form from '@rjsf/fluent-ui'
import validator from '@rjsf/validator-ajv8'
import { widgets } from '../components/rjsf/widgets.jsx'
import {
getStatus,
getEvents,
@ -223,78 +222,19 @@ export default function DashboardPage() {
{statusError && <div className="alert alert-danger py-2">{statusError}</div>}
{status && <StatusBar status={status} />}
<div className="mb-3">
<button
className="btn btn-sm btn-outline-secondary"
onClick={() => setAccordionOpen(o => !o)}
aria-expanded={accordionOpen}
>
{accordionOpen ? '▾' : '▸'} Config
</button>
</div>
{accordionOpen && (
<div className="card mb-4">
{['plc', 'datasets', 'plots'].map((sectionId) => (
<div className="card mb-4" key={sectionId}>
<div className="card-body">
<div className="d-flex flex-wrap gap-2 align-items-center mb-3">
<div className="fw-semibold">🧩 Schema:</div>
<div className="btn-group btn-group-sm" role="group">
{['plc', 'datasets', 'plots'].map(id => (
<button
key={id}
type="button"
className={`btn btn-outline-primary ${currentSchemaId === id ? 'active' : ''}`}
onClick={() => setCurrentSchemaId(id)}
>
{id}
</button>
))}
</div>
<div className="fw-semibold text-uppercase">🧩 {sectionId}</div>
<div className="ms-auto d-flex gap-2">
<label className="btn btn-sm btn-outline-secondary mb-0">
Import
<input type="file" accept="application/json" hidden onChange={async (e) => {
const file = e.target.files?.[0]
if (!file) return
try {
const text = await file.text()
const json = JSON.parse(text)
setFormData(json)
setMessage(`Imported ${file.name}`)
} catch { setMessage('Invalid JSON file') }
}} />
</label>
<button className="btn btn-sm btn-outline-secondary" onClick={() => {
const blob = new Blob([JSON.stringify(formData ?? {}, null, 2)], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `${currentSchemaId}_config.json`
a.click()
URL.revokeObjectURL(url)
}}> Export</button>
<button className="btn btn-sm btn-primary" onClick={saveConfig} disabled={saving}>💾 Save</button>
<SectionControls sectionId={sectionId} />
</div>
</div>
{message && <div className="alert alert-info py-2">{message}</div>}
{schema && (
<Form
schema={schema}
formData={formData}
validator={validator}
onSubmit={(e) => { setFormData(e.formData); saveConfig() }}
onChange={({ formData }) => setFormData(formData)}
widgets={widgets}
uiSchema={uiSchema}
>
<div />
</Form>
)}
<SectionForm sectionId={sectionId} />
</div>
</div>
)}
))}
<section className="mb-2 d-flex align-items-center justify-content-between">
<h2 className="h6 mb-0">📋 Recent Events</h2>
@ -336,4 +276,98 @@ export default function DashboardPage() {
)
}
function SectionControls({ sectionId }) {
const [busy, setBusy] = useState(false)
const [localData, setLocalData] = useState(null)
useEffect(() => {
; (async () => {
try {
const res = await readConfig(sectionId)
setLocalData(res.data)
} catch { /* ignore */ }
})()
}, [sectionId])
return (
<div className="d-flex gap-2">
<label className="btn btn-sm btn-outline-secondary mb-0">
Import
<input type="file" accept="application/json" hidden onChange={async (e) => {
const file = e.target.files?.[0]
if (!file) return
try {
const text = await file.text()
const json = JSON.parse(text)
setBusy(true)
await writeConfig(sectionId, json)
setLocalData(json)
} catch { /* ignore */ } finally { setBusy(false) }
}} />
</label>
<button className="btn btn-sm btn-outline-secondary" onClick={async () => {
const data = localData ?? (await readConfig(sectionId)).data
const blob = new Blob([JSON.stringify(data ?? {}, null, 2)], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `${sectionId}_config.json`
a.click()
URL.revokeObjectURL(url)
}}> Export</button>
<button className="btn btn-sm btn-primary" disabled={busy} onClick={async () => {
setBusy(true)
try {
const res = await readConfig(sectionId)
await writeConfig(sectionId, res.data)
} finally { setBusy(false) }
}}>💾 Save</button>
</div>
)
}
function SectionForm({ sectionId }) {
const [localSchema, setLocalSchema] = useState(null)
const [localUi, setLocalUi] = useState(null)
const [localData, setLocalData] = useState(null)
const [loading, setLoading] = useState(true)
const [saving, setSaving] = useState(false)
useEffect(() => {
let mounted = true
; (async () => {
setLoading(true)
try {
const [schemaResp, dataResp] = await Promise.all([
getSchema(sectionId),
readConfig(sectionId),
])
if (!mounted) return
setLocalSchema(schemaResp.schema)
setLocalUi(schemaResp.ui_schema || buildUiSchema(schemaResp.schema))
setLocalData(dataResp.data)
} finally {
if (mounted) setLoading(false)
}
})()
return () => { mounted = false }
}, [sectionId])
if (loading || !localSchema) return <div className="text-muted">Loading {sectionId}</div>
return (
<Form
schema={localSchema}
formData={localData}
validator={validator}
onChange={({ formData }) => setLocalData(formData)}
onSubmit={async ({ formData }) => {
setSaving(true)
try { await writeConfig(sectionId, formData) } finally { setSaving(false) }
}}
uiSchema={localUi}
>
<div />
</Form>
)
}

View File

@ -1,21 +0,0 @@
{
"plc_config": {
"ip": "10.1.33.11",
"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"
}
}

View File

@ -1,69 +0,0 @@
{
"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"
}

View File

@ -1,23 +0,0 @@
{
"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"
}