Cambios backend/frontend
This commit is contained in:
2220
frontend/package-lock.json
generated
2220
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -21,10 +21,12 @@
|
||||
"leaflet": "^1.9.4",
|
||||
"leaflet.heat": "^0.2.0",
|
||||
"react": "^19.0.0",
|
||||
"react-bootstrap": "^2.10.10",
|
||||
"react-chartjs-2": "^5.3.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-leaflet": "^5.0.0",
|
||||
"react-router-dom": "^7.3.0"
|
||||
"react-router-dom": "^7.3.0",
|
||||
"swagger-ui-react": "^5.22.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.19.0",
|
||||
|
||||
453
frontend/public/apidoc.json
Normal file
453
frontend/public/apidoc.json
Normal file
@@ -0,0 +1,453 @@
|
||||
{
|
||||
"name": "ContaminUS",
|
||||
"version": "1.0.0",
|
||||
"logic_api": [
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/batch",
|
||||
"description": "Añadir los valores de los sensores (batch)",
|
||||
"params": [
|
||||
{
|
||||
"name": "deviceId",
|
||||
"type": "integer",
|
||||
"description": "ID del dispositivo",
|
||||
"in": "body",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "sensorId",
|
||||
"type": "integer",
|
||||
"description": "ID del sensor",
|
||||
"in": "body",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "lat",
|
||||
"type": "float",
|
||||
"description": "Latitud",
|
||||
"in": "body",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "lon",
|
||||
"type": "float",
|
||||
"description": "Longitud",
|
||||
"in": "body",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "temperature",
|
||||
"type": "float",
|
||||
"description": "Temperatura",
|
||||
"in": "body",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "humidity",
|
||||
"type": "float",
|
||||
"description": "Humedad",
|
||||
"in": "body",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "pressure",
|
||||
"type": "float",
|
||||
"description": "Presión",
|
||||
"in": "body",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"type": "float",
|
||||
"description": "Valor de CO",
|
||||
"in": "body",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "timestamp",
|
||||
"type": "long",
|
||||
"description": "Marca temporal del valor",
|
||||
"in": "body",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/v1/groups/:groupId/devices/:deviceId/latest-values",
|
||||
"description": "Obtener los últimos valores de un dispositivo",
|
||||
"params": [
|
||||
{
|
||||
"name": "groupId",
|
||||
"type": "integer",
|
||||
"description": "ID del grupo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "deviceId",
|
||||
"type": "integer",
|
||||
"description": "ID del dispositivo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/v1/groups/:groupId/devices/:deviceId/pollution-map",
|
||||
"description": "Obtener el mapa de contaminación de un dispositivo",
|
||||
"params": [
|
||||
{
|
||||
"name": "groupId",
|
||||
"type": "integer",
|
||||
"description": "ID del grupo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "deviceId",
|
||||
"type": "integer",
|
||||
"description": "ID del dispositivo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/v1/groups/:groupId/devices/:deviceId/history",
|
||||
"description": "Obtener el histórico de un dispositivo",
|
||||
"params": [
|
||||
{
|
||||
"name": "groupId",
|
||||
"type": "integer",
|
||||
"description": "ID del grupo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "deviceId",
|
||||
"type": "integer",
|
||||
"description": "ID del dispositivo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/v1/groups/:groupId/devices/:deviceId/sensors/:sensorId/values",
|
||||
"description": "Obtener los valores de un sensor específico",
|
||||
"params": [
|
||||
{
|
||||
"name": "groupId",
|
||||
"type": "integer",
|
||||
"description": "ID del grupo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "deviceId",
|
||||
"type": "integer",
|
||||
"description": "ID del dispositivo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "sensorId",
|
||||
"type": "integer",
|
||||
"description": "ID del sensor",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/v1/groups/:groupId/devices/:deviceId/actuators/:actuator_id/status",
|
||||
"description": "Obtener el estado de un actuador",
|
||||
"params": [
|
||||
{
|
||||
"name": "groupId",
|
||||
"type": "integer",
|
||||
"description": "ID del grupo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "deviceId",
|
||||
"type": "integer",
|
||||
"description": "ID del dispositivo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "actuator_id",
|
||||
"type": "integer",
|
||||
"description": "ID del actuador",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"raw_api": [
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/raw/v1/groups",
|
||||
"description": "Obtener todos los grupos"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/raw/v1/groups",
|
||||
"description": "Crear un nuevo grupo",
|
||||
"params": [
|
||||
{
|
||||
"name": "groupId",
|
||||
"type": "integer",
|
||||
"description": "ID del grupo",
|
||||
"in": "body",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "groupName",
|
||||
"type": "string",
|
||||
"description": "Nombre del grupo",
|
||||
"in": "body",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/raw/v1/groups/:groupId/devices",
|
||||
"description": "Obtener todos los dispositivos de un grupo",
|
||||
"params": [
|
||||
{
|
||||
"name": "groupId",
|
||||
"type": "integer",
|
||||
"description": "ID del grupo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/raw/v1/groups/:groupId/devices",
|
||||
"description": "Crear un nuevo dispositivo en un grupo",
|
||||
"params": [
|
||||
{
|
||||
"name": "deviceId",
|
||||
"type": "integer",
|
||||
"description": "ID del dispositivo",
|
||||
"in": "body",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "groupId",
|
||||
"type": "integer",
|
||||
"description": "ID del grupo",
|
||||
"in": "body",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "deviceName",
|
||||
"type": "string",
|
||||
"description": "Nombre del dispositivo",
|
||||
"in": "body",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/raw/v1/groups/:groupId/devices/:deviceId",
|
||||
"description": "Obtener un dispositivo de un grupo",
|
||||
"params": [
|
||||
{
|
||||
"name": "groupId",
|
||||
"type": "integer",
|
||||
"description": "ID del grupo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "deviceId",
|
||||
"type": "integer",
|
||||
"description": "ID del dispositivo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"method": "PUT",
|
||||
"path": "/api/raw/v1/groups/:groupId/devices/:deviceId",
|
||||
"description": "Actualizar un dispositivo de un grupo",
|
||||
"params": [
|
||||
{
|
||||
"name": "groupId",
|
||||
"type": "integer",
|
||||
"description": "ID del grupo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "deviceId",
|
||||
"type": "integer",
|
||||
"description": "ID del dispositivo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/raw/v1/groups/:groupId/devices/:deviceId/sensors",
|
||||
"description": "Obtener todos los sensores de un dispositivo",
|
||||
"params": [
|
||||
{
|
||||
"name": "groupId",
|
||||
"type": "integer",
|
||||
"description": "ID del grupo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "deviceId",
|
||||
"type": "integer",
|
||||
"description": "ID del dispositivo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/raw/v1/groups/:groupId/devices/:deviceId/sensors",
|
||||
"description": "Crear un nuevo sensor",
|
||||
"params": [
|
||||
{
|
||||
"name": "sensorId",
|
||||
"type": "integer",
|
||||
"description": "ID del sensor",
|
||||
"in": "body",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "deviceName",
|
||||
"type": "string",
|
||||
"description": "Nombre del dispositivo",
|
||||
"in": "body",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/raw/v1/groups/:groupId/devices/:deviceId/sensors/:sensorId",
|
||||
"description": "Obtener un sensor específico",
|
||||
"params": [
|
||||
{
|
||||
"name": "groupId",
|
||||
"type": "integer",
|
||||
"description": "ID del grupo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "deviceId",
|
||||
"type": "integer",
|
||||
"description": "ID del dispositivo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "sensorId",
|
||||
"type": "integer",
|
||||
"description": "ID del sensor",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"method": "PUT",
|
||||
"path": "/api/raw/v1/groups/:groupId/devices/:deviceId/sensors/:sensorId",
|
||||
"description": "Actualizar un sensor específico",
|
||||
"params": [
|
||||
{
|
||||
"name": "groupId",
|
||||
"type": "integer",
|
||||
"description": "ID del grupo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "deviceId",
|
||||
"type": "integer",
|
||||
"description": "ID del dispositivo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "sensorId",
|
||||
"type": "integer",
|
||||
"description": "ID del sensor",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/raw/v1/groups/:groupId/devices/:deviceId/sensors/:sensorId/values",
|
||||
"description": "Obtener los valores de un sensor",
|
||||
"params": [
|
||||
{
|
||||
"name": "groupId",
|
||||
"type": "integer",
|
||||
"description": "ID del grupo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "deviceId",
|
||||
"type": "integer",
|
||||
"description": "ID del dispositivo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "sensorId",
|
||||
"type": "integer",
|
||||
"description": "ID del sensor",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/raw/v1/v_latest_values",
|
||||
"description": "Vista: últimos valores registrados"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/raw/v1/v_pollution_map",
|
||||
"description": "Vista: mapa de contaminación"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/raw/v1/v_sensor_history_by_device",
|
||||
"description": "Vista: histórico de sensores por dispositivo"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/raw/v1/v_sensor_values",
|
||||
"description": "Vista: valores individuales de sensores"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import GroupView from '@/pages/GroupView.jsx'
|
||||
|
||||
import { Routes, Route } from 'react-router-dom'
|
||||
import ContentWrapper from './components/layout/ContentWrapper'
|
||||
import Docs from './pages/Docs'
|
||||
|
||||
const App = () => {
|
||||
|
||||
@@ -23,6 +24,7 @@ const App = () => {
|
||||
<Route path="/" element={<Groups />} />
|
||||
<Route path="/groups/:groupId" element={<GroupView />} />
|
||||
<Route path="/groups/:groupId/devices/:deviceId" element={<Dashboard />} />
|
||||
<Route path="/docs" element={<Docs url={"/apidoc.json"} />} />
|
||||
</Routes>
|
||||
</ContentWrapper>
|
||||
</>
|
||||
|
||||
74
frontend/src/components/ApiDocs.jsx
Normal file
74
frontend/src/components/ApiDocs.jsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { Accordion } from 'react-bootstrap';
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
|
||||
const ApiDocs = ({ json }) => {
|
||||
if (!json) return <p className="text-muted">No hay documentación disponible.</p>;
|
||||
|
||||
const renderEndpoints = (endpoints) => (
|
||||
<Accordion>
|
||||
{endpoints.map((ep, index) => (
|
||||
<Accordion.Item eventKey={index.toString()} key={index}>
|
||||
<Accordion.Header>
|
||||
<span className={`badge bg-${getMethodColor(ep.method)} me-2 text-uppercase`}>{ep.method}</span>
|
||||
<code>{ep.path}</code>
|
||||
</Accordion.Header>
|
||||
<Accordion.Body>
|
||||
{ep.description && <p className="mb-2">{ep.description}</p>}
|
||||
|
||||
{ep.params?.length > 0 && (
|
||||
<div className="d-flex flex-column gap-2 mt-3">
|
||||
{ep.params.map((param, i) => (
|
||||
<div key={i} className="bg-light border rounded px-3 py-2">
|
||||
<div className="d-flex justify-content-between flex-wrap mb-1">
|
||||
<strong>{param.name}</strong>
|
||||
<span className="badge bg-secondary">{param.in}</span>
|
||||
</div>
|
||||
<div className="small text-muted">
|
||||
<div><strong>Tipo:</strong> {param.type}</div>
|
||||
<div><strong>¿Requerido?:</strong> {param.required ? 'Sí' : 'No'}</div>
|
||||
{param.description && <div><strong>Descripción:</strong> {param.description}</div>}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</Accordion.Body>
|
||||
</Accordion.Item>
|
||||
))}
|
||||
</Accordion>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="container p-4 bg-white rounded-4 border">
|
||||
<h1 className="fw-bold mb-5 text-dark">{json.name} <small className="text-muted fs-5">v{json.version}</small></h1>
|
||||
|
||||
<h3 className="mb-3 text-dark">API de Lógica</h3>
|
||||
{renderEndpoints(json.logic_api)}
|
||||
|
||||
<h3 className="mb-3 text-dark mt-5">API de Datos (Raw)</h3>
|
||||
{renderEndpoints(json.raw_api)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getMethodColor = (method) => {
|
||||
switch (method.toUpperCase()) {
|
||||
case 'GET': return 'success';
|
||||
case 'POST': return 'primary';
|
||||
case 'PUT': return 'warning';
|
||||
case 'DELETE': return 'danger';
|
||||
default: return 'secondary';
|
||||
}
|
||||
};
|
||||
|
||||
ApiDocs.propTypes = {
|
||||
json: PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
version: PropTypes.string.isRequired,
|
||||
logic_api: PropTypes.array,
|
||||
raw_api: PropTypes.array
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
export default ApiDocs;
|
||||
@@ -86,8 +86,8 @@ const SummaryCardsContent = () => {
|
||||
|
||||
|
||||
if (data) {
|
||||
let coData = data[1];
|
||||
let tempData = data[2];
|
||||
let coData = data[2];
|
||||
let tempData = data[1];
|
||||
|
||||
CardsData[0].content = tempData.temperature + "°C";
|
||||
CardsData[0].status = "Temperatura actual";
|
||||
|
||||
34
frontend/src/pages/Docs.jsx
Normal file
34
frontend/src/pages/Docs.jsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import ApiDocs from '@/components/ApiDocs';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
const Docs = ({ url }) => {
|
||||
const [json, setJson] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchDocs = async () => {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
const data = await response.json();
|
||||
setJson(data);
|
||||
} catch (error) {
|
||||
console.error('Error fetching API docs:', error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchDocs();
|
||||
}, [url]);
|
||||
|
||||
return (
|
||||
<ApiDocs json={json} />
|
||||
);
|
||||
}
|
||||
|
||||
Docs.propTypes = {
|
||||
url: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default Docs;
|
||||
@@ -1,13 +0,0 @@
|
||||
import '@/css/Home.css';
|
||||
|
||||
const Home = () => {
|
||||
return (
|
||||
<div className='container mt-5 text-center align-items-center justify-content-center'>
|
||||
<h1 className="display-1 color-animated-bold mb-4">No sé que poner XD</h1>
|
||||
<img className="img-fluid image-animated-bold" src="/images/etsii.gif" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default Home;
|
||||
Reference in New Issue
Block a user