[P2606] WebServer NGINX: plan actualizado, ficha nodo, HTTPS operativo
- P2606: Plan v1.1 EN EJECUCIÓN. Fases 1-3 completadas. - Ficha nodo srvv-nginx-rm.md. P2606 en adn/07_proyectos.md. - Incluye cambios acumulados de sesiones anteriores.
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
module.exports = (pool) => {
|
||||
const router = require('express').Router();
|
||||
|
||||
// GET /api/ambitos — Listar ámbitos
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const { rows } = await pool.query(`
|
||||
SELECT a.*,
|
||||
(SELECT COUNT(*) FROM bitacoras.nodos WHERE ambito_id = a.id) as count_nodos
|
||||
FROM bitacoras.ambitos a
|
||||
ORDER BY a.parent_id NULLS FIRST, a.nombre ASC
|
||||
`);
|
||||
res.json(rows);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/ambitos — Crear ámbito
|
||||
router.post('/', async (req, res) => {
|
||||
const { nombre, descripcion, parent_id, activo } = req.body;
|
||||
try {
|
||||
const { rows } = await pool.query(
|
||||
'INSERT INTO bitacoras.ambitos (nombre, descripcion, parent_id, activo) VALUES ($1, $2, $3, $4) RETURNING *',
|
||||
[nombre, descripcion, parent_id || null, activo ?? true]
|
||||
);
|
||||
res.status(201).json(rows[0]);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/ambitos/estadisticas — Métricas por ámbito
|
||||
router.get('/stats/global', async (req, res) => {
|
||||
try {
|
||||
const { rows } = await pool.query(`
|
||||
SELECT a.nombre, a.id,
|
||||
(SELECT COUNT(*) FROM bitacoras.nodos WHERE ambito_id = a.id) as nodos_count,
|
||||
(SELECT COUNT(*) FROM bitacoras.entradas WHERE ambito_id = a.id) as entradas_count
|
||||
FROM bitacoras.ambitos a
|
||||
WHERE a.activo = true
|
||||
ORDER BY nodos_count DESC
|
||||
`);
|
||||
res.json(rows);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/ambitos/:id — Info de ámbito
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const { rows } = await pool.query(`
|
||||
SELECT a.*, p.nombre as parent_nombre,
|
||||
(SELECT COUNT(*) FROM bitacoras.nodos WHERE ambito_id = a.id) as count_nodos
|
||||
FROM bitacoras.ambitos a
|
||||
LEFT JOIN bitacoras.ambitos p ON a.parent_id = p.id
|
||||
WHERE a.id = $1
|
||||
`, [req.params.id]);
|
||||
if (rows.length === 0) return res.status(404).json({ error: 'Ámbito no encontrado' });
|
||||
res.json(rows[0]);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// PUT /api/ambitos/:id — Actualizar ámbito
|
||||
router.put('/:id', async (req, res) => {
|
||||
const { nombre, descripcion, parent_id, activo } = req.body;
|
||||
try {
|
||||
const { rows } = await pool.query(
|
||||
'UPDATE bitacoras.ambitos SET nombre=$1, descripcion=$2, parent_id=$3, activo=$4 WHERE id=$5 RETURNING *',
|
||||
[nombre, descripcion, parent_id || null, activo ?? true, req.params.id]
|
||||
);
|
||||
if (rows.length === 0) return res.status(404).json({ error: 'Ámbito no encontrado' });
|
||||
res.json(rows[0]);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// DELETE /api/ambitos/:id — Eliminar ámbito
|
||||
router.delete('/:id', async (req, res) => {
|
||||
try {
|
||||
const { rowCount } = await pool.query('DELETE FROM bitacoras.ambitos WHERE id = $1', [req.params.id]);
|
||||
if (rowCount === 0) return res.status(404).json({ error: 'Ámbito no encontrado' });
|
||||
res.json({ message: 'Ámbito eliminado' });
|
||||
} catch (err) {
|
||||
if (err.code === '23503') { // Foreign key constraint violation
|
||||
await pool.query('UPDATE bitacoras.ambitos SET activo=false WHERE id=$1', [req.params.id]);
|
||||
return res.json({ message: 'Ámbito marcado inactivo por poseer nodos/entradas relacionados' });
|
||||
}
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/ambitos/:id/nodos — Nodos del ámbito
|
||||
router.get('/:id/nodos', async (req, res) => {
|
||||
try {
|
||||
const { rows } = await pool.query('SELECT * FROM bitacoras.nodos WHERE ambito_id = $1 ORDER BY nombre', [req.params.id]);
|
||||
res.json(rows);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// GET /api/ambitos/:id/dashboard — Resumen de salud/estado del ámbito
|
||||
router.get('/:id/dashboard', async (req, res) => {
|
||||
const { id } = req.params;
|
||||
try {
|
||||
const ambito = await pool.query('SELECT * FROM bitacoras.ambitos WHERE id = $1', [id]);
|
||||
if (ambito.rows.length === 0) return res.status(404).json({ error: 'Ámbito no encontrado' });
|
||||
|
||||
const nodos = await pool.query('SELECT * FROM bitacoras.nodos WHERE ambito_id = $1 AND activo = true', [id]);
|
||||
const entradasRecientes = await pool.query(`
|
||||
SELECT e.*, n.nombre as nodo_nombre
|
||||
FROM bitacoras.entradas e
|
||||
JOIN bitacoras.nodos n ON e.nodo_id = n.id
|
||||
WHERE n.ambito_id = $1
|
||||
ORDER BY e.inicio DESC LIMIT 10
|
||||
`, [id]);
|
||||
|
||||
const gestionPendiente = await pool.query(`
|
||||
SELECT g.*, n.nombre as nodo_nombre
|
||||
FROM bitacoras.gestion g
|
||||
JOIN bitacoras.nodos n ON g.nodo_id = n.id
|
||||
WHERE n.ambito_id = $1 AND g.activo = true AND g.tipo = 'pendiente'
|
||||
`, [id]);
|
||||
|
||||
res.json({
|
||||
ambito: ambito.rows[0],
|
||||
nodos: nodos.rows,
|
||||
entradas: entradasRecientes.rows,
|
||||
gestion: gestionPendiente.rows
|
||||
});
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
return router;
|
||||
};
|
||||
@@ -59,37 +59,42 @@ module.exports = (pool) => {
|
||||
bitacora = bResult.rows[0];
|
||||
}
|
||||
|
||||
// Obtener entradas con datos del nodo
|
||||
// Obtener entradas con datos del nodo y ámbito
|
||||
const { rows: entradas } = await pool.query(`
|
||||
SELECT e.*, n.nombre as nodo_nombre, n.ip as nodo_ip
|
||||
FROM bitacoras.entradas e
|
||||
LEFT JOIN bitacoras.nodos n ON e.nodo_id = n.id
|
||||
WHERE e.bitacora_id = $1
|
||||
ORDER BY e.inicio DESC
|
||||
`, [bitacora.id]);
|
||||
SELECT e.*, n.nombre as nodo_nombre, n.ip as nodo_ip,
|
||||
COALESCE(a_evt.nombre, a_nodo.nombre) as ambito_nombre
|
||||
FROM bitacoras.entradas e
|
||||
LEFT JOIN bitacoras.nodos n ON e.nodo_id = n.id
|
||||
LEFT JOIN bitacoras.ambitos a_evt ON e.ambito_id = a_evt.id
|
||||
LEFT JOIN bitacoras.ambitos a_nodo ON n.ambito_id = a_nodo.id
|
||||
WHERE e.bitacora_id = $1
|
||||
ORDER BY e.inicio DESC
|
||||
`, [bitacora.id]);
|
||||
|
||||
// Obtener gestión asociada a esta bitácora (fija) + activos globales (rollover)
|
||||
const { rows: gestion } = await pool.query(`
|
||||
SELECT g.*, n.nombre as nodo_nombre
|
||||
FROM bitacoras.gestion g
|
||||
LEFT JOIN bitacoras.nodos n ON g.nodo_id = n.id
|
||||
WHERE g.bitacora_id = $1 OR g.activo = true
|
||||
ORDER BY g.tipo, g.orden
|
||||
`, [bitacora.id]);
|
||||
SELECT g.*, n.nombre as nodo_nombre, a.nombre as ambito_nombre
|
||||
FROM bitacoras.gestion g
|
||||
LEFT JOIN bitacoras.nodos n ON g.nodo_id = n.id
|
||||
LEFT JOIN bitacoras.ambitos a ON n.ambito_id = a.id
|
||||
WHERE g.bitacora_id = $1 OR g.activo = true
|
||||
ORDER BY g.tipo, g.orden
|
||||
`, [bitacora.id]);
|
||||
|
||||
// Obtener resúmenes por nodo
|
||||
const { rows: resúmenes } = await pool.query(`
|
||||
SELECT r.*, n.nombre as nodo_nombre
|
||||
FROM bitacoras.resumen_nodos r
|
||||
JOIN bitacoras.nodos n ON r.nodo_id = n.id
|
||||
WHERE r.bitacora_id = $1
|
||||
`, [bitacora.id]);
|
||||
SELECT r.*, n.nombre as nodo_nombre, a.nombre as ambito_nombre
|
||||
FROM bitacoras.resumen_nodos r
|
||||
JOIN bitacoras.nodos n ON r.nodo_id = n.id
|
||||
LEFT JOIN bitacoras.ambitos a ON n.ambito_id = a.id
|
||||
WHERE r.bitacora_id = $1
|
||||
`, [bitacora.id]);
|
||||
|
||||
// Agrupar por nodo
|
||||
// Agrupar por nodo e incluir el ambito en el agrupador
|
||||
const porNodo = {};
|
||||
entradas.forEach(e => {
|
||||
const nodo = e.nodo_nombre || 'sin-nodo';
|
||||
if (!porNodo[nodo]) porNodo[nodo] = { nodo: nodo, ip: e.nodo_ip, entradas: [], resumen: '' };
|
||||
if (!porNodo[nodo]) porNodo[nodo] = { nodo: nodo, ip: e.nodo_ip, ambito_nombre: e.ambito_nombre, entradas: [], resumen: '' };
|
||||
porNodo[nodo].entradas.push(e);
|
||||
});
|
||||
|
||||
@@ -97,8 +102,9 @@ module.exports = (pool) => {
|
||||
resúmenes.forEach(r => {
|
||||
if (porNodo[r.nodo_nombre]) {
|
||||
porNodo[r.nodo_nombre].resumen = r.resumen;
|
||||
porNodo[r.nodo_nombre].ambito_nombre = r.ambito_nombre || porNodo[r.nodo_nombre].ambito_nombre;
|
||||
} else {
|
||||
porNodo[r.nodo_nombre] = { nodo: r.nodo_nombre, entradas: [], resumen: r.resumen };
|
||||
porNodo[r.nodo_nombre] = { nodo: r.nodo_nombre, ambito_nombre: r.ambito_nombre, entradas: [], resumen: r.resumen };
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = () => {
|
||||
const router = require('express').Router();
|
||||
|
||||
const DOCS_BASE = path.resolve('/app/docs/proy');
|
||||
|
||||
// GET /api/docs/:proyecto/:archivo — Servir .md crudo
|
||||
// Ejemplo: /api/docs/P2601_Dasuten/P2601.09_DASUTEN-sin-DC.md
|
||||
router.get('/:proyecto/:archivo', (req, res) => {
|
||||
const { proyecto, archivo } = req.params;
|
||||
|
||||
// Sanitizar: solo permitir caracteres seguros
|
||||
if (!/^[A-Za-z0-9._-]+$/.test(proyecto) || !/^[A-Za-z0-9._-]+$/.test(archivo)) {
|
||||
return res.status(400).json({ error: 'Nombre de archivo inválido' });
|
||||
}
|
||||
|
||||
// Solo permitir .md
|
||||
if (!archivo.endsWith('.md')) {
|
||||
return res.status(400).json({ error: 'Solo se permiten archivos .md' });
|
||||
}
|
||||
|
||||
const filePath = path.join(DOCS_BASE, proyecto, archivo);
|
||||
|
||||
// Verificar que no escapa del directorio base
|
||||
if (!filePath.startsWith(DOCS_BASE)) {
|
||||
return res.status(403).json({ error: 'Acceso denegado' });
|
||||
}
|
||||
|
||||
// Leer y devolver contenido
|
||||
fs.readFile(filePath, 'utf8', (err, content) => {
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
return res.status(404).json({ error: 'Archivo no encontrado' });
|
||||
}
|
||||
return res.status(500).json({ error: 'Error al leer archivo' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
proyecto,
|
||||
archivo,
|
||||
content,
|
||||
lastModified: fs.statSync(filePath).mtime.toISOString()
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// GET /api/docs — Listar proyectos disponibles
|
||||
router.get('/', (req, res) => {
|
||||
try {
|
||||
const dirs = fs.readdirSync(DOCS_BASE, { withFileTypes: true })
|
||||
.filter(d => d.isDirectory())
|
||||
.map(d => {
|
||||
const files = fs.readdirSync(path.join(DOCS_BASE, d.name))
|
||||
.filter(f => f.endsWith('.md'));
|
||||
return { proyecto: d.name, archivos: files };
|
||||
});
|
||||
res.json(dirs);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: 'Error listando docs' });
|
||||
}
|
||||
});
|
||||
|
||||
return router;
|
||||
};
|
||||
@@ -1,39 +1,77 @@
|
||||
module.exports = (pool) => {
|
||||
const router = require('express').Router();
|
||||
|
||||
// GET /api/entradas — Listar entradas (filtro por bitacora_id o nodo_id)
|
||||
// GET /api/entradas — Listar entradas (filtro por bitacora_id o nodo_id o ambito_id)
|
||||
router.get('/', async (req, res) => {
|
||||
const { bitacora_id, nodo_id } = req.query;
|
||||
let query = 'SELECT e.*, n.nombre as nodo_nombre FROM bitacoras.entradas e LEFT JOIN bitacoras.nodos n ON e.nodo_id = n.id WHERE 1=1';
|
||||
const { bitacora_id, nodo_id, ambito_id } = req.query;
|
||||
let query = `
|
||||
SELECT e.*, n.nombre as nodo_nombre, a.nombre as ambito_nombre
|
||||
FROM bitacoras.entradas e
|
||||
LEFT JOIN bitacoras.nodos n ON e.nodo_id = n.id
|
||||
LEFT JOIN bitacoras.ambitos a ON e.ambito_id = a.id
|
||||
WHERE 1=1
|
||||
`;
|
||||
const params = [];
|
||||
if (bitacora_id) { params.push(bitacora_id); query += ` AND e.bitacora_id = $${params.length}`; }
|
||||
if (nodo_id) { params.push(nodo_id); query += ` AND e.nodo_id = $${params.length}`; }
|
||||
if (ambito_id) { params.push(ambito_id); query += ` AND e.ambito_id = $${params.length}`; }
|
||||
|
||||
query += ' ORDER BY e.inicio DESC';
|
||||
const { rows } = await pool.query(query, params);
|
||||
res.json(rows);
|
||||
try {
|
||||
const { rows } = await pool.query(query, params);
|
||||
res.json(rows);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/entradas — Crear entrada
|
||||
router.post('/', async (req, res) => {
|
||||
const { inicio, fin, descripcion, estado, modo, es_ia, bitacora_id, nodo_id } = req.body;
|
||||
const { rows } = await pool.query(`
|
||||
INSERT INTO bitacoras.entradas (inicio, fin, descripcion, estado, modo, es_ia, bitacora_id, nodo_id)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *
|
||||
`, [inicio, fin || null, descripcion, estado || '⏳', modo || 'P', es_ia || false, bitacora_id, nodo_id]);
|
||||
res.status(201).json(rows[0]);
|
||||
const { inicio, fin, descripcion, estado, modo, es_ia, bitacora_id, nodo_id, ambito_id, doc_path } = req.body;
|
||||
try {
|
||||
const { rows } = await pool.query(`
|
||||
INSERT INTO bitacoras.entradas (inicio, fin, descripcion, estado, modo, es_ia, bitacora_id, nodo_id, ambito_id, doc_path)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING *
|
||||
`, [inicio, fin || null, descripcion, estado || '⏳', modo || 'P', es_ia || false, bitacora_id, nodo_id || null, ambito_id || null, doc_path || null]);
|
||||
res.status(201).json(rows[0]);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// PUT /api/entradas/:id — Actualizar entrada
|
||||
router.put('/:id', async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { inicio, fin, descripcion, estado, modo, es_ia } = req.body;
|
||||
const { rows } = await pool.query(`
|
||||
UPDATE bitacoras.entradas SET inicio=$1, fin=$2, descripcion=$3, estado=$4, modo=$5, es_ia=$6
|
||||
WHERE id=$7 RETURNING *
|
||||
`, [inicio, fin, descripcion, estado, modo, es_ia, id]);
|
||||
|
||||
if (rows.length === 0) return res.status(404).json({ error: 'Entrada no encontrada' });
|
||||
res.json(rows[0]);
|
||||
const { inicio, fin, descripcion, estado, modo, es_ia, nodo_id, ambito_id, doc_path } = req.body;
|
||||
try {
|
||||
// Construir SET dinámico para evitar sobreescribir nulos si no vienen en la petición
|
||||
let updateFields = [];
|
||||
let params = [];
|
||||
let pIdx = 1;
|
||||
|
||||
if (inicio !== undefined) { updateFields.push(`inicio=$${pIdx++}`); params.push(inicio); }
|
||||
if (fin !== undefined) { updateFields.push(`fin=$${pIdx++}`); params.push(fin); }
|
||||
if (descripcion !== undefined) { updateFields.push(`descripcion=$${pIdx++}`); params.push(descripcion); }
|
||||
if (estado !== undefined) { updateFields.push(`estado=$${pIdx++}`); params.push(estado); }
|
||||
if (modo !== undefined) { updateFields.push(`modo=$${pIdx++}`); params.push(modo); }
|
||||
if (es_ia !== undefined) { updateFields.push(`es_ia=$${pIdx++}`); params.push(es_ia); }
|
||||
if (nodo_id !== undefined) { updateFields.push(`nodo_id=$${pIdx++}`); params.push(nodo_id); }
|
||||
if (ambito_id !== undefined) { updateFields.push(`ambito_id=$${pIdx++}`); params.push(ambito_id); }
|
||||
if (doc_path !== undefined) { updateFields.push(`doc_path=$${pIdx++}`); params.push(doc_path); }
|
||||
|
||||
if (updateFields.length === 0) return res.status(400).json({ error: 'No data to update' });
|
||||
|
||||
params.push(id);
|
||||
const { rows } = await pool.query(`
|
||||
UPDATE bitacoras.entradas SET ${updateFields.join(', ')}
|
||||
WHERE id=$${pIdx} RETURNING *
|
||||
`, params);
|
||||
|
||||
if (rows.length === 0) return res.status(404).json({ error: 'Entrada no encontrada' });
|
||||
res.json(rows[0]);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// DELETE /api/entradas/:id — Eliminar entrada
|
||||
|
||||
@@ -2,17 +2,30 @@
|
||||
module.exports = (pool) => {
|
||||
const router = require('express').Router();
|
||||
|
||||
// GET /api/gestion — Listar activos (para rollover) o por bitácora
|
||||
// GET /api/gestion — Listar activos (para rollover) o por bitácora (opcional filtro ámbito)
|
||||
router.get('/', async (req, res) => {
|
||||
const { bitacora_id, activos } = req.query;
|
||||
let query = 'SELECT g.*, n.nombre as nodo_nombre FROM bitacoras.gestion g LEFT JOIN bitacoras.nodos n ON g.nodo_id = n.id';
|
||||
const { bitacora_id, activos, ambito_id } = req.query;
|
||||
let query = `
|
||||
SELECT g.*, n.nombre as nodo_nombre, a.nombre as ambito_nombre
|
||||
FROM bitacoras.gestion g
|
||||
LEFT JOIN bitacoras.nodos n ON g.nodo_id = n.id
|
||||
LEFT JOIN bitacoras.ambitos a ON n.ambito_id = a.id
|
||||
`;
|
||||
const params = [];
|
||||
let whereUsed = false;
|
||||
|
||||
if (activos === 'true') {
|
||||
query += ' WHERE g.activo = true';
|
||||
whereUsed = true;
|
||||
} else if (bitacora_id) {
|
||||
query += ' WHERE g.bitacora_id = $1';
|
||||
params.push(bitacora_id);
|
||||
whereUsed = true;
|
||||
}
|
||||
|
||||
if (ambito_id) {
|
||||
params.push(ambito_id);
|
||||
query += (whereUsed ? ' AND' : ' WHERE') + ` (n.ambito_id = $${params.length})`;
|
||||
}
|
||||
|
||||
query += ' ORDER BY g.tipo, g.orden, g.created_at DESC';
|
||||
|
||||
@@ -3,30 +3,56 @@ module.exports = (pool) => {
|
||||
|
||||
// GET /api/nodos — Listar todos los nodos
|
||||
router.get('/', async (req, res) => {
|
||||
const { rows } = await pool.query('SELECT * FROM bitacoras.nodos WHERE activo = true ORDER BY nombre');
|
||||
res.json(rows);
|
||||
const { ambito_id } = req.query;
|
||||
let query = `
|
||||
SELECT n.*, a.nombre as ambito_nombre
|
||||
FROM bitacoras.nodos n
|
||||
LEFT JOIN bitacoras.ambitos a ON n.ambito_id = a.id
|
||||
WHERE n.activo = true
|
||||
`;
|
||||
const params = [];
|
||||
if (ambito_id) {
|
||||
params.push(ambito_id);
|
||||
query += ` AND n.ambito_id = $${params.length}`;
|
||||
}
|
||||
query += ' ORDER BY n.nombre';
|
||||
|
||||
try {
|
||||
const { rows } = await pool.query(query, params);
|
||||
res.json(rows);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/nodos — Crear nodo
|
||||
router.post('/', async (req, res) => {
|
||||
const { nombre, ip, tipo, descripcion } = req.body;
|
||||
const { rows } = await pool.query(
|
||||
'INSERT INTO bitacoras.nodos (nombre, ip, tipo, descripcion) VALUES ($1, $2, $3, $4) RETURNING *',
|
||||
[nombre, ip, tipo, descripcion]
|
||||
);
|
||||
res.status(201).json(rows[0]);
|
||||
const { nombre, ip, tipo, descripcion, ambito_id } = req.body;
|
||||
try {
|
||||
const { rows } = await pool.query(
|
||||
'INSERT INTO bitacoras.nodos (nombre, ip, tipo, descripcion, ambito_id) VALUES ($1, $2, $3, $4, $5) RETURNING *',
|
||||
[nombre, ip, tipo, descripcion, ambito_id || null]
|
||||
);
|
||||
res.status(201).json(rows[0]);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// PUT /api/nodos/:id — Actualizar nodo
|
||||
router.put('/:id', async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { nombre, ip, tipo, descripcion, activo } = req.body;
|
||||
const { rows } = await pool.query(
|
||||
'UPDATE bitacoras.nodos SET nombre=$1, ip=$2, tipo=$3, descripcion=$4, activo=$5 WHERE id=$6 RETURNING *',
|
||||
[nombre, ip, tipo, descripcion, activo, id]
|
||||
);
|
||||
if (rows.length === 0) return res.status(404).json({ error: 'Nodo no encontrado' });
|
||||
res.json(rows[0]);
|
||||
const { nombre, ip, tipo, descripcion, activo, ambito_id } = req.body;
|
||||
try {
|
||||
const { rows } = await pool.query(
|
||||
'UPDATE bitacoras.nodos SET nombre=$1, ip=$2, tipo=$3, descripcion=$4, activo=$5, ambito_id=$6 WHERE id=$7 RETURNING *',
|
||||
[nombre, ip, tipo, descripcion, activo, ambito_id || null, id]
|
||||
);
|
||||
if (rows.length === 0) return res.status(404).json({ error: 'Nodo no encontrado' });
|
||||
res.json(rows[0]);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// DELETE /api/nodos/:id — Eliminar nodo (lógico o físico si no tiene entradas asociadas)
|
||||
|
||||
@@ -25,12 +25,14 @@ app.get('/health', async (req, res) => {
|
||||
});
|
||||
|
||||
// Routes
|
||||
app.use('/api/ambitos', require('./routes/ambitos')(pool));
|
||||
app.use('/api/nodos', require('./routes/nodos')(pool));
|
||||
app.use('/api/bitacoras', require('./routes/bitacoras')(pool));
|
||||
app.use('/api/entradas', require('./routes/entradas')(pool));
|
||||
app.use('/api/proyectos', require('./routes/proyectos')(pool));
|
||||
app.use('/api/gestion', require('./routes/gestion')(pool));
|
||||
app.use('/api/resumen', require('./routes/resumen')(pool));
|
||||
app.use('/api/docs', require('./routes/docs')());
|
||||
|
||||
// Error handler
|
||||
app.use((err, req, res, next) => {
|
||||
|
||||
Reference in New Issue
Block a user