From b2794204ace9ca6ff9d641388ef68ca9a34980a5 Mon Sep 17 00:00:00 2001 From: Trabajo Date: Wed, 29 Apr 2026 17:22:07 +0200 Subject: [PATCH 1/5] Arreglo de nombres y rutas --- .../{sim-controller.ts => monitor-controller.ts} | 6 +++--- .../{sim-usecases.ts => monitor-usecases.ts} | 2 +- src/index.ts | 8 ++++---- src/infrastructure/monitor-routes-http.ts | 10 ++++++++++ src/infrastructure/sim-routes-http.ts | 10 ---------- 5 files changed, 18 insertions(+), 18 deletions(-) rename src/application/{sim-controller.ts => monitor-controller.ts} (80%) rename src/application/{sim-usecases.ts => monitor-usecases.ts} (98%) create mode 100644 src/infrastructure/monitor-routes-http.ts delete mode 100644 src/infrastructure/sim-routes-http.ts diff --git a/src/application/sim-controller.ts b/src/application/monitor-controller.ts similarity index 80% rename from src/application/sim-controller.ts rename to src/application/monitor-controller.ts index 343da21..e69ecff 100644 --- a/src/application/sim-controller.ts +++ b/src/application/monitor-controller.ts @@ -1,10 +1,10 @@ import { type Request, type Response, type NextFunction } from 'express'; -import { SimUsecases } from './sim-usecases.js'; +import { MonitorUsecases } from './monitor-usecases.js'; import ejs from 'ejs'; import path from 'path'; -export class SimController { - constructor(private usecases: SimUsecases) {} +export class MonitorController { + constructor(private usecases: MonitorUsecases) {} renderDashboard = async (_req: Request, res: Response, next: NextFunction) => { try { diff --git a/src/application/sim-usecases.ts b/src/application/monitor-usecases.ts similarity index 98% rename from src/application/sim-usecases.ts rename to src/application/monitor-usecases.ts index 936cd0f..e098c22 100644 --- a/src/application/sim-usecases.ts +++ b/src/application/monitor-usecases.ts @@ -2,7 +2,7 @@ import axios from 'axios'; import { type Pool } from 'pg'; import { type Proyecto, RepositoryError } from '../domain/common.js'; -export class SimUsecases { +export class MonitorUsecases { constructor(private db: Pool) {} async checkAllProjectsHealth(): Promise { diff --git a/src/index.ts b/src/index.ts index 55fd278..88da15e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,8 @@ import express from "express"; import type { Request, Response, NextFunction } from "express"; import path from "path"; -import { createRouter } from "./infrastructure/sim-routes-http.js"; -import { SimUsecases } from "./application/sim-usecases.js"; +import { createRouter } from "./infrastructure/monitor-routes-http.js"; +import { MonitorUsecases } from "./application/monitor-usecases.js"; import { pool } from "./config/postgreConfig.js"; import { AppError } from "./domain/common.js"; @@ -14,7 +14,7 @@ app.use(express.json()); app.set("view engine", "ejs"); app.set("views", path.join(process.cwd(), "src/views")); -const monitorJob = new SimUsecases(pool); +const monitorJob = new MonitorUsecases(pool); app.use("/", createRouter(monitorJob)); @@ -49,4 +49,4 @@ app.use((err: Error, req: Request, res: Response, _next: NextFunction) => { app.listen(port, () => { console.log(`Server is running on port ${port}`); -}); \ No newline at end of file +}); diff --git a/src/infrastructure/monitor-routes-http.ts b/src/infrastructure/monitor-routes-http.ts new file mode 100644 index 0000000..2ff66f6 --- /dev/null +++ b/src/infrastructure/monitor-routes-http.ts @@ -0,0 +1,10 @@ +import { Router } from 'express'; +import { MonitorController } from '../application/monitor-controller.js'; +import { MonitorUsecases } from '../application/monitor-usecases.js'; + +export function createRouter(usecases: MonitorUsecases): Router { + const router = Router(); + const controller = new MonitorController(usecases); + router.get('/', controller.renderDashboard); + return router; +} diff --git a/src/infrastructure/sim-routes-http.ts b/src/infrastructure/sim-routes-http.ts deleted file mode 100644 index 1764b80..0000000 --- a/src/infrastructure/sim-routes-http.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Router } from 'express'; -import { SimController } from '../application/sim-controller.js'; -import { SimUsecases } from '../application/sim-usecases.js'; - -export function createRouter(usecases: SimUsecases): Router { - const router = Router(); - const controller = new SimController(usecases); - router.get('/', controller.renderDashboard); - return router; -} \ No newline at end of file From 615fe7f2c87a1faddf1090b9720e4308dd0ac66c Mon Sep 17 00:00:00 2001 From: Trabajo Date: Tue, 5 May 2026 09:39:12 +0200 Subject: [PATCH 2/5] =?UTF-8?q?Configuraci=C3=B3n=20del=20proyecto=20y=20s?= =?UTF-8?q?alida=20de=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 ++++ README.md | 23 +++++++++++------------ package.json | 1 + tsconfig.json | 5 +++-- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 9fe585b..0ab4d18 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# TypeScript build output +dist/ + # ---> Node # Logs logs @@ -142,3 +145,4 @@ dist # JetBrains IDE local state .idea/ + diff --git a/README.md b/README.md index 16b7d6d..9304e93 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# sf-monitorizacion-health +# sf-monitorizacion-health Aplicación Node.js/Express para monitorizar el endpoint de salud (`url_health`) de un conjunto de proyectos y mostrar un dashboard con el **último estado** registrado en PostgreSQL. @@ -12,16 +12,17 @@ Aplicación Node.js/Express para monitorizar el endpoint de salud (`url_health`) - `TIMEOUT` si el error es `ECONNABORTED` 4. El dashboard (`GET /`) muestra, por proyecto, el **último** registro de `estados`. -El job se ejecuta de forma periódica (cada 60s) en bucle recursivo para evitar solapamientos. +El job se ejecuta de forma periódica (configurable desde la interfaz, por defecto 5 min) en bucle recursivo para evitar solapamientos. ## Requisitos - Node.js 18+ (recomendado) +- TypeScript - PostgreSQL ## Configuración -La app lee estas variables de entorno (consultadas desde `src/config/postgreConfig.ts`): +La app lee estas variables de entorno (consultadas desde `src/config/postgres-config.ts`): - `DB_HOST` - `DB_PORT` @@ -43,27 +44,26 @@ DB_NAME=sf_monitorizacion_health La migración `deployment/database/migrations/001_init.sql` crea: -- `proyectos` -- tipo ENUM `tipo_estado` con `OK`, `ERROR`, `TIMEOUT` +- `proyectos` (con restricción UNIQUE en nombre y URL, y límite de 100 caracteres para el nombre) - `estados` -- índice para consultar rápido el último estado por proyecto -> Nota: la migración hace `DROP TABLE IF EXISTS`, así que destruye los datos actuales. +> Nota: La migración utiliza `CREATE TABLE IF NOT EXISTS`. ## Cómo ejecutar 1. Instala dependencias: - `npm install` 2. Asegúrate de tener creada la BD (ejecuta `deployment/database/migrations/001_init.sql`). -3. Ejecuta en modo desarrollo: - - `npm run dev` +3. Ejecuta los scripts disponibles: + - `npm run dev`: Inicia en modo desarrollo con `tsx`. + - `npm run build`: Compila el código TypeScript. La app levanta el servidor en `http://localhost:3000`. ## Dashboard Ruta: -- `GET /` : renderiza `src/views/index.ejs` +- `GET /` : renderiza la vista principal. En la tabla se muestra: - Nombre del proyecto (`proyectos.nombre`) @@ -75,7 +75,7 @@ Si un proyecto todavía no tiene estados registrados, el dashboard muestra `PEND ## Añadir proyectos -Inserta en `proyectos`: +Se pueden añadir desde la interfaz en `/nuevo` o mediante SQL: ```sql INSERT INTO proyectos (nombre, url_health) @@ -83,4 +83,3 @@ VALUES ('Mi proyecto', 'https://ejemplo.com/health'); ``` Los estados empezarán a generarse automáticamente cuando arranque el servidor y empiece el ciclo de checks. - diff --git a/package.json b/package.json index c4d2d94..2815a6f 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ }, "scripts": { "dev": "tsx src/index.ts", + "build": "tsc", "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { diff --git a/tsconfig.json b/tsconfig.json index dda4b2c..6e15b92 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,8 +2,8 @@ // Visit https://aka.ms/tsconfig to read more about this file "compilerOptions": { // File Layout - // "rootDir": "./src", - // "outDir": "./dist", + "rootDir": "./src", + "outDir": "./dist", // Environment Settings // See also https://aka.ms/tsconfig/module "module": "nodenext", @@ -37,3 +37,4 @@ "skipLibCheck": true, } } + From 24805819933133212dd7360b16d38983b6b375d3 Mon Sep 17 00:00:00 2001 From: Trabajo Date: Tue, 5 May 2026 09:47:51 +0200 Subject: [PATCH 3/5] Renombrado de configuracion postgres y limpieza --- src/config/{postgreConfig.ts => postgres-config.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/config/{postgreConfig.ts => postgres-config.ts} (100%) diff --git a/src/config/postgreConfig.ts b/src/config/postgres-config.ts similarity index 100% rename from src/config/postgreConfig.ts rename to src/config/postgres-config.ts From 818a1899b018efde40678bf7a67be708925617a3 Mon Sep 17 00:00:00 2001 From: Trabajo Date: Tue, 5 May 2026 09:49:23 +0200 Subject: [PATCH 4/5] =?UTF-8?q?Implementaci=C3=B3n=20de=20logica=20de=20mo?= =?UTF-8?q?nitorizaci=C3=B3n=20y=20scheduler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/application/monitor-scheduler.ts | 53 +++++++++++++++ src/application/monitor-usecases.ts | 98 +++++++++++++++++----------- src/repository/monitor-repository.ts | 83 +++++++++++++++++++++++ 3 files changed, 195 insertions(+), 39 deletions(-) create mode 100644 src/application/monitor-scheduler.ts create mode 100644 src/repository/monitor-repository.ts diff --git a/src/application/monitor-scheduler.ts b/src/application/monitor-scheduler.ts new file mode 100644 index 0000000..b394d52 --- /dev/null +++ b/src/application/monitor-scheduler.ts @@ -0,0 +1,53 @@ +import { AppError } from '../domain/common.js'; +import { MonitorUsecases } from './monitor-usecases.js'; + +export class MonitorScheduler { + private timeout?: ReturnType | undefined; + + constructor( + private usecases: MonitorUsecases, + private intervalMs: number + ) {} + + start(): void { + void this.runAndScheduleNext(); + } + + getIntervalSeconds(): number { + return Math.floor(this.intervalMs / 1000); + } + + setIntervalSeconds(seconds: number): void { + if (!Number.isInteger(seconds) || seconds < 10 || seconds > 3600) { + throw new AppError('El intervalo debe estar entre 10 y 3600 segundos', 400); + } + + this.intervalMs = seconds * 1000; + this.scheduleNext(); + } + + private scheduleNext(): void { + if (this.timeout) { + clearTimeout(this.timeout); + } + + this.timeout = setTimeout(() => { + void this.runAndScheduleNext(); + }, this.intervalMs); + } + + private async runAndScheduleNext(): Promise { + if (this.timeout) { + clearTimeout(this.timeout); + this.timeout = undefined; + } + + try { + await this.usecases.checkAllProjectsHealth(); + } catch (err) { + console.error(`[Background Job Error]: ${err instanceof Error ? err.message : String(err)}`); + } finally { + this.scheduleNext(); + } + } +} diff --git a/src/application/monitor-usecases.ts b/src/application/monitor-usecases.ts index e098c22..c424546 100644 --- a/src/application/monitor-usecases.ts +++ b/src/application/monitor-usecases.ts @@ -1,57 +1,77 @@ import axios from 'axios'; -import { type Pool } from 'pg'; -import { type Proyecto, RepositoryError } from '../domain/common.js'; +import { AppError, type Proyecto, RepositoryError } from '../domain/common.js'; +import { MonitorRepository } from '../repository/monitor-repository.js'; export class MonitorUsecases { - constructor(private db: Pool) {} + constructor(private repository: MonitorRepository) {} + + private async checkProjectAndSaveStatus(project: Proyecto): Promise { + let status: Proyecto['estado_codigo'] = 'OK'; + try { + await axios.get(project.url_health, { timeout: 5000, validateStatus: (s) => s === 200 }); + } catch (e) { + const axiosError = e as { code?: string }; + status = axiosError.code === 'ECONNABORTED' ? 'TIMEOUT' : 'ERROR'; + } + + await this.repository.saveProjectStatus(project.id, status); + } async checkAllProjectsHealth(): Promise { try { - const { rows: projects } = await this.db.query('SELECT * FROM proyectos'); + const projects = await this.repository.getProjects(); + const checks = projects.map((project) => this.checkProjectAndSaveStatus(project)); - const checks = projects.map(async (p) => { - let status: Proyecto['estado_codigo'] = 'OK'; - try { - // Solo 200 es válido: cualquier otro código (301, 403, 5xx) indica un problema real del servicio. - await axios.get(p.url_health, { timeout: 5000, validateStatus: (s) => s === 200 }); - } catch (e) { - const axiosError = e as { code?: string }; - status = axiosError.code === 'ECONNABORTED' ? 'TIMEOUT' : 'ERROR'; - } - - return this.db.query( - 'INSERT INTO estados (proyecto_id, estado_codigo, fecha) VALUES ($1, $2, NOW())', - [p.id, status] - ); - }); - - // allSettled en lugar de all: un fallo al insertar en BD no debe cancelar el resto de checks. await Promise.allSettled(checks); } catch (err) { - throw new RepositoryError('Fallo masivo en el chequeo de salud', { cause: err instanceof Error ? err.message : String(err) }); + throw new RepositoryError('Fallo masivo en el chequeo de salud', { + cause: err instanceof Error ? err.message : String(err) + }); } } async getDashboardData(): Promise { - // LEFT JOIN LATERAL nos deja calcular "la última fila de estados" para cada proyecto. - // Usamos LEFT JOIN para no perder proyectos que todavía no tienen estados registrados. - const query = ` - SELECT p.id, p.nombre, p.url_health, e.estado_codigo, e.fecha - FROM proyectos p - LEFT JOIN LATERAL ( - SELECT estado_codigo, fecha - FROM estados - WHERE proyecto_id = p.id - ORDER BY fecha DESC - LIMIT 1 - ) e ON true - ORDER BY p.nombre ASC; - `; try { - const { rows } = await this.db.query(query); - return rows; + return await this.repository.getDashboardData(); } catch (err) { - throw new RepositoryError('Error al recuperar datos del dashboard', { cause: err instanceof Error ? err.message : String(err) }); + throw new RepositoryError('Error al recuperar datos del dashboard', { + cause: err instanceof Error ? err.message : String(err) + }); } } + + async createNewProject(nombre: string, urlHealth: string): Promise { + const trimmedNombre = typeof nombre === 'string' ? nombre.trim() : ''; + const trimmedUrl = typeof urlHealth === 'string' ? urlHealth.trim() : ''; + + if (trimmedNombre === '') { + throw new AppError('El nombre del proyecto es obligatorio', 400); + } + + if (trimmedNombre.length > 100) { + throw new AppError('El nombre del proyecto no puede superar los 100 caracteres', 400); + } + + if (trimmedUrl === '') { + throw new AppError('La URL de health check es obligatoria', 400); + } + + try { + new URL(trimmedUrl); + } catch { + throw new AppError('La URL de health check no es valida', 400); + } + + await this.repository.addProject(trimmedNombre, trimmedUrl); + } + + async checkProjectHealth(projectId: number): Promise { + const project = await this.repository.getProjectById(projectId); + if (!project) { + throw new AppError('Proyecto no encontrado', 404); + } + + await this.checkProjectAndSaveStatus(project); + } } + diff --git a/src/repository/monitor-repository.ts b/src/repository/monitor-repository.ts new file mode 100644 index 0000000..b5d7f29 --- /dev/null +++ b/src/repository/monitor-repository.ts @@ -0,0 +1,83 @@ +import { type Pool } from 'pg'; +import { type Proyecto, RepositoryError } from '../domain/common.js'; + +export class MonitorRepository { + constructor(private db: Pool) {} + + async getProjects(): Promise { + try { + const { rows } = await this.db.query('SELECT * FROM proyectos'); + return rows; + } catch (err) { + throw new RepositoryError('Error al recuperar proyectos', { + cause: err instanceof Error ? err.message : String(err) + }); + } + } + + async getProjectById(projectId: number): Promise { + try { + const { rows } = await this.db.query( + 'SELECT * FROM proyectos WHERE id = $1', + [projectId] + ); + return rows[0]; + } catch (err) { + throw new RepositoryError('Error al recuperar el proyecto', { + projectId, + cause: err instanceof Error ? err.message : String(err) + }); + } + } + + async saveProjectStatus(proyectoId: number, status: Proyecto['estado_codigo']): Promise { + try { + await this.db.query( + 'INSERT INTO estados (proyecto_id, estado_codigo, fecha) VALUES ($1, $2, NOW())', + [proyectoId, status] + ); + } catch (err) { + throw new RepositoryError('Error al guardar estado del proyecto', { + proyectoId, + cause: err instanceof Error ? err.message : String(err) + }); + } + } + + async getDashboardData(): Promise { + const query = ` + SELECT p.id, p.nombre, p.url_health, e.estado_codigo, e.fecha + FROM proyectos p + LEFT JOIN LATERAL ( + SELECT estado_codigo, fecha + FROM estados + WHERE proyecto_id = p.id + ORDER BY fecha DESC + LIMIT 1 + ) e ON true + ORDER BY p.nombre ASC; + `; + + try { + const { rows } = await this.db.query(query); + return rows; + } catch (err) { + throw new RepositoryError('Error al recuperar datos del dashboard', { + cause: err instanceof Error ? err.message : String(err) + }); + } + } + + async addProject(nombre: string, url_health: string): Promise { + try { + await this.db.query( + 'INSERT INTO proyectos (nombre, url_health) VALUES ($1, $2)', + [nombre.trim(), url_health.trim()] + ); + } catch (err) { + throw new RepositoryError('Error al crear el proyecto', { + cause: err instanceof Error ? err.message : String(err) + }); + } + } +} From d8522b49af535f6ddc1e96c7222f8f76304bb236 Mon Sep 17 00:00:00 2001 From: Trabajo Date: Tue, 5 May 2026 10:05:21 +0200 Subject: [PATCH 5/5] Controladores rutas y vistas del dashboard --- src/application/monitor-controller.ts | 67 ++++++- src/index.ts | 25 ++- src/infrastructure/monitor-routes-http.ts | 12 +- src/views/add-project.ejs | 33 ++++ src/views/error.ejs | 2 +- src/views/index.ejs | 207 ++++++++++++++++++---- 6 files changed, 286 insertions(+), 60 deletions(-) create mode 100644 src/views/add-project.ejs diff --git a/src/application/monitor-controller.ts b/src/application/monitor-controller.ts index e69ecff..cc7de57 100644 --- a/src/application/monitor-controller.ts +++ b/src/application/monitor-controller.ts @@ -1,21 +1,74 @@ import { type Request, type Response, type NextFunction } from 'express'; +import { MonitorScheduler } from './monitor-scheduler.js'; import { MonitorUsecases } from './monitor-usecases.js'; -import ejs from 'ejs'; -import path from 'path'; +import { AppError } from '../domain/common.js'; export class MonitorController { - constructor(private usecases: MonitorUsecases) {} + constructor( + private usecases: MonitorUsecases, + private scheduler: MonitorScheduler + ) {} renderDashboard = async (_req: Request, res: Response, next: NextFunction) => { try { const proyectos = await this.usecases.getDashboardData(); - const filePath = path.join(process.cwd(), 'src/views/index.ejs'); - ejs.renderFile(filePath, { proyectos }, (err, str) => { - if (err) return next(err); - res.send(str); + res.render('index', { + proyectos, + monitorIntervalSeconds: this.scheduler.getIntervalSeconds() }); } catch (err) { next(err); } } + + renderAddProject = async (_req: Request, res: Response, next: NextFunction) => { + try { + res.render('add-project'); + } catch (err) { + next(err); + } + } + + saveProject = async (req: Request, res: Response, next: NextFunction) => { + try { + const { nombre, url_health } = req.body; + await this.usecases.createNewProject(nombre, url_health); + res.redirect('/'); + } catch (err) { + next(err); + } + } + + checkAllProjects = async (_req: Request, res: Response, next: NextFunction) => { + try { + await this.usecases.checkAllProjectsHealth(); + res.sendStatus(204); + } catch (err) { + next(err); + } + } + + checkProject = async (req: Request, res: Response, next: NextFunction) => { + try { + const projectId = Number(req.params['id']); + if (!Number.isInteger(projectId) || projectId <= 0) { + throw new AppError('Id de proyecto invalido', 400); + } + + await this.usecases.checkProjectHealth(projectId); + res.sendStatus(204); + } catch (err) { + next(err); + } + } + + updateMonitorInterval = async (req: Request, res: Response, next: NextFunction) => { + try { + const seconds = Number(req.body.seconds); + this.scheduler.setIntervalSeconds(seconds); + res.json({ seconds: this.scheduler.getIntervalSeconds() }); + } catch (err) { + next(err); + } + } } diff --git a/src/index.ts b/src/index.ts index 88da15e..fcb64d2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,33 +2,28 @@ import express from "express"; import type { Request, Response, NextFunction } from "express"; import path from "path"; import { createRouter } from "./infrastructure/monitor-routes-http.js"; +import { MonitorScheduler } from "./application/monitor-scheduler.js"; import { MonitorUsecases } from "./application/monitor-usecases.js"; -import { pool } from "./config/postgreConfig.js"; +import { MonitorRepository } from "./repository/monitor-repository.js"; +import { pool } from "./config/postgres-config.js"; import { AppError } from "./domain/common.js"; const app = express(); -const port = 3000; +const port = Number(process.env.PORT) || 3000; app.use(express.json()); +app.use(express.urlencoded({ extended: false })); app.set("view engine", "ejs"); app.set("views", path.join(process.cwd(), "src/views")); -const monitorJob = new MonitorUsecases(pool); +const monitorRepository = new MonitorRepository(pool); +const monitorJob = new MonitorUsecases(monitorRepository); +const monitorScheduler = new MonitorScheduler(monitorJob, 300000); -app.use("/", createRouter(monitorJob)); +app.use("/", createRouter(monitorJob, monitorScheduler)); -const poll = async () => { - try { - await monitorJob.checkAllProjectsHealth(); - } catch (err) { - console.error(`[Background Job Error]: ${err instanceof Error ? err.message : String(err)}`); - } finally { - setTimeout(poll, 300000); - } -}; - -void poll(); +monitorScheduler.start(); app.use((err: Error, req: Request, res: Response, _next: NextFunction) => { const status = err instanceof AppError ? err.status : 500; diff --git a/src/infrastructure/monitor-routes-http.ts b/src/infrastructure/monitor-routes-http.ts index 2ff66f6..bfdc44c 100644 --- a/src/infrastructure/monitor-routes-http.ts +++ b/src/infrastructure/monitor-routes-http.ts @@ -1,10 +1,18 @@ import { Router } from 'express'; import { MonitorController } from '../application/monitor-controller.js'; +import { MonitorScheduler } from '../application/monitor-scheduler.js'; import { MonitorUsecases } from '../application/monitor-usecases.js'; -export function createRouter(usecases: MonitorUsecases): Router { +export function createRouter(usecases: MonitorUsecases, scheduler: MonitorScheduler): Router { const router = Router(); - const controller = new MonitorController(usecases); + const controller = new MonitorController(usecases, scheduler); + router.get('/', controller.renderDashboard); + router.get('/nuevo', controller.renderAddProject); + router.post('/proyectos', controller.saveProject); + router.post('/check-all', controller.checkAllProjects); + router.post('/check/:id', controller.checkProject); + router.post('/monitor-interval', controller.updateMonitorInterval); + return router; } diff --git a/src/views/add-project.ejs b/src/views/add-project.ejs new file mode 100644 index 0000000..da94afb --- /dev/null +++ b/src/views/add-project.ejs @@ -0,0 +1,33 @@ + + + + + Nuevo Proyecto + + + +
+

Añadir Proyecto

+
+ + + + + + +
+ Volver + +
+
+
+ + \ No newline at end of file diff --git a/src/views/error.ejs b/src/views/error.ejs index 3a180a1..2785ad8 100644 --- a/src/views/error.ejs +++ b/src/views/error.ejs @@ -11,7 +11,7 @@ - <%# Vista genérica para captura de rrores mediante el middleware de Express %> + <%# Vista genérica para captura de errores mediante el middleware de Express %>

Error

<%= message %>

Volver al inicio diff --git a/src/views/index.ejs b/src/views/index.ejs index 028e84b..af1db89 100644 --- a/src/views/index.ejs +++ b/src/views/index.ejs @@ -5,52 +5,189 @@ Monitor de Salud -

Monitor de Salud

- - - - - - - - - - - <%# Iteración de la lista de proyectos inyectada de desde el controlador. %> - <% proyectos.forEach(function(p) { %> - - - - <%# La clase CSS coincide con el estado_codigo para aplicar el color correspondiente %> - - - - <% }) %> - -
ProyectoURLEstadoÚltima comprobación
<%= p.nombre %> - - <%= p.url_health %> - - <%= p.estado_codigo || 'PENDIENTE' %> - - <%# Formateo de fecha en servidor antes de renderizar %> - <%= p.fecha ? new Date(p.fecha).toLocaleString('es-ES') : '—' %> -
+

Monitor de Salud

+ +
+ + +
+ + + segundos + + +
+
+ + + + + + + + + + + + + <% proyectos.forEach(function(p) { %> + + + + + + + + <% }) %> + +
ProyectoURLEstadoUltima comprobacionAcciones
<%= p.nombre %> + + <%= p.url_health %> + + + <%= p.estado_codigo || 'PENDIENTE' %> + + <%= p.fecha ? new Date(p.fecha).toLocaleString('es-ES') : '-' %> + + +
+ +