From f90b5bab4bcaa0208f7821c696f75b3b172f6b8f Mon Sep 17 00:00:00 2001 From: Fernando Canchola Cruz Date: Wed, 23 Jul 2025 12:36:07 -0600 Subject: [PATCH 01/13] =?UTF-8?q?Agrega=20traducci=C3=B3n=20al=20espa?= =?UTF-8?q?=C3=B1ol=20del=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.es.md | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 README.es.md diff --git a/README.es.md b/README.es.md new file mode 100644 index 000000000..a13ca6a75 --- /dev/null +++ b/README.es.md @@ -0,0 +1,149 @@ +

bluewave-labs%2Fcheckmate | Trendshift

+ +![](https://img.shields.io/github/license/bluewave-labs/checkmate) +![](https://img.shields.io/github/repo-size/bluewave-labs/checkmate) +![](https://img.shields.io/github/commit-activity/m/bluewave-labs/checkmate) +![](https://img.shields.io/github/last-commit/bluewave-labs/checkmate) +![](https://img.shields.io/github/languages/top/bluewave-labs/checkmate) +![](https://img.shields.io/github/issues/bluewave-labs/checkmate) +![](https://img.shields.io/github/issues-pr/bluewave-labs/checkmate) +[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/9901/badge)](https://www.bestpractices.dev/projects/9901) +[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/bluewave-labs/checkmate) + +

Checkmate

+ +

Una aplicación de monitoreo de infraestructura y disponibilidad de código abierto

+ +image + +Este repositorio contiene tanto el frontend como el backend de Checkmate, una herramienta de monitoreo de código abierto y autoalojada para rastrear hardware de servidores, tiempo de actividad, tiempos de respuesta e incidentes en tiempo real con visualizaciones atractivas. Checkmate verifica regularmente si un servidor/sitio web es accesible y funciona de manera óptima, proporcionando alertas y reportes en tiempo real sobre disponibilidad, tiempo de inactividad y respuesta de los servicios monitoreados. + +Checkmate también tiene un agente llamado [Capture](https://github.com/bluewave-labs/capture) para obtener datos de servidores remotos. Aunque no es obligatorio para ejecutar Checkmate, proporciona información adicional sobre el estado del CPU, RAM, disco y temperatura de tus servidores. + +Checkmate ha sido probado con más de 1000 monitores activos sin problemas significativos ni cuellos de botella en el rendimiento. + +**Si deseas patrocinar una función, [consulta este enlace](https://checkmate.so/sponsored-features).** + +## 📚 Tabla de contenidos + +- [📦 Demo](#-demo) +- [🔗 Guía del usuario](#-guía-del-usuario) +- [🛠️ Instalación](#️-instalación) +- [🚀 Despliegue con Helm](#-despliegue-con-helm) +- [🏁 Traducciones](#-traducciones) +- [🚀 Rendimiento](#-rendimiento) +- [💚 Preguntas e ideas](#-preguntas-e-ideas) +- [🧩 Funcionalidades](#-funcionalidades) +- [🏗️ Capturas de pantalla](#-capturas-de-pantalla) +- [🏗️ Tecnología utilizada](#-tecnología-utilizada) +- [🔗 Enlaces útiles](#enlaces-útiles) +- [🤝 Cómo contribuir](#-cómo-contribuir) +- [💰 Patrocinadores](#-patrocinadores) + +## 📦 Demo + +Puedes ver la última versión de [Checkmate](https://checkmate-demo.bluewavelabs.ca/) en acción. Usuario: `uptimedemo@demo.com`, contraseña: `Demouser1!`. (Actualizamos el servidor demo periódicamente. Si no funciona, contáctanos en el canal de Discussions). + +## 🔗 Guía del usuario + +Instrucciones de uso disponibles [aquí](https://docs.checkmate.so/checkmate-2.1). Aún está en desarrollo, pero se actualiza constantemente. + +## 🛠️ Instalación + +Consulta las instrucciones de instalación en el [portal de documentación de Checkmate](https://docs.checkmate.so/checkmate-2.1/users-guide/quickstart). + +También puedes usar [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md) o [Pikapods](https://www.pikapods.com/) para desplegar rápidamente una instancia. Si quieres monitorear tu infraestructura, necesitarás el agente [Capture](https://github.com/bluewave-labs/capture). + +## 🏁 Traducciones + +Si deseas usar Checkmate en otro idioma, [únete a este proyecto en Poeditor](https://poeditor.com/join/project/lRUoGZFCsJ) para colaborar con las traducciones. + +## 🚀 Rendimiento + +Gracias a optimizaciones avanzadas, Checkmate tiene un consumo de memoria muy bajo. Aquí el uso de memoria monitoreando 323 servidores cada minuto: + +![image](https://github.com/user-attachments/assets/37e04a75-d83a-488f-b25c-025511b492c9) + +Uso de MongoDB y Redis en el mismo servidor (398Mb y 15Mb): + +![image](https://github.com/user-attachments/assets/3b469e85-e675-4040-a162-3f24c1afc751) + +## 💚 Preguntas e ideas + +Puedes contactarnos por: + +- [Canal de Discord](https://discord.gg/NAb6H3UTjK) +- [Discusiones en GitHub](https://github.com/bluewave-labs/bluewave-uptime/discussions) +- [Grupo en Reddit](https://www.reddit.com/r/CheckmateMonitoring/) + +¡Tus ideas son bienvenidas! + +## 🧩 Funcionalidades + +- Código abierto, autoalojable (Raspberry Pi incluido) +- Monitoreo de sitios web, puertos, ping, SSL +- Velocidad de página +- Monitoreo de infraestructura (requiere agente Capture) +- Monitoreo Docker +- Incidentes y mantenimiento programado +- Notificaciones (email, Discord, Slack, Telegram, Webhooks) +- Consulta JSON +- Multilenguaje + +**Próximas funciones (Milestone 2.2):** +- Mejores notificaciones +- Monitoreo de red +- Otras mejoras + +## 🏗️ Capturas de pantalla + +(Ver imágenes en el repo original) + +## 🏗️ Tecnología utilizada + +- ReactJs +- MUI +- Node.js +- MongoDB +- Recharts +- Y muchos componentes de código abierto + +## Enlaces útiles + +- ⭐ Dale una estrella si te gusta el proyecto +- 📌 Participa en [Discussions](https://github.com/bluewave-labs/checkmate/discussions) +- 📥 Usa [NewReleases](https://newreleases.io/) para recibir actualizaciones +- 🎥 Mira un [video de instalación](https://www.youtube.com/watch?v=GfFOc0xHIwY) + +## 🤝 Cómo contribuir + +Somos [Alex](http://github.com/ajhollid), [Vishnu](http://github.com/vishnusn77), [Mohadeseh](http://github.com/mohicody), [Gorkem](http://github.com/gorkem-bwl/), [Owaise](http://github.com/Owaiseimdad), [Aryaman](https://github.com/Br0wnHammer) y [Mert](https://github.com/mertssmnoglu). + +Checkmate tiene más de 7000 estrellas y 90+ contribuidores globales. Está marcado por empleados de Google, Microsoft, Intel, Cisco, etc. + +Pasos para contribuir: + +1. ⭐ Dale estrella al repositorio +2. Lee la [guía de contribución](https://github.com/bluewave-labs/Checkmate/blob/develop/CONTRIBUTING.md) +3. Revisa la [estructura del proyecto](https://docs.checkmate.so/checkmate-2.1/developers-guide/general-project-structure) +4. Aprende más con [DeepWiki](https://deepwiki.com/bluewave-labs/Checkmate) +5. Abre un issue si encuentras errores +6. Mira las tareas con `good-first-issue` +7. Envía un PR con mejoras o nuevas funciones + +![Contribuidores](https://contrib.rocks/image?repo=bluewave-labs/checkmate) + +[![Historial de estrellas](https://api.star-history.com/svg?repos=bluewave-labs/checkmate&type=Date)](https://star-history.com/#bluewave-labs/bluewave-uptime&Date) + +## 💰 Patrocinadores + +Gracias a [Gitbook](https://gitbook.io/) y [Poeditor](https://poeditor.com/) por sus cuentas gratuitas. + +¿Quieres patrocinar? Escríbenos a hello@bluewavelabs.ca +También puedes [ver funciones patrocinadas](https://checkmate.so/sponsored-features) + +Otros proyectos: + +- [VerifyWise](https://github.com/bluewave-labs/verifywise): Gobernanza de IA +- [DataRoom](https://github.com/bluewave-labs/bluewave-dataroom): Compartir archivos seguro +- [Guidefox](https://github.com/bluewave-labs/guidefox): Onboarding con tours y ayudas visuales \ No newline at end of file From 35270e2761d0e904b18c882901a8f076e846409e Mon Sep 17 00:00:00 2001 From: Fernando Canchola Cruz Date: Thu, 24 Jul 2025 11:38:45 -0600 Subject: [PATCH 02/13] Update README.es.md Fixed readme.es.md --- README.es.md | 179 +++++++++++++++++++++++++++++---------------------- 1 file changed, 101 insertions(+), 78 deletions(-) diff --git a/README.es.md b/README.es.md index a13ca6a75..21909b2ed 100644 --- a/README.es.md +++ b/README.es.md @@ -7,143 +7,166 @@ ![](https://img.shields.io/github/languages/top/bluewave-labs/checkmate) ![](https://img.shields.io/github/issues/bluewave-labs/checkmate) ![](https://img.shields.io/github/issues-pr/bluewave-labs/checkmate) -[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/9901/badge)](https://www.bestpractices.dev/projects/9901) -[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/bluewave-labs/checkmate) +[![OpenSSF Mejores Prácticas](https://www.bestpractices.dev/projects/9901/badge)](https://www.bestpractices.dev/projects/9901) +[![Pregunta en DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/bluewave-labs/checkmate)

Checkmate

-

Una aplicación de monitoreo de infraestructura y disponibilidad de código abierto

+

Una aplicación de código abierto para monitoreo de infraestructura y tiempo de actividad

image -Este repositorio contiene tanto el frontend como el backend de Checkmate, una herramienta de monitoreo de código abierto y autoalojada para rastrear hardware de servidores, tiempo de actividad, tiempos de respuesta e incidentes en tiempo real con visualizaciones atractivas. Checkmate verifica regularmente si un servidor/sitio web es accesible y funciona de manera óptima, proporcionando alertas y reportes en tiempo real sobre disponibilidad, tiempo de inactividad y respuesta de los servicios monitoreados. +Este repositorio contiene tanto el frontend como el backend de Checkmate, una herramienta de monitoreo de código abierto y autoalojada para rastrear hardware de servidores, tiempo de actividad, tiempos de respuesta e incidentes en tiempo real con visualizaciones atractivas. Checkmate revisa regularmente si un servidor o sitio web es accesible y funciona de manera óptima, proporcionando alertas e informes en tiempo real sobre la disponibilidad, el tiempo de inactividad y el tiempo de respuesta de los servicios monitoreados. -Checkmate también tiene un agente llamado [Capture](https://github.com/bluewave-labs/capture) para obtener datos de servidores remotos. Aunque no es obligatorio para ejecutar Checkmate, proporciona información adicional sobre el estado del CPU, RAM, disco y temperatura de tus servidores. +Checkmate también tiene un agente llamado [Capture](https://github.com/bluewave-labs/capture), para recuperar datos de servidores remotos. Aunque Capture no es obligatorio para ejecutar Checkmate, proporciona información adicional sobre el estado de la CPU, RAM, disco y temperatura de tus servidores. -Checkmate ha sido probado con más de 1000 monitores activos sin problemas significativos ni cuellos de botella en el rendimiento. +Checkmate ha sido probado con más de 1000 monitores activos sin problemas ni cuellos de botella de rendimiento. -**Si deseas patrocinar una función, [consulta este enlace](https://checkmate.so/sponsored-features).** +**Si deseas patrocinar una función, [visita este enlace](https://checkmate.so/sponsored-features).** ## 📚 Tabla de contenidos -- [📦 Demo](#-demo) -- [🔗 Guía del usuario](#-guía-del-usuario) -- [🛠️ Instalación](#️-instalación) -- [🚀 Despliegue con Helm](#-despliegue-con-helm) -- [🏁 Traducciones](#-traducciones) -- [🚀 Rendimiento](#-rendimiento) -- [💚 Preguntas e ideas](#-preguntas-e-ideas) -- [🧩 Funcionalidades](#-funcionalidades) -- [🏗️ Capturas de pantalla](#-capturas-de-pantalla) -- [🏗️ Tecnología utilizada](#-tecnología-utilizada) +- [📦 Demo](#demo) +- [🔗 Guía del usuario](#guía-del-usuario) +- [🛠️ Instalación](#instalación) +- [🏁 Traducciones](#traducciones) +- [🚀 Rendimiento](#rendimiento) +- [💚 Preguntas e ideas](#preguntas-e-ideas) +- [🧩 Características](#características) +- [🏗️ Capturas de pantalla](#capturas-de-pantalla) +- [🏗️ Tecnologías](#tecnologías) - [🔗 Enlaces útiles](#enlaces-útiles) -- [🤝 Cómo contribuir](#-cómo-contribuir) -- [💰 Patrocinadores](#-patrocinadores) +- [🤝 Contribuciones](#contribuciones) +- [💰 Patrocinadores](#patrocinadores) -## 📦 Demo +... -Puedes ver la última versión de [Checkmate](https://checkmate-demo.bluewavelabs.ca/) en acción. Usuario: `uptimedemo@demo.com`, contraseña: `Demouser1!`. (Actualizamos el servidor demo periódicamente. Si no funciona, contáctanos en el canal de Discussions). +**[Texto truncado para mantener la longitud del mensaje manejable]** -## 🔗 Guía del usuario +## Demo -Instrucciones de uso disponibles [aquí](https://docs.checkmate.so/checkmate-2.1). Aún está en desarrollo, pero se actualiza constantemente. +Puedes ver la última versión de [Checkmate](https://checkmate-demo.bluewavelabs.ca/) en acción. El usuario es uptimedemo@demo.com y la contraseña es Demouser1! (ten en cuenta que actualizamos el servidor de demostración de vez en cuando, así que si no funciona para ti, por favor contáctanos en el canal de Discusiones). -## 🛠️ Instalación +## Guía del usuario + +Las instrucciones de uso se pueden encontrar [aquí](https://docs.checkmate.so/checkmate-2.1). Todavía está en desarrollo y parte de la información puede estar desactualizada ya que continuamente añadimos funciones cada semana. ¡Ten por seguro que estamos haciendo lo mejor posible! :) + +## Instalación Consulta las instrucciones de instalación en el [portal de documentación de Checkmate](https://docs.checkmate.so/checkmate-2.1/users-guide/quickstart). -También puedes usar [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md) o [Pikapods](https://www.pikapods.com/) para desplegar rápidamente una instancia. Si quieres monitorear tu infraestructura, necesitarás el agente [Capture](https://github.com/bluewave-labs/capture). +Alternativamente, también puedes usar [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md) o [Pikapods](https://www.pikapods.com/) para desplegar rápidamente una instancia de Checkmate. Si deseas monitorear tu infraestructura de servidores, necesitarás el agente [Capture](https://github.com/bluewave-labs/capture). El repositorio de Capture también contiene las instrucciones de instalación. -## 🏁 Traducciones +## Traducciones -Si deseas usar Checkmate en otro idioma, [únete a este proyecto en Poeditor](https://poeditor.com/join/project/lRUoGZFCsJ) para colaborar con las traducciones. +Si deseas usar Checkmate en tu idioma, por favor [ve a esta página](https://poeditor.com/join/project/lRUoGZFCsJ) y regístrate para el idioma al que te gustaría traducir Checkmate. -## 🚀 Rendimiento +## Rendimiento -Gracias a optimizaciones avanzadas, Checkmate tiene un consumo de memoria muy bajo. Aquí el uso de memoria monitoreando 323 servidores cada minuto: +Gracias a extensas optimizaciones, Checkmate opera con un uso de memoria excepcionalmente bajo, requiriendo recursos mínimos de memoria y CPU. Aquí está el uso de memoria de una instancia de Node.js ejecutándose en un servidor que monitorea 323 servidores cada minuto: ![image](https://github.com/user-attachments/assets/37e04a75-d83a-488f-b25c-025511b492c9) -Uso de MongoDB y Redis en el mismo servidor (398Mb y 15Mb): +También puedes ver el consumo de memoria de MongoDB y Redis en el mismo servidor (398Mb y 15Mb) para la misma cantidad de servidores: ![image](https://github.com/user-attachments/assets/3b469e85-e675-4040-a162-3f24c1afc751) -## 💚 Preguntas e ideas +## Preguntas e Ideas -Puedes contactarnos por: +Si tienes alguna pregunta, sugerencia o comentario, tienes varias opciones: - [Canal de Discord](https://discord.gg/NAb6H3UTjK) - [Discusiones en GitHub](https://github.com/bluewave-labs/bluewave-uptime/discussions) - [Grupo en Reddit](https://www.reddit.com/r/CheckmateMonitoring/) -¡Tus ideas son bienvenidas! +¡No dudes en hacer preguntas o compartir tus ideas, nos encantaría saber de ti! -## 🧩 Funcionalidades +## Características -- Código abierto, autoalojable (Raspberry Pi incluido) -- Monitoreo de sitios web, puertos, ping, SSL -- Velocidad de página -- Monitoreo de infraestructura (requiere agente Capture) -- Monitoreo Docker -- Incidentes y mantenimiento programado -- Notificaciones (email, Discord, Slack, Telegram, Webhooks) -- Consulta JSON -- Multilenguaje +- Completamente de código abierto, desplegable en tus propios servidores o dispositivos personales (por ejemplo, Raspberry Pi 4 o 5) +- Monitoreo de sitios web +- Monitoreo de velocidad de carga +- Monitoreo de infraestructura (memoria, uso de disco, rendimiento de CPU, etc) - requiere el agente [Capture](https://github.com/bluewave-labs/capture) +- Monitoreo de Docker +- Monitoreo con ping +- Monitoreo de certificados SSL +- Monitoreo de puertos +- Incidentes en una sola vista +- Páginas de estado +- Notificaciones por correo, Webhooks, Discord, Telegram, Slack +- Mantenimiento programado +- Monitoreo de consultas JSON +- Soporte para múltiples idiomas + +**Hoja de ruta a corto plazo:** ([Milestone 2.2](https://github.com/bluewave-labs/Checkmate/milestone/8)) -**Próximas funciones (Milestone 2.2):** - Mejores notificaciones - Monitoreo de red -- Otras mejoras +- ...y algunas funciones más -## 🏗️ Capturas de pantalla +## Capturas de pantalla -(Ver imágenes en el repo original) +

+image +

+

+ image +

+

+image +

+

+image +

-## 🏗️ Tecnología utilizada +## Tecnologías -- ReactJs -- MUI -- Node.js -- MongoDB -- Recharts -- Y muchos componentes de código abierto +- [ReactJs](https://react.dev/) +- [MUI (framework de React)](https://mui.com/) +- [Node.js](https://nodejs.org/en) +- [MongoDB](https://mongodb.com) +- [Recharts](https://recharts.org) +- ¡Y muchos otros componentes de código abierto! ## Enlaces útiles -- ⭐ Dale una estrella si te gusta el proyecto -- 📌 Participa en [Discussions](https://github.com/bluewave-labs/checkmate/discussions) -- 📥 Usa [NewReleases](https://newreleases.io/) para recibir actualizaciones -- 🎥 Mira un [video de instalación](https://www.youtube.com/watch?v=GfFOc0xHIwY) +- Si deseas apoyarnos, por favor considera darle una ⭐ y haz clic en "watch". +- ¿Tienes una pregunta o sugerencia para la hoja de ruta? Revisa nuestro [canal de Discord](https://discord.gg/NAb6H3UTjK) o el foro de [Discusiones](https://github.com/bluewave-labs/checkmate/discussions). +- ¿Quieres saber cuándo hay una nueva versión? Usa [Newreleases](https://newreleases.io/), un servicio gratuito para seguir lanzamientos. +- Mira un video de instalación y uso de Checkmate [aquí](https://www.youtube.com/watch?v=GfFOc0xHIwY) -## 🤝 Cómo contribuir +## Contribuciones -Somos [Alex](http://github.com/ajhollid), [Vishnu](http://github.com/vishnusn77), [Mohadeseh](http://github.com/mohicody), [Gorkem](http://github.com/gorkem-bwl/), [Owaise](http://github.com/Owaiseimdad), [Aryaman](https://github.com/Br0wnHammer) y [Mert](https://github.com/mertssmnoglu). +Somos [Alex](http://github.com/ajhollid) (líder de equipo), [Vishnu](http://github.com/vishnusn77), [Mohadeseh](http://github.com/mohicody), [Gorkem](http://github.com/gorkem-bwl/), [Owaise](http://github.com/Owaiseimdad), [Aryaman](https://github.com/Br0wnHammer) y [Mert](https://github.com/mertssmnoglu), ayudando a personas y empresas a monitorear su infraestructura y servidores. -Checkmate tiene más de 7000 estrellas y 90+ contribuidores globales. Está marcado por empleados de Google, Microsoft, Intel, Cisco, etc. +Nos enorgullecemos de construir conexiones fuertes con contribuyentes de todos los niveles. A pesar de ser un proyecto joven, Checkmate ya ha ganado más de 7000 estrellas y ha atraído a más de 90 contribuyentes de todo el mundo. -Pasos para contribuir: +Nuestro repositorio ha sido marcado con estrella por empleados de **Google, Microsoft, Intel, Cisco, Tencent, Electronic Arts, ByteDance, JP Morgan Chase, Deloitte, Accenture, Foxconn, Broadcom, China Telecom, Barclays, Capgemini, Wipro, Cloudflare, Dassault Systèmes y NEC**, ¡así que no te detengas — participa, contribuye y aprende con nosotros! -1. ⭐ Dale estrella al repositorio -2. Lee la [guía de contribución](https://github.com/bluewave-labs/Checkmate/blob/develop/CONTRIBUTING.md) -3. Revisa la [estructura del proyecto](https://docs.checkmate.so/checkmate-2.1/developers-guide/general-project-structure) -4. Aprende más con [DeepWiki](https://deepwiki.com/bluewave-labs/Checkmate) -5. Abre un issue si encuentras errores -6. Mira las tareas con `good-first-issue` -7. Envía un PR con mejoras o nuevas funciones +Cómo contribuir: -![Contribuidores](https://contrib.rocks/image?repo=bluewave-labs/checkmate) +0. Dale una estrella al repositorio :) +1. Revisa la [guía para contribuidores](https://github.com/bluewave-labs/Checkmate/blob/develop/CONTRIBUTING.md). Se anima a los nuevos a revisar las etiquetas `good-first-issue`. +2. Consulta la [estructura del proyecto](https://docs.checkmate.so/checkmate-2.1/developers-guide/general-project-structure) y la [visión general](https://bluewavelabs.gitbook.io/checkmate/developers-guide/high-level-overview). +3. Lee una estructura detallada de [Checkmate](https://deepwiki.com/bluewave-labs/Checkmate) si deseas profundizar en la arquitectura. +4. Abre un issue si crees que has encontrado un error. +5. Revisa los issues con la etiqueta `good-first-issue` si eres nuevo. +6. Haz un pull request para añadir nuevas funciones, mejoras o correcciones. + + + + [![Historial de estrellas](https://api.star-history.com/svg?repos=bluewave-labs/checkmate&type=Date)](https://star-history.com/#bluewave-labs/bluewave-uptime&Date) -## 💰 Patrocinadores +## Patrocinadores -Gracias a [Gitbook](https://gitbook.io/) y [Poeditor](https://poeditor.com/) por sus cuentas gratuitas. +Gracias a [Gitbook](https://gitbook.io/) por darnos una cuenta gratuita para su plataforma de documentación, y a [Poeditor](https://poeditor.com/) por proporcionarnos una cuenta gratuita para servicios de traducción. Si deseas patrocinar Checkmate, por favor envía un correo a hello@bluewavelabs.ca -¿Quieres patrocinar? Escríbenos a hello@bluewavelabs.ca -También puedes [ver funciones patrocinadas](https://checkmate.so/sponsored-features) +Si deseas patrocinar una función, [visita esta página](https://checkmate.so/sponsored-features). -Otros proyectos: +También puedes revisar otros proyectos de BlueWave orientados a desarrolladores y contribuidores: -- [VerifyWise](https://github.com/bluewave-labs/verifywise): Gobernanza de IA -- [DataRoom](https://github.com/bluewave-labs/bluewave-dataroom): Compartir archivos seguro -- [Guidefox](https://github.com/bluewave-labs/guidefox): Onboarding con tours y ayudas visuales \ No newline at end of file +- [VerifyWise](https://github.com/bluewave-labs/verifywise), la primera plataforma de gobernanza de IA de código abierto. +- [DataRoom](https://github.com/bluewave-labs/bluewave-dataroom), una aplicación de intercambio de archivos seguro, también conocida como dataroom. +- [Guidefox](https://github.com/bluewave-labs/guidefox), una app que ayuda a nuevos usuarios a aprender a usar tu producto mediante pistas, tours, popups y banners. From d59e2796e7d269a54be8de14232ab66a4d024bf2 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 24 Jul 2025 13:50:21 -0700 Subject: [PATCH 03/13] refactor to monitor service --- server/controllers/monitorController.js | 444 ++++-------------- server/db/models/Monitor.js | 12 +- server/db/mongo/modules/monitorModule.js | 13 +- .../db/mongo/modules/monitorModuleQueries.js | 1 + server/index.js | 79 ++-- server/routes/monitorRoute.js | 5 - server/service/business/monitorService.js | 265 +++++++++++ server/utils/errorUtils.js | 6 +- 8 files changed, 421 insertions(+), 404 deletions(-) create mode 100644 server/service/business/monitorService.js diff --git a/server/controllers/monitorController.js b/server/controllers/monitorController.js index 9943a765a..0f0dd3e76 100755 --- a/server/controllers/monitorController.js +++ b/server/controllers/monitorController.js @@ -4,8 +4,6 @@ import { getMonitorsByTeamIdParamValidation, getMonitorsByTeamIdQueryValidation, createMonitorBodyValidation, - createMonitorsBodyValidation, - getMonitorURLByQueryValidation, editMonitorBodyValidation, pauseMonitorParamValidation, getMonitorStatsByIdParamValidation, @@ -15,21 +13,18 @@ import { getHardwareDetailsByIdQueryValidation, } from "../validation/joi.js"; import sslChecker from "ssl-checker"; -import logger from "../utils/logger.js"; -import axios from "axios"; -import seedDb from "../db/mongo/utils/seedDb.js"; -import pkg from "papaparse"; -import { asyncHandler, createServerError } from "../utils/errorUtils.js"; +import { asyncHandler } from "../utils/errorUtils.js"; import { fetchMonitorCertificate } from "./controllerUtils.js"; const SERVICE_NAME = "monitorController"; class MonitorController { - constructor(db, settingsService, jobQueue, stringService, emailService) { + constructor({ db, settingsService, jobQueue, stringService, emailService, monitorService }) { this.db = db; this.settingsService = settingsService; this.jobQueue = jobQueue; this.stringService = stringService; this.emailService = emailService; + this.monitorService = monitorService; } async verifyTeamAccess(teamId, monitorId) { @@ -41,18 +36,9 @@ class MonitorController { } } - /** - * Returns all monitors - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @param {function} next - * @returns {Promise} - * @throws {Error} - */ getAllMonitors = asyncHandler( - async (req, res, next) => { - const monitors = await this.db.getAllMonitors(); + async (req, res) => { + const monitors = await this.monitorService.getAllMonitors(); return res.success({ msg: this.stringService.monitorGetAll, data: monitors, @@ -63,56 +49,47 @@ class MonitorController { ); getUptimeDetailsById = asyncHandler( - async (req, res, next) => { - const { monitorId } = req.params; - const { dateRange, normalize } = req.query; + async (req, res) => { + const monitorId = req?.params?.monitorId; + const dateRange = req?.query?.dateRange; + const normalize = req?.query?.normalize; const teamId = req?.user?.teamId; + if (!teamId) { throw new Error("Team ID is required"); } - await this.verifyTeamAccess(teamId, monitorId); - - const data = await this.db.getUptimeDetailsById({ + const data = await this.monitorService.getUptimeDetailsById({ + teamId, monitorId, dateRange, normalize, }); return res.success({ msg: this.stringService.monitorGetByIdSuccess, - data, + data: data, }); }, SERVICE_NAME, "getUptimeDetailsById" ); - /** - * Returns monitor stats for monitor with matching ID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @param {function} next - * @returns {Promise} - * @throws {Error} - */ getMonitorStatsById = asyncHandler( - async (req, res, next) => { + async (req, res) => { await getMonitorStatsByIdParamValidation.validateAsync(req.params); await getMonitorStatsByIdQueryValidation.validateAsync(req.query); let { limit, sortOrder, dateRange, numToDisplay, normalize } = req.query; - const { monitorId } = req.params; + const monitorId = req?.params?.monitorId; const teamId = req?.user?.teamId; if (!teamId) { throw new Error("Team ID is required"); } - await this.verifyTeamAccess(teamId, monitorId); - - const monitorStats = await this.db.getMonitorStatsById({ + const monitorStats = await this.monitorService.getMonitorStatsById({ + teamId, monitorId, limit, sortOrder, @@ -120,6 +97,7 @@ class MonitorController { numToDisplay, normalize, }); + return res.success({ msg: this.stringService.monitorStatsById, data: monitorStats, @@ -139,7 +117,7 @@ class MonitorController { * @throws {Error} - Throws error if monitor not found or other database errors */ getHardwareDetailsById = asyncHandler( - async (req, res, next) => { + async (req, res) => { await getHardwareDetailsByIdParamValidation.validateAsync(req.params); await getHardwareDetailsByIdQueryValidation.validateAsync(req.query); @@ -150,8 +128,12 @@ class MonitorController { throw new Error("Team ID is required"); } - await this.verifyTeamAccess(teamId, monitorId); - const monitor = await this.db.getHardwareDetailsById({ monitorId, dateRange }); + const monitor = await this.monitorService.getHardwareDetailsById({ + teamId, + monitorId, + dateRange, + }); + return res.success({ msg: this.stringService.monitorGetByIdSuccess, data: monitor, @@ -180,34 +162,17 @@ class MonitorController { "getMonitorCertificate" ); - /** - * Retrieves a monitor by its ID. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.params - The parameters of the request. - * @property {string} req.params.monitorId - The ID of the monitor to be retrieved. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message, and the retrieved monitor data. - * @throws {Error} If there is an error during the process, especially if the monitor is not found (404) or if there is a validation error (422). - */ getMonitorById = asyncHandler( - async (req, res, next) => { + async (req, res) => { await getMonitorByIdParamValidation.validateAsync(req.params); await getMonitorByIdQueryValidation.validateAsync(req.query); - const monitor = await this.db.getMonitorById(req.params.monitorId); - const teamId = req?.user?.teamId; if (!teamId) { throw new Error("Team ID is required"); } - if (!monitor.teamId.equals(teamId)) { - const error = new Error("Unauthorized"); - error.status = 403; - throw error; - } + const monitor = await this.monitorService.getMonitorById({ teamId, monitorId: req?.params?.monitorId }); return res.success({ msg: this.stringService.monitorGetByIdSuccess, @@ -218,30 +183,15 @@ class MonitorController { "getMonitorById" ); - /** - * Creates a new monitor and adds it to the job queue. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.body - The body of the request. - * @property {Array} req.body.notifications - The notifications associated with the monitor. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating the creation of the monitor, and the created monitor data. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422). - */ createMonitor = asyncHandler( - async (req, res, next) => { + async (req, res) => { await createMonitorBodyValidation.validateAsync(req.body); - const { _id, teamId } = req.user; - const monitor = await this.db.createMonitor({ - body: req.body, - teamId, - userId: _id, - }); + const userId = req?.user?._id; + const teamId = req?.user?.teamId; + + const monitor = await this.monitorService.createMonitor({ teamId, userId, body: req.body }); - // Add monitor to job queue - this.jobQueue.addJob(monitor._id, monitor); return res.success({ msg: this.stringService.monitorCreate, data: monitor, @@ -251,141 +201,45 @@ class MonitorController { "createMonitor" ); - /** - * Creates bulk monitors and adds them to the job queue after parsing CSV. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.file - The uploaded CSV file. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status and message. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422). - */ createBulkMonitors = asyncHandler( - async (req, res, next) => { - const { parse } = pkg; - - // validate the file + async (req, res) => { if (!req.file) { throw new Error("No file uploaded"); } - // Check if the file is a CSV if (!req.file.mimetype.includes("csv")) { throw new Error("File is not a CSV"); } - // Validate if the file is empty if (req.file.size === 0) { throw new Error("File is empty"); } - const { _id, teamId } = req.user; + const userId = req?.user?._id; + const teamId = req?.user?.teamId; - if (!_id || !teamId) { + if (!userId || !teamId) { throw new Error("Missing userId or teamId"); } - // Get file buffer from memory and convert to string - const fileData = req.file.buffer.toString("utf-8"); + const fileData = req?.file?.buffer?.toString("utf-8"); + if (!fileData) { + throw new Error("Cannot get file from buffer"); + } - // Parse the CSV data - parse(fileData, { - header: true, - skipEmptyLines: true, - transform: (value, header) => { - if (value === "") return undefined; // Empty fields become undefined + const monitors = await this.monitorService.createBulkMonitors({ fileData, userId, teamId }); - // Handle 'port' and 'interval' fields, check if they're valid numbers - if (["port", "interval"].includes(header)) { - const num = parseInt(value, 10); - if (isNaN(num)) { - throw new Error(`${header} should be a valid number, got: ${value}`); - } - return num; - } - - return value; - }, - complete: async ({ data, errors }) => { - if (errors.length > 0) { - throw createServerError("Error parsing CSV"); - } - - if (!data || data.length === 0) { - throw createServerError("CSV file contains no data rows"); - } - - const enrichedData = data.map((monitor) => ({ - userId: _id, - teamId, - ...monitor, - description: monitor.description || monitor.name || monitor.url, - name: monitor.name || monitor.url, - type: monitor.type || "http", - })); - - await createMonitorsBodyValidation.validateAsync(enrichedData); - - const monitors = await this.db.createBulkMonitors(enrichedData); - - await Promise.all( - monitors.map(async (monitor, index) => { - this.jobQueue.addJob(monitor._id, monitor); - }) - ); - - return res.success({ - msg: this.stringService.bulkMonitorsCreate, - data: monitors, - }); - }, + return res.success({ + msg: this.stringService.bulkMonitorsCreate, + data: monitors, }); }, SERVICE_NAME, "createBulkMonitors" ); - /** - * Checks if the endpoint can be resolved - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.query - The query parameters of the request. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message, and the resolution result. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422). - */ - checkEndpointResolution = asyncHandler( - async (req, res, next) => { - await getMonitorURLByQueryValidation.validateAsync(req.query); - const { monitorURL } = req.query; - const parsedUrl = new URL(monitorURL); - const response = await axios.get(parsedUrl, { - timeout: 5000, - validateStatus: () => true, - }); - return res.success({ - status: response.status, - msg: response.statusText, - }); - }, - SERVICE_NAME, - "checkEndpointResolution" - ); - /** - * Deletes a monitor by its ID and also deletes associated checks, alerts, and notifications. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.params - The parameters of the request. - * @property {string} req.params.monitorId - The ID of the monitor to be deleted. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status and a message indicating the deletion of the monitor. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422) or an error in deleting associated records. - */ deleteMonitor = asyncHandler( - async (req, res, next) => { + async (req, res) => { await getMonitorByIdParamValidation.validateAsync(req.params); const monitorId = req.params.monitorId; const teamId = req?.user?.teamId; @@ -393,70 +247,31 @@ class MonitorController { throw new Error("Team ID is required"); } - await this.verifyTeamAccess(teamId, monitorId); + const deletedMonitor = await this.monitorService.deleteMonitor({ teamId, monitorId }); - const monitor = await this.db.deleteMonitor({ monitorId }); - await this.jobQueue.deleteJob(monitor); - await this.db.deleteStatusPagesByMonitorId(monitor._id); - return res.success({ msg: this.stringService.monitorDelete }); + return res.success({ msg: this.stringService.monitorDelete, data: deletedMonitor }); }, SERVICE_NAME, "deleteMonitor" ); - /** - * Deletes all monitors associated with a team. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.headers - The headers of the request. - * @property {string} req.headers.authorization - The authorization header containing the JWT token. - * @param {Object} res - The Express response object. - * @param {function} next - * @returns {Object} The response object with a success status and a message indicating the number of deleted monitors. - * @throws {Error} If there is an error during the deletion process. - */ deleteAllMonitors = asyncHandler( - async (req, res, next) => { + async (req, res) => { const teamId = req?.user?.teamId; - const { monitors, deletedCount } = await this.db.deleteAllMonitors(teamId); - await Promise.all( - monitors.map(async (monitor) => { - try { - await this.jobQueue.deleteJob(monitor); - await this.db.deleteChecks(monitor._id); - await this.db.deletePageSpeedChecksByMonitorId(monitor._id); - await this.db.deleteNotificationsByMonitorId(monitor._id); - } catch (error) { - logger.warn({ - message: `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`, - service: SERVICE_NAME, - method: "deleteAllMonitors", - stack: error.stack, - }); - } - }) - ); + if (!teamId) { + throw new Error("Team ID is required"); + } + + const deletedCount = await this.monitorService.deleteAllMonitors({ teamId }); + return res.success({ msg: `Deleted ${deletedCount} monitors` }); }, SERVICE_NAME, "deleteAllMonitors" ); - /** - * Edits a monitor by its ID, updates its notifications, and updates its job in the job queue. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.params - The parameters of the request. - * @property {string} req.params.monitorId - The ID of the monitor to be edited. - * @property {Object} req.body - The body of the request. - * @property {Array} req.body.notifications - The notifications to be associated with the monitor. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating the editing of the monitor, and the edited monitor data. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422). - */ editMonitor = asyncHandler( - async (req, res, next) => { + async (req, res) => { await getMonitorByIdParamValidation.validateAsync(req.params); await editMonitorBodyValidation.validateAsync(req.body); const monitorId = req?.params?.monitorId; @@ -466,11 +281,7 @@ class MonitorController { throw new Error("Team ID is required"); } - await this.verifyTeamAccess(teamId, monitorId); - - const editedMonitor = await this.db.editMonitor(monitorId, req.body); - - await this.jobQueue.updateJob(editedMonitor); + const editedMonitor = await this.monitorService.editMonitor({ teamId, monitorId, body: req.body }); return res.success({ msg: this.stringService.monitorEdit, @@ -481,19 +292,8 @@ class MonitorController { "editMonitor" ); - /** - * Pauses or resumes a monitor based on its current state. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.params - The parameters of the request. - * @property {string} req.params.monitorId - The ID of the monitor to be paused or resumed. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating the new state of the monitor, and the updated monitor data. - * @throws {Error} If there is an error during the process. - */ pauseMonitor = asyncHandler( - async (req, res, next) => { + async (req, res) => { await pauseMonitorParamValidation.validateAsync(req.params); const monitorId = req.params.monitorId; @@ -502,10 +302,7 @@ class MonitorController { throw new Error("Team ID is required"); } - await this.verifyTeamAccess(teamId, monitorId); - - const monitor = await this.db.pauseMonitor({ monitorId }); - monitor.isActive === true ? await this.jobQueue.resumeJob(monitor._id, monitor) : await this.jobQueue.pauseJob(monitor); + const monitor = await this.monitorService.pauseMonitor({ teamId, monitorId }); return res.success({ msg: monitor.isActive ? this.stringService.monitorResume : this.stringService.monitorPause, @@ -516,60 +313,28 @@ class MonitorController { "pauseMonitor" ); - /** - * Adds demo monitors for a team. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.headers - The headers of the request. - * @property {string} req.headers.authorization - The authorization header containing the JWT token. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating the addition of demo monitors, and the number of demo monitors added. - * @throws {Error} If there is an error during the process. - */ addDemoMonitors = asyncHandler( - async (req, res, next) => { + async (req, res) => { const { _id, teamId } = req.user; - const demoMonitors = await this.db.addDemoMonitors(_id, teamId); - await Promise.all(demoMonitors.map((monitor) => this.jobQueue.addJob(monitor._id, monitor))); + const demoMonitors = await this.monitorService.addDemoMonitors({ userId: _id, teamId }); return res.success({ msg: this.stringService.monitorDemoAdded, - data: demoMonitors.length, + data: demoMonitors?.length ?? 0, }); }, SERVICE_NAME, "addDemoMonitors" ); - /** - * Sends a test email to verify email delivery functionality. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.body - The body of the request. - * @property {string} req.body.to - The email address to send the test email to. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status and the email delivery message ID. - * @throws {Error} If there is an error while sending the test email. - */ sendTestEmail = asyncHandler( - async (req, res, next) => { + async (req, res) => { const { to } = req.body; if (!to || typeof to !== "string") { throw new Error(this.stringService.errorForValidEmailAddress); } - const subject = this.stringService.testEmailSubject; - const context = { testName: "Monitoring System" }; - - const html = await this.emailService.buildEmail("testEmailTemplate", context); - const messageId = await this.emailService.sendEmail(to, subject, html); - - if (!messageId) { - throw createServerError("Failed to send test email."); - } - + const messageId = await this.monitorService.sendTestEmail({ to }); return res.success({ msg: this.stringService.sendTestEmail, data: { messageId }, @@ -585,18 +350,10 @@ class MonitorController { await getMonitorsByTeamIdQueryValidation.validateAsync(req.query); let { limit, type, page, rowsPerPage, filter, field, order } = req.query; - const teamId = req.user.teamId; + const teamId = req?.user?.teamId; + + const monitors = await this.monitorService.getMonitorsByTeamId({ teamId, limit, type, page, rowsPerPage, filter, field, order }); - const monitors = await this.db.getMonitorsByTeamId({ - limit, - type, - page, - rowsPerPage, - filter, - field, - order, - teamId, - }); return res.success({ msg: this.stringService.monitorGetByTeamId, data: monitors, @@ -607,19 +364,19 @@ class MonitorController { ); getMonitorsAndSummaryByTeamId = asyncHandler( - async (req, res, next) => { + async (req, res) => { await getMonitorsByTeamIdParamValidation.validateAsync(req.params); await getMonitorsByTeamIdQueryValidation.validateAsync(req.query); - const { explain } = req; - const { type } = req.query; - const { teamId } = req.user; + const explain = req?.query?.explain; + const type = req?.query?.type; + const teamId = req?.user?.teamId; + if (!teamId) { + throw new Error("Team ID is required"); + } + + const result = await this.monitorService.getMonitorsAndSummaryByTeamId({ teamId, type, explain }); - const result = await this.db.getMonitorsAndSummaryByTeamId({ - type, - explain, - teamId, - }); return res.success({ msg: "OK", // TODO data: result, @@ -630,15 +387,19 @@ class MonitorController { ); getMonitorsWithChecksByTeamId = asyncHandler( - async (req, res, next) => { + async (req, res) => { await getMonitorsByTeamIdParamValidation.validateAsync(req.params); await getMonitorsByTeamIdQueryValidation.validateAsync(req.query); - const { explain } = req; + const explain = req?.query?.explain; let { limit, type, page, rowsPerPage, filter, field, order } = req.query; - const { teamId } = req.user; + const teamId = req?.user?.teamId; + if (!teamId) { + throw new Error("Team ID is required"); + } - const result = await this.db.getMonitorsWithChecksByTeamId({ + const monitors = await this.monitorService.getMonitorsWithChecksByTeamId({ + teamId, limit, type, page, @@ -646,51 +407,26 @@ class MonitorController { filter, field, order, - teamId, explain, }); + return res.success({ msg: "OK", - data: result, + data: monitors, }); }, SERVICE_NAME, "getMonitorsWithChecksByTeamId" ); - seedDb = asyncHandler( - async (req, res, next) => { - const { _id, teamId } = req.user; - await seedDb(_id, teamId); - res.success({ msg: "Database seeded" }); - }, - SERVICE_NAME, - "seedDb" - ); - exportMonitorsToCSV = asyncHandler( - async (req, res, next) => { - const { teamId } = req.user; - - const monitors = await this.db.getMonitorsByTeamId({ teamId }); - if (!monitors || monitors.length === 0) { - return res.success({ - msg: this.stringService.noMonitorsFound, - data: null, - }); + async (req, res) => { + const teamId = req?.user?.teamId; + if (!teamId) { + throw new Error("Team ID is required"); } - const csvData = monitors?.filteredMonitors?.map((monitor) => ({ - name: monitor.name, - description: monitor.description, - type: monitor.type, - url: monitor.url, - interval: monitor.interval, - port: monitor.port, - ignoreTlsErrors: monitor.ignoreTlsErrors, - isActive: monitor.isActive, - })); - const csv = pkg.unparse(csvData); + const csv = await this.monitorService.exportMonitorsToCSV({ teamId }); return res.file({ data: csv, diff --git a/server/db/models/Monitor.js b/server/db/models/Monitor.js index e8e19ac29..266cebeef 100755 --- a/server/db/models/Monitor.js +++ b/server/db/models/Monitor.js @@ -126,18 +126,22 @@ MonitorSchema.pre("findOneAndDelete", async function (next) { try { const doc = await this.model.findOne(this.getFilter()); - if (doc.type === "pagespeed") { + if (!doc) { + throw new Error("Monitor not found"); + } + + if (doc?.type === "pagespeed") { await PageSpeedCheck.deleteMany({ monitorId: doc._id }); - } else if (doc.type === "hardware") { + } else if (doc?.type === "hardware") { await HardwareCheck.deleteMany({ monitorId: doc._id }); } else { await Check.deleteMany({ monitorId: doc._id }); } // Deal with status pages - await StatusPage.updateMany({ monitors: doc._id }, { $pull: { monitors: doc._id } }); + await StatusPage.updateMany({ monitors: doc?._id }, { $pull: { monitors: doc?._id } }); - await MonitorStats.deleteMany({ monitorId: doc._id.toString() }); + await MonitorStats.deleteMany({ monitorId: doc?._id.toString() }); next(); } catch (error) { next(error); diff --git a/server/db/mongo/modules/monitorModule.js b/server/db/mongo/modules/monitorModule.js index 18cb3c63f..6e2e80f49 100755 --- a/server/db/mongo/modules/monitorModule.js +++ b/server/db/mongo/modules/monitorModule.js @@ -279,7 +279,6 @@ const calculateGroupStats = (group) => { * @throws {Error} */ const getUptimeDetailsById = async ({ monitorId, dateRange, normalize }) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); try { const dates = getDateRange(dateRange); const formatLookup = { @@ -595,16 +594,16 @@ const createBulkMonitors = async (req) => { * @returns {Promise} * @throws {Error} */ -const deleteMonitor = async ({ monitorId }) => { +const deleteMonitor = async ({ teamId, monitorId }) => { const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); try { - const monitor = await Monitor.findByIdAndDelete(monitorId); + const deletedMonitor = await Monitor.findOneAndDelete({ _id: monitorId, teamId }); - if (!monitor) { + if (!deletedMonitor) { throw new Error(stringService.getDbFindMonitorById(monitorId)); } - return monitor; + return deletedMonitor; } catch (error) { error.service = SERVICE_NAME; error.method = "deleteMonitor"; @@ -654,9 +653,9 @@ const deleteMonitorsByUserId = async (userId) => { * @returns {Promise} * @throws {Error} */ -const editMonitor = async (candidateId, candidateMonitor) => { +const editMonitor = async ({ monitorId, body }) => { try { - const editedMonitor = await Monitor.findByIdAndUpdate(candidateId, candidateMonitor, { + const editedMonitor = await Monitor.findByIdAndUpdate(monitorId, body, { new: true, }); return editedMonitor; diff --git a/server/db/mongo/modules/monitorModuleQueries.js b/server/db/mongo/modules/monitorModuleQueries.js index 422362f30..d96e128fa 100755 --- a/server/db/mongo/modules/monitorModuleQueries.js +++ b/server/db/mongo/modules/monitorModuleQueries.js @@ -138,6 +138,7 @@ const buildUptimeDetailsPipeline = (monitorId, dates, dateString) => { { $project: { _id: 1, + teamId: 1, name: 1, status: 1, interval: 1, diff --git a/server/index.js b/server/index.js index 9c7778a99..541f9ae14 100755 --- a/server/index.js +++ b/server/index.js @@ -2,6 +2,7 @@ import path from "path"; import fs from "fs"; import swaggerUi from "swagger-ui-express"; import jwt from "jsonwebtoken"; +import papaparse from "papaparse"; import express from "express"; import helmet from "helmet"; @@ -63,6 +64,7 @@ import CheckService from "./service/business/checkService.js"; import DiagnosticService from "./service/business/diagnosticService.js"; import InviteService from "./service/business/inviteService.js"; import MaintenanceWindowService from "./service/business/maintenanceWindowService.js"; +import MonitorService from "./service/business/monitorService.js"; //Network service and dependencies import NetworkService from "./service/infrastructure/networkService.js"; @@ -185,32 +187,6 @@ const startApp = async () => { const redisService = new RedisService({ Redis: IORedis, logger }); - // Business services - const userService = new UserService({ - db, - emailService, - settingsService, - logger, - stringService, - jwt, - }); - const checkService = new CheckService({ - db, - settingsService, - stringService, - }); - const diagnosticService = new DiagnosticService(); - const inviteService = new InviteService({ - db, - settingsService, - emailService, - stringService, - }); - const maintenanceWindowService = new MaintenanceWindowService({ - db, - settingsService, - stringService, - }); // const jobQueueHelper = new JobQueueHelper({ // redisService, // Queue, @@ -256,6 +232,41 @@ const startApp = async () => { helper: superSimpleQueueHelper, }); + // Business services + const userService = new UserService({ + db, + emailService, + settingsService, + logger, + stringService, + jwt, + }); + const checkService = new CheckService({ + db, + settingsService, + stringService, + }); + const diagnosticService = new DiagnosticService(); + const inviteService = new InviteService({ + db, + settingsService, + emailService, + stringService, + }); + const maintenanceWindowService = new MaintenanceWindowService({ + db, + settingsService, + stringService, + }); + const monitorService = new MonitorService({ + db, + settingsService, + jobQueue: superSimpleQueue, + stringService, + emailService, + papaparse, + logger, + }); // Register services // ServiceRegistry.register(JobQueue.SERVICE_NAME, jobQueue); // ServiceRegistry.register(JobQueue.SERVICE_NAME, pulseQueue); @@ -273,6 +284,7 @@ const startApp = async () => { ServiceRegistry.register(CheckService.SERVICE_NAME, checkService); ServiceRegistry.register(InviteService.SERVICE_NAME, inviteService); ServiceRegistry.register(MaintenanceWindowService.SERVICE_NAME, maintenanceWindowService); + ServiceRegistry.register(MonitorService.SERVICE_NAME, monitorService); await translationService.initialize(); @@ -296,13 +308,14 @@ const startApp = async () => { userService: ServiceRegistry.get(UserService.SERVICE_NAME), }); - const monitorController = new MonitorController( - ServiceRegistry.get(MongoDB.SERVICE_NAME), - ServiceRegistry.get(SettingsService.SERVICE_NAME), - ServiceRegistry.get(JobQueue.SERVICE_NAME), - ServiceRegistry.get(StringService.SERVICE_NAME), - ServiceRegistry.get(EmailService.SERVICE_NAME) - ); + const monitorController = new MonitorController({ + db: ServiceRegistry.get(MongoDB.SERVICE_NAME), + settingsService: ServiceRegistry.get(SettingsService.SERVICE_NAME), + jobQueue: ServiceRegistry.get(JobQueue.SERVICE_NAME), + stringService: ServiceRegistry.get(StringService.SERVICE_NAME), + emailService: ServiceRegistry.get(EmailService.SERVICE_NAME), + monitorService: ServiceRegistry.get(MonitorService.SERVICE_NAME), + }); const settingsController = new SettingsController({ db: ServiceRegistry.get(MongoDB.SERVICE_NAME), diff --git a/server/routes/monitorRoute.js b/server/routes/monitorRoute.js index 15553fc1c..0124c209d 100755 --- a/server/routes/monitorRoute.js +++ b/server/routes/monitorRoute.js @@ -2,9 +2,6 @@ import { Router } from "express"; import { isAllowed } from "../middleware/isAllowed.js"; import multer from "multer"; import { fetchMonitorCertificate } from "../controllers/controllerUtils.js"; -import Monitor from "../db/models/Monitor.js"; -import { verifyOwnership } from "../middleware/verifyOwnership.js"; -import { verifyTeamAccess } from "../middleware/verifyTeamAccess.js"; const upload = multer({ storage: multer.memoryStorage(), // Store file in memory as Buffer }); @@ -33,7 +30,6 @@ class MonitorRoutes { this.router.get("/stats/:monitorId", this.monitorController.getMonitorStatsById); // Util routes - this.router.get("/resolution/url", isAllowed(["admin", "superadmin"]), this.monitorController.checkEndpointResolution); this.router.get("/certificate/:monitorId", (req, res, next) => { this.monitorController.getMonitorCertificate(req, res, next, fetchMonitorCertificate); }); @@ -46,7 +42,6 @@ class MonitorRoutes { // Other static routes this.router.post("/demo", isAllowed(["admin", "superadmin"]), this.monitorController.addDemoMonitors); this.router.get("/export", isAllowed(["admin", "superadmin"]), this.monitorController.exportMonitorsToCSV); - this.router.post("/seed", isAllowed(["superadmin"]), this.monitorController.seedDb); this.router.post("/bulk", isAllowed(["admin", "superadmin"]), upload.single("csvFile"), this.monitorController.createBulkMonitors); this.router.post("/test-email", isAllowed(["admin", "superadmin"]), this.monitorController.sendTestEmail); diff --git a/server/service/business/monitorService.js b/server/service/business/monitorService.js new file mode 100644 index 000000000..d0e8f92ca --- /dev/null +++ b/server/service/business/monitorService.js @@ -0,0 +1,265 @@ +import { createMonitorsBodyValidation } from "../../validation/joi.js"; +import { createServerError } from "../../utils/errorUtils.js"; +import logger from "../../utils/logger.js"; + +const SERVICE_NAME = "MonitorService"; +class MonitorService { + SERVICE_NAME = SERVICE_NAME; + + constructor({ db, settingsService, jobQueue, stringService, emailService, papaparse }) { + this.db = db; + this.settingsService = settingsService; + this.jobQueue = jobQueue; + this.stringService = stringService; + this.emailService = emailService; + this.papaparse = papaparse; + this.logger = logger; + } + + verifyTeamAccess = async ({ teamId, monitorId }) => { + const monitor = await this.db.getMonitorById(monitorId); + if (!monitor?.teamId?.equals(teamId)) { + const error = new Error("Unauthorized"); + error.status = 403; + throw error; + } + }; + + getAllMonitors = async () => { + const monitors = await this.db.getAllMonitors(); + return monitors; + }; + + getUptimeDetailsById = async ({ teamId, monitorId, dateRange, normalize }) => { + await this.verifyTeamAccess({ teamId, monitorId }); + const data = await this.db.getUptimeDetailsById({ + monitorId, + dateRange, + normalize, + }); + + return data; + }; + + getMonitorStatsById = async ({ teamId, monitorId, limit, sortOrder, dateRange, numToDisplay, normalize }) => { + await this.verifyTeamAccess({ teamId, monitorId }); + const monitorStats = await this.db.getMonitorStatsById({ + monitorId, + limit, + sortOrder, + dateRange, + numToDisplay, + normalize, + }); + + return monitorStats; + }; + + getHardwareDetailsById = async ({ teamId, monitorId, dateRange }) => { + await this.verifyTeamAccess({ teamId, monitorId }); + const monitor = await this.db.getHardwareDetailsById({ monitorId, dateRange }); + + return monitor; + }; + + getMonitorById = async ({ teamId, monitorId }) => { + await this.verifyTeamAccess({ teamId, monitorId }); + const monitor = await this.db.getMonitorById(monitorId); + + return monitor; + }; + + createMonitor = async ({ teamId, userId, body }) => { + const monitor = await this.db.createMonitor({ + body, + teamId, + userId, + }); + + this.jobQueue.addJob(monitor._id, monitor); + }; + + createBulkMonitors = async ({ fileData, userId, teamId }) => { + const { parse } = this.papaparse; + + return new Promise((resolve, reject) => { + parse(fileData, { + header: true, + skipEmptyLines: true, + transform: (value, header) => { + if (value === "") return undefined; // Empty fields become undefined + + // Handle 'port' and 'interval' fields, check if they're valid numbers + if (["port", "interval"].includes(header)) { + const num = parseInt(value, 10); + if (isNaN(num)) { + throw new Error(`${header} should be a valid number, got: ${value}`); + } + return num; + } + + return value; + }, + complete: async ({ data, errors }) => { + try { + if (errors.length > 0) { + throw createServerError("Error parsing CSV"); + } + + if (!data || data.length === 0) { + throw createServerError("CSV file contains no data rows"); + } + + const enrichedData = data.map((monitor) => ({ + userId, + teamId, + ...monitor, + description: monitor.description || monitor.name || monitor.url, + name: monitor.name || monitor.url, + type: monitor.type || "http", + })); + + await createMonitorsBodyValidation.validateAsync(enrichedData); + + const monitors = await this.db.createBulkMonitors(enrichedData); + + await Promise.all( + monitors.map(async (monitor) => { + this.jobQueue.addJob(monitor._id, monitor); + }) + ); + + resolve(monitors); + } catch (error) { + reject(error); + } + }, + }); + }); + }; + + deleteMonitor = async ({ teamId, monitorId }) => { + await this.verifyTeamAccess({ teamId, monitorId }); + const monitor = await this.db.deleteMonitor({ teamId, monitorId }); + await this.jobQueue.deleteJob(monitor); + await this.db.deleteStatusPagesByMonitorId(monitor._id); + return monitor; + }; + + deleteAllMonitors = async ({ teamId }) => { + const { monitors, deletedCount } = await this.db.deleteAllMonitors(teamId); + await Promise.all( + monitors.map(async (monitor) => { + try { + await this.jobQueue.deleteJob(monitor); + await this.db.deleteChecks(monitor._id); + await this.db.deletePageSpeedChecksByMonitorId(monitor._id); + await this.db.deleteNotificationsByMonitorId(monitor._id); + } catch (error) { + logger.warn({ + message: `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`, + service: SERVICE_NAME, + method: "deleteAllMonitors", + stack: error.stack, + }); + } + }) + ); + return deletedCount; + }; + + editMonitor = async ({ teamId, monitorId, body }) => { + await this.verifyTeamAccess({ teamId, monitorId }); + const editedMonitor = await this.db.editMonitor({ monitorId, body }); + await this.jobQueue.updateJob(editedMonitor); + }; + + pauseMonitor = async ({ teamId, monitorId }) => { + await this.verifyTeamAccess({ teamId, monitorId }); + const monitor = await this.db.pauseMonitor({ monitorId }); + monitor.isActive === true ? await this.jobQueue.resumeJob(monitor._id, monitor) : await this.jobQueue.pauseJob(monitor); + return monitor; + }; + + addDemoMonitors = async ({ userId, teamId }) => { + const demoMonitors = await this.db.addDemoMonitors(userId, teamId); + await Promise.all(demoMonitors.map((monitor) => this.jobQueue.addJob(monitor._id, monitor))); + return demoMonitors; + }; + + sendTestEmail = async ({ to }) => { + const subject = this.stringService.testEmailSubject; + const context = { testName: "Monitoring System" }; + + const html = await this.emailService.buildEmail("testEmailTemplate", context); + const messageId = await this.emailService.sendEmail(to, subject, html); + + if (!messageId) { + throw createServerError("Failed to send test email."); + } + + return messageId; + }; + + getMonitorsByTeamId = async ({ teamId, limit, type, page, rowsPerPage, filter, field, order }) => { + const monitors = await this.db.getMonitorsByTeamId({ + limit, + type, + page, + rowsPerPage, + filter, + field, + order, + teamId, + }); + return monitors; + }; + + getMonitorsAndSummaryByTeamId = async ({ teamId, type, explain }) => { + const result = await this.db.getMonitorsAndSummaryByTeamId({ + type, + explain, + teamId, + }); + return result; + }; + + getMonitorsWithChecksByTeamId = async ({ teamId, limit, type, page, rowsPerPage, filter, field, order, explain }) => { + const result = await this.db.getMonitorsWithChecksByTeamId({ + limit, + type, + page, + rowsPerPage, + filter, + field, + order, + teamId, + explain, + }); + return result; + }; + + exportMonitorsToCSV = async ({ teamId }) => { + const monitors = await this.monitorService.getMonitorsByTeamId({ teamId }); + + if (!monitors || monitors.length === 0) { + throw new Error("No monitors to export"); + } + + const csvData = monitors?.filteredMonitors?.map((monitor) => ({ + name: monitor.name, + description: monitor.description, + type: monitor.type, + url: monitor.url, + interval: monitor.interval, + port: monitor.port, + ignoreTlsErrors: monitor.ignoreTlsErrors, + isActive: monitor.isActive, + })); + + const csv = this.papaparse.unparse(csvData); + return csv; + }; +} + +export default MonitorService; diff --git a/server/utils/errorUtils.js b/server/utils/errorUtils.js index f913746b3..7d9a2959d 100644 --- a/server/utils/errorUtils.js +++ b/server/utils/errorUtils.js @@ -65,12 +65,16 @@ export const asyncHandler = (fn, serviceName, methodName) => { return next(appError); } + if (error.status) { + const appError = createError(error.message, error.status, serviceName, methodName, { originalError: error.message, stack: error.stack }); + return next(appError); + } + // For unknown errors, create a server error const appError = createServerError(error.message || "An unexpected error occurred", { originalError: error.message, stack: error.stack }); appError.service = serviceName; appError.method = methodName; appError.stack = error.stack; // Preserve original stack - return next(appError); } }; From 19d3ae119c7391bdd7928a2060713f8cc18a44b8 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 24 Jul 2025 13:51:00 -0700 Subject: [PATCH 04/13] added safe var access --- client/src/Hooks/useMonitorUtils.js | 14 +++--- client/src/Pages/Uptime/Configure/index.jsx | 48 ++++++++++----------- client/src/Utils/NetworkService.js | 24 ----------- 3 files changed, 31 insertions(+), 55 deletions(-) diff --git a/client/src/Hooks/useMonitorUtils.js b/client/src/Hooks/useMonitorUtils.js index 417c6eadd..b57c11a12 100644 --- a/client/src/Hooks/useMonitorUtils.js +++ b/client/src/Hooks/useMonitorUtils.js @@ -6,18 +6,18 @@ const useMonitorUtils = () => { let uptimePercentage = ""; let percentageColor = ""; - if (monitor.uptimePercentage !== undefined) { + if (monitor?.uptimePercentage !== undefined) { uptimePercentage = - monitor.uptimePercentage === 0 + monitor?.uptimePercentage === 0 ? "0" - : (monitor.uptimePercentage * 100).toFixed(2); + : (monitor?.uptimePercentage * 100).toFixed(2); percentageColor = - monitor.uptimePercentage < 0.25 + monitor?.uptimePercentage < 0.25 ? theme.palette.error.main - : monitor.uptimePercentage < 0.5 + : monitor?.uptimePercentage < 0.5 ? theme.palette.warning.main - : monitor.uptimePercentage < 0.75 + : monitor?.uptimePercentage < 0.75 ? theme.palette.success.main : theme.palette.success.main; } @@ -32,7 +32,7 @@ const useMonitorUtils = () => { const determineState = useCallback((monitor) => { if (typeof monitor === "undefined") return "pending"; - if (monitor.isActive === false) return "paused"; + if (monitor?.isActive === false) return "paused"; if (monitor?.status === undefined) return "pending"; return monitor?.status == true ? "up" : "down"; }, []); diff --git a/client/src/Pages/Uptime/Configure/index.jsx b/client/src/Pages/Uptime/Configure/index.jsx index 2dacc26ab..061db6ac0 100644 --- a/client/src/Pages/Uptime/Configure/index.jsx +++ b/client/src/Pages/Uptime/Configure/index.jsx @@ -163,18 +163,18 @@ const Configure = () => { e.preventDefault(); const toSubmit = { - _id: form._id, - url: form.url, - name: form.name, - type: form.type, - matchMethod: form.matchMethod, - expectedValue: form.expectedValue, - jsonPath: form.jsonPath, - interval: form.interval, - teamId: form.teamId, - userId: form.userId, - port: form.port, - ignoreTlsErrors: form.ignoreTlsErrors, + _id: form?._id, + url: form?.url, + name: form?.name, + type: form?.type, + matchMethod: form?.matchMethod, + expectedValue: form?.expectedValue, + jsonPath: form?.jsonPath, + interval: form?.interval, + teamId: form?.teamId, + userId: form?.userId, + port: form?.port, + ignoreTlsErrors: form?.ignoreTlsErrors, }; if (!useAdvancedMatching) { @@ -197,7 +197,7 @@ const Configure = () => { return; } - toSubmit.notifications = form.notifications; + toSubmit.notifications = form?.notifications; await updateMonitor({ monitor: toSubmit, redirect: "/uptime" }); }; @@ -235,7 +235,7 @@ const Configure = () => { component="h1" variant="monitorName" > - {form.name} + {form?.name} { component="h2" variant="monitorUrl" > - {form.url?.replace(/^https?:\/\//, "") || "..."} + {form?.url?.replace(/^https?:\/\//, "") || "..."} { type="number" label={t("portToMonitor")} placeholder="5173" - value={form.port || ""} + value={form?.port || ""} onChange={onChange} error={errors["port"] ? true : false} helperText={errors["port"]} - hidden={form.type !== "port"} + hidden={form?.type !== "port"} /> { @@ -398,7 +398,7 @@ const Configure = () => { control={ @@ -430,12 +430,12 @@ const Configure = () => { isChecked={useAdvancedMatching} onChange={onChange} /> - {form.type === "http" && useAdvancedMatching && ( + {form?.type === "http" && useAdvancedMatching && ( <>