diff --git a/.github/workflows/staging-deploy.yml b/.github/workflows/staging-deploy.yml
index 27b7e8690..84be4e483 100644
--- a/.github/workflows/staging-deploy.yml
+++ b/.github/workflows/staging-deploy.yml
@@ -88,3 +88,4 @@ jobs:
docker compose down
docker compose pull
docker compose up -d
+ docker system prune -af
diff --git a/.gitignore b/.gitignore
index 997dff9c5..74015c392 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
.idea
.vscode
-.VSCodeCounter
\ No newline at end of file
+.VSCodeCounter
+*.sh
+mongo
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 000000000..1535e84cf
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,145 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Project Overview
+
+Checkmate is an open-source uptime and infrastructure monitoring application. It monitors server hardware, uptime, response times, and incidents with real-time alerts. The companion agent [Capture](https://github.com/bluewave-labs/capture) provides infrastructure metrics (CPU, RAM, disk, temperature).
+
+## Development Commands
+
+### Client (React/Vite)
+```bash
+cd client
+npm install
+npm run dev # Start dev server at http://localhost:5173
+npm run build # TypeScript check + production build
+npm run lint # ESLint (strict, max-warnings 0)
+npm run format # Prettier formatting
+npm run format-check # Check formatting
+```
+
+### Server (Node.js/Express)
+```bash
+cd server
+npm install
+npm run dev # Start with hot-reload (nodemon + tsx) at http://localhost:52345
+npm run build # TypeScript compile + path alias resolution
+npm run test # Run Mocha tests with c8 coverage
+npm run lint # ESLint v9
+npm run lint-fix # Auto-fix lint issues
+npm run format # Prettier formatting
+```
+
+### Docker Development
+```bash
+cd docker/dev
+./build_images.sh
+docker run -d -p 27017:27017 -v uptime_mongo_data:/data/db --name uptime_database_mongo mongo:6.0
+```
+
+## Environment Setup
+
+### Server `.env` (minimum required)
+```env
+CLIENT_HOST="http://localhost:5173"
+JWT_SECRET="my_secret_key_change_this"
+DB_CONNECTION_STRING="mongodb://localhost:27017/uptime_db"
+TOKEN_TTL="99d"
+ORIGIN="localhost"
+LOG_LEVEL="debug"
+```
+
+### Client `.env`
+```env
+VITE_APP_API_BASE_URL="http://localhost:52345/api/v1"
+VITE_APP_LOG_LEVEL="debug"
+```
+
+## Architecture
+
+### Monorepo Structure
+- `/client` - React 18 + TypeScript + Vite + MUI frontend
+- `/server` - Node.js 20+ + Express + TypeScript backend
+- `/docker` - Multi-environment Docker configs (dev, staging, prod, arm, mono)
+
+### Backend Layers
+```
+server/src/
+├── controllers/ # Route handlers (authController, monitorController, etc.)
+├── service/ # Business logic
+│ ├── business/ # Core monitoring logic
+│ ├── infrastructure/ # Server/system utilities
+│ └── system/ # App-level settings
+├── db/
+│ ├── models/ # Mongoose schemas (Monitor, Check, Incident, User, etc.)
+│ ├── migration/ # Database migrations (run on startup)
+│ └── modules/ # Database-specific modules
+├── middleware/v1/ # verifyJWT, rateLimiter, sanitization, responseHandler
+├── routes/v1/ # API route definitions
+├── validation/ # Joi input validation schemas
+└── repositories/ # Data access layer
+```
+
+### Frontend Structure
+```
+client/src/
+├── Components/ # Reusable UI components
+├── Pages/ # Page components (Auth, Uptime, Infrastructure, Incidents, etc.)
+├── Features/ # Redux slices (Auth, UI)
+├── Hooks/ # Custom React hooks
+├── Utils/ # Utilities (NetworkService.js is main API client)
+├── Validation/ # Input validation
+└── locales/ # i18n translations
+```
+
+### API
+- Base URL: `/api/v1`
+- Documentation: `http://localhost:52345/api-docs` (Swagger UI)
+- OpenAPI spec: `/server/openapi.json`
+
+### Key Technologies
+- **State Management**: Redux Toolkit + Redux-Persist
+- **Data Fetching**: SWR + Axios
+- **Database**: MongoDB with Mongoose ODM
+- **Queue/Cache**: Redis + BullMQ + Pulse (cron scheduling)
+- **i18n**: i18next + react-i18next (translations via PoEditor)
+
+## Code Conventions
+
+### Internationalization
+All user-facing strings must use the translation function:
+```javascript
+t('your.key') // Never hardcode UI strings
+```
+
+### Branching
+- Always branch from `develop` (not master)
+- Use descriptive names: `feat/add-alerts`, `fix/login-error`
+- PRs target `develop` branch
+
+### Formatting
+- **Client**: Prettier with `printWidth: 90`, tabs, double quotes
+- **Server**: Prettier with `printWidth: 150`, tabs, double quotes
+- Both use ESLint with strict settings
+
+### Testing
+Server tests use Mocha + Chai + Sinon:
+```bash
+npm test # Run all tests with coverage
+npm test -- --grep "pattern" # Run specific tests
+```
+Test files: `server/tests/**/*.test.js`
+
+## Database Models
+
+Key Mongoose models in `/server/src/db/models/`:
+- **Monitor** - Monitoring configuration (website, infrastructure, port, etc.)
+- **Check** - Individual monitoring check results
+- **Incident** - Downtime incidents
+- **User** - User accounts
+- **Team** - Team/workspace management
+- **StatusPage** - Public status pages
+- **Notification** - Alert configuration (email, Discord, Slack, webhooks)
+- **MaintenanceWindow** - Scheduled maintenance periods
+- **AppSettings** - Global application settings
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 2392ea485..656523f88 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -10,6 +10,31 @@ PS: **We work closely with contributors on our [Discord channel](https://discord
---
+
+## 🚀 Quick Setup Checklist
+
+Before you dive in, make sure you have these installed:
+
+```bash
+# Check Node.js (v16+ required)
+node --version
+
+# Check npm
+npm --version
+
+# Check Docker
+docker --version
+
+# Check Git
+git --version
+```
+
+**New to contributing?** Start here:
+1. Pick a [`good-first-issue`](https://github.com/bluewave-labs/checkmate/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
+2. Comment that you'd like to work on it
+3. Follow the [setup guide](#set-up-checkmate-locally) below
+4. Join our [Discord](https://discord.com/invite/NAb6H3UTjK) if you get stuck
+
## Table of contents
- [How do I...?](#how-do-i)
@@ -66,7 +91,7 @@ cd Checkmate
Navigate to the Docker dev directory:
```bash
-cd server/docker/dev
+cd docker/dev
```
Build the Docker images:
@@ -78,13 +103,13 @@ Build the Docker images:
Run MongoDB container:
```bash
-docker run -d -p 27017:27017 -v $(pwd)/mongo/data:/data/db --name uptime_database_mongo uptime_database_mongo
+docker run -d -p 27017:27017 -v uptime_mongo_data:/data/db --name uptime_database_mongo mongo:6.0
```
Navigate back to the root directory:
```bash
-cd ../../..
+cd ../..
```
#### Step 3: Set Up the Backend (Server)
@@ -161,7 +186,7 @@ Start the frontend:
npm run dev
```
-The client will run at `http://localhost:5173`.
+The client will run at `http://localhost:5173`.
#### Step 5: Access the Application
diff --git a/Checkmate.CodeCanvas b/Checkmate.CodeCanvas
new file mode 100644
index 000000000..623ea35bb
--- /dev/null
+++ b/Checkmate.CodeCanvas
@@ -0,0 +1,11325 @@
+{
+ "drawioXML": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n",
+ "fileName": "Checkmate.CodeCanvas",
+ "fileURL": "github",
+ "diagramTemplateVersion": 0.2,
+ "filePath": "Checkmate.CodeCanvas",
+ "repoData": {
+ "client": {
+ "path": "client",
+ "fileName": "client",
+ "cellName": "client",
+ "cellId": "0b23ea66-3e37-4643-b3b9-acb87ea02bae",
+ "visible": true,
+ "children": [
+ "client/src"
+ ]
+ },
+ "client/src": {
+ "path": "client/src",
+ "fileName": "src",
+ "cellName": "src",
+ "cellId": "d3264577-10f0-42fe-9411-7e9df0754d1e",
+ "visible": true,
+ "parentCellId": "0b23ea66-3e37-4643-b3b9-acb87ea02bae",
+ "children": [
+ "client/src/Pages",
+ "client/src/Hooks",
+ "client/src/Utils",
+ "client/src/Routes"
+ ]
+ },
+ "client/src/Hooks": {
+ "path": "client/src/Hooks",
+ "fileName": "Hooks",
+ "cellName": "Hooks",
+ "cellId": "4295a047-076f-4c07-95dd-b3123b6614f7",
+ "visible": true,
+ "parentCellId": "d3264577-10f0-42fe-9411-7e9df0754d1e",
+ "children": [
+ "client/src/Hooks/v1"
+ ]
+ },
+ "client/src/Hooks/v1": {
+ "path": "client/src/Hooks/v1",
+ "fileName": "v1",
+ "cellName": "v1",
+ "cellId": "3bf0c0f9-cbb0-4f0c-8cb3-6e2a4abc0961",
+ "visible": true,
+ "parentCellId": "4295a047-076f-4c07-95dd-b3123b6614f7",
+ "children": [
+ "client/src/Hooks/v1/monitorHooks.js",
+ "client/src/Hooks/v1/useNotifications.js",
+ "client/src/Hooks/v1/checkHooks.js",
+ "client/src/Hooks/v1/settingsHooks.js"
+ ]
+ },
+ "client/src/Hooks/v1/checkHooks.js": {
+ "path": "client/src/Hooks/v1/checkHooks.js",
+ "fileName": "checkHooks.js",
+ "cellName": "checkHooks.js",
+ "cellId": "933a6cdc-349c-4725-b7ae-14cf780bfde8",
+ "visible": true,
+ "parentCellId": "3bf0c0f9-cbb0-4f0c-8cb3-6e2a4abc0961",
+ "children": [
+ "client/src/Hooks/v1/checkHooks.js-simstep-8dc4347c-3e9e-40d7-ae6f-54684f2e6956",
+ "client/src/Hooks/v1/checkHooks.js-simstep-faba07f3-7460-47ae-924d-f92f5b2e821d"
+ ]
+ },
+ "client/src/Hooks/v1/monitorHooks.js": {
+ "path": "client/src/Hooks/v1/monitorHooks.js",
+ "fileName": "monitorHooks.js",
+ "cellName": "monitorHooks.js",
+ "cellId": "595cf0e8-2f86-4543-b51c-b2aeac307f4b",
+ "visible": true,
+ "parentCellId": "3bf0c0f9-cbb0-4f0c-8cb3-6e2a4abc0961"
+ },
+ "client/src/Hooks/v1/settingsHooks.js": {
+ "path": "client/src/Hooks/v1/settingsHooks.js",
+ "fileName": "settingsHooks.js",
+ "cellName": "settingsHooks.js",
+ "cellId": "8ee5d524-e61d-4e39-a729-babf54929def",
+ "visible": true,
+ "parentCellId": "3bf0c0f9-cbb0-4f0c-8cb3-6e2a4abc0961",
+ "children": [
+ "client/src/Hooks/v1/settingsHooks.js-simstep-d7098072-9a83-4369-8176-9646ba34e186",
+ "client/src/Hooks/v1/settingsHooks.js-simstep-a157bd07-b969-44f8-914c-3d8393c1605a"
+ ]
+ },
+ "client/src/Hooks/v1/useNotifications.js": {
+ "path": "client/src/Hooks/v1/useNotifications.js",
+ "fileName": "useNotifications.js",
+ "cellName": "useNotifications.js",
+ "cellId": "cd2428fd-6545-48dd-b685-5261311b7532",
+ "visible": true,
+ "parentCellId": "3bf0c0f9-cbb0-4f0c-8cb3-6e2a4abc0961",
+ "children": [
+ "client/src/Hooks/v1/useNotifications.js-simstep-383c6be8-aa51-4398-9ae3-21035413f73a"
+ ]
+ },
+ "client/src/Pages": {
+ "path": "client/src/Pages",
+ "fileName": "Pages",
+ "cellName": "Pages",
+ "cellId": "f0feab85-316a-41b5-a2f1-629220018846",
+ "visible": true,
+ "parentCellId": "d3264577-10f0-42fe-9411-7e9df0754d1e",
+ "children": [
+ "client/src/Pages/v1"
+ ]
+ },
+ "client/src/Pages/v1": {
+ "path": "client/src/Pages/v1",
+ "fileName": "v1",
+ "cellName": "v1",
+ "cellId": "1a083b4a-787c-4eb4-8029-6700de987679",
+ "visible": true,
+ "parentCellId": "f0feab85-316a-41b5-a2f1-629220018846",
+ "children": [
+ "client/src/Pages/v1/Uptime",
+ "client/src/Pages/v1/Infrastructure",
+ "client/src/Pages/v1/Notifications",
+ "client/src/Pages/v1/StatusPage",
+ "client/src/Pages/v1/PageSpeed",
+ "client/src/Pages/v1/Maintenance",
+ "client/src/Pages/v1/Account",
+ "client/src/Pages/v1/Settings",
+ "client/src/Pages/v1/Logs"
+ ]
+ },
+ "client/src/Pages/v1/Account": {
+ "path": "client/src/Pages/v1/Account",
+ "fileName": "Account",
+ "cellName": "Account",
+ "cellId": "3cad7666-0d1a-4e31-b6ca-70d9c3a46b19",
+ "visible": true,
+ "parentCellId": "1a083b4a-787c-4eb4-8029-6700de987679",
+ "children": [
+ "client/src/Pages/v1/Account/index.jsx",
+ "client/src/Pages/v1/Account/components"
+ ]
+ },
+ "client/src/Pages/v1/Account/components": {
+ "path": "client/src/Pages/v1/Account/components",
+ "fileName": "components",
+ "cellName": "components",
+ "cellId": "425a4c15-043f-4d05-8832-da00e18f97b9",
+ "visible": true,
+ "parentCellId": "3cad7666-0d1a-4e31-b6ca-70d9c3a46b19",
+ "children": [
+ "client/src/Pages/v1/Account/components/TeamPanel.jsx"
+ ]
+ },
+ "client/src/Pages/v1/Account/components/TeamPanel.jsx": {
+ "path": "client/src/Pages/v1/Account/components/TeamPanel.jsx",
+ "fileName": "TeamPanel.jsx",
+ "cellName": "TeamPanel.jsx",
+ "cellId": "1145f351-2257-411d-86f7-30bf8c26ad68",
+ "visible": true,
+ "parentCellId": "425a4c15-043f-4d05-8832-da00e18f97b9",
+ "children": [
+ "client/src/Pages/v1/Account/components/TeamPanel.jsx-simstep-eebaca25-d588-49c4-a35c-9abc82fde580",
+ "client/src/Pages/v1/Account/components/TeamPanel.jsx-simstep-700f35e0-b39d-417c-b0a8-0612feade977"
+ ]
+ },
+ "client/src/Pages/v1/Account/index.jsx": {
+ "path": "client/src/Pages/v1/Account/index.jsx",
+ "fileName": "index.jsx",
+ "cellName": "index.jsx",
+ "cellId": "33960806-1b9e-4d61-a163-e4bebf82d87a",
+ "visible": true,
+ "parentCellId": "3cad7666-0d1a-4e31-b6ca-70d9c3a46b19",
+ "children": [
+ "client/src/Pages/v1/Account/index.jsx-simstep-2d5a952a-ce62-4948-9309-96b28ad74e59"
+ ]
+ },
+ "client/src/Pages/v1/Infrastructure": {
+ "path": "client/src/Pages/v1/Infrastructure",
+ "fileName": "Infrastructure",
+ "cellName": "Infrastructure",
+ "cellId": "3f26b33e-477f-41df-a78a-3e3e4f4209ae",
+ "visible": true,
+ "parentCellId": "1a083b4a-787c-4eb4-8029-6700de987679",
+ "children": [
+ "client/src/Pages/v1/Infrastructure/Create",
+ "client/src/Pages/v1/Infrastructure/Details"
+ ]
+ },
+ "client/src/Pages/v1/Infrastructure/Create": {
+ "path": "client/src/Pages/v1/Infrastructure/Create",
+ "fileName": "Create",
+ "cellName": "Create",
+ "cellId": "74019e8a-3148-4a32-aae2-5afd16f3eb9b",
+ "visible": true,
+ "parentCellId": "3f26b33e-477f-41df-a78a-3e3e4f4209ae",
+ "children": [
+ "client/src/Pages/v1/Infrastructure/Create/Components"
+ ]
+ },
+ "client/src/Pages/v1/Infrastructure/Create/Components": {
+ "path": "client/src/Pages/v1/Infrastructure/Create/Components",
+ "fileName": "Components",
+ "cellName": "Components",
+ "cellId": "ceded1fc-87a9-43c8-a756-746be3d4ad8d",
+ "visible": true,
+ "parentCellId": "74019e8a-3148-4a32-aae2-5afd16f3eb9b",
+ "children": [
+ "client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx"
+ ]
+ },
+ "client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx": {
+ "path": "client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx",
+ "fileName": "CustomAlertsSection.jsx",
+ "cellName": "CustomAlertsSection.jsx",
+ "cellId": "83800be3-2a0e-4419-b379-3f995f4050e7",
+ "visible": true,
+ "parentCellId": "ceded1fc-87a9-43c8-a756-746be3d4ad8d",
+ "children": [
+ "client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx-simstep-80dd889a-1fc9-4d56-b9cf-5b45bf1110b1"
+ ]
+ },
+ "client/src/Pages/v1/Infrastructure/Details": {
+ "path": "client/src/Pages/v1/Infrastructure/Details",
+ "fileName": "Details",
+ "cellName": "Details",
+ "cellId": "591128f9-0994-4e01-a1ab-f23357f97aff",
+ "visible": true,
+ "parentCellId": "3f26b33e-477f-41df-a78a-3e3e4f4209ae",
+ "children": [
+ "client/src/Pages/v1/Infrastructure/Details/index.jsx",
+ "client/src/Pages/v1/Infrastructure/Details/Components"
+ ]
+ },
+ "client/src/Pages/v1/Infrastructure/Details/Components": {
+ "path": "client/src/Pages/v1/Infrastructure/Details/Components",
+ "fileName": "Components",
+ "cellName": "Components",
+ "cellId": "e97d4e61-483e-4ac5-85a1-0ff72fcdd449",
+ "visible": true,
+ "parentCellId": "591128f9-0994-4e01-a1ab-f23357f97aff",
+ "children": [
+ "client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes"
+ ]
+ },
+ "client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes": {
+ "path": "client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes",
+ "fileName": "GaugeBoxes",
+ "cellName": "GaugeBoxes",
+ "cellId": "6a7c7720-a399-4d4d-8225-36e0b2f59731",
+ "visible": true,
+ "parentCellId": "e97d4e61-483e-4ac5-85a1-0ff72fcdd449",
+ "children": [
+ "client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/index.jsx"
+ ]
+ },
+ "client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/index.jsx": {
+ "path": "client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/index.jsx",
+ "fileName": "index.jsx",
+ "cellName": "index.jsx",
+ "cellId": "8fe3d890-3605-407d-b06c-1d890a354e46",
+ "visible": true,
+ "parentCellId": "6a7c7720-a399-4d4d-8225-36e0b2f59731",
+ "children": [
+ "client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/index.jsx-simstep-6eedce82-b9c8-4022-95e5-3a6e00628105"
+ ]
+ },
+ "client/src/Pages/v1/Infrastructure/Details/index.jsx": {
+ "path": "client/src/Pages/v1/Infrastructure/Details/index.jsx",
+ "fileName": "index.jsx",
+ "cellName": "index.jsx",
+ "cellId": "e292c552-b3df-4bac-bd9a-8f44c260a5aa",
+ "visible": true,
+ "parentCellId": "591128f9-0994-4e01-a1ab-f23357f97aff",
+ "children": [
+ "client/src/Pages/v1/Infrastructure/Details/index.jsx-simstep-c27365db-9db6-4cc9-96f0-4f046fd38562"
+ ]
+ },
+ "client/src/Pages/v1/Logs": {
+ "path": "client/src/Pages/v1/Logs",
+ "fileName": "Logs",
+ "cellName": "Logs",
+ "cellId": "1716ec41-8b42-4386-9850-55b8a607638e",
+ "visible": true,
+ "parentCellId": "1a083b4a-787c-4eb4-8029-6700de987679",
+ "children": [
+ "client/src/Pages/v1/Logs/Diagnostics"
+ ]
+ },
+ "client/src/Pages/v1/Logs/Diagnostics": {
+ "path": "client/src/Pages/v1/Logs/Diagnostics",
+ "fileName": "Diagnostics",
+ "cellName": "Diagnostics",
+ "cellId": "e0cb598c-e300-4fd3-8554-db7ffd6201f1",
+ "visible": true,
+ "parentCellId": "1716ec41-8b42-4386-9850-55b8a607638e",
+ "children": [
+ "client/src/Pages/v1/Logs/Diagnostics/index.jsx"
+ ]
+ },
+ "client/src/Pages/v1/Logs/Diagnostics/index.jsx": {
+ "path": "client/src/Pages/v1/Logs/Diagnostics/index.jsx",
+ "fileName": "index.jsx",
+ "cellName": "index.jsx",
+ "cellId": "87e0e2ea-4a6d-460d-95bc-31e2efd9c9cf",
+ "visible": true,
+ "parentCellId": "e0cb598c-e300-4fd3-8554-db7ffd6201f1",
+ "children": [
+ "client/src/Pages/v1/Logs/Diagnostics/index.jsx-simstep-a9b24b5f-534b-4baf-a185-bac7c32a1c42",
+ "client/src/Pages/v1/Logs/Diagnostics/index.jsx-simstep-e6c94f52-a599-4568-a06f-2db18f963817"
+ ]
+ },
+ "client/src/Pages/v1/Maintenance": {
+ "path": "client/src/Pages/v1/Maintenance",
+ "fileName": "Maintenance",
+ "cellName": "Maintenance",
+ "cellId": "8021b55d-2d3a-4416-b598-e134e76c9434",
+ "visible": true,
+ "parentCellId": "1a083b4a-787c-4eb4-8029-6700de987679",
+ "children": [
+ "client/src/Pages/v1/Maintenance/CreateMaintenance",
+ "client/src/Pages/v1/Maintenance/index.jsx",
+ "client/src/Pages/v1/Maintenance/MaintenanceTable"
+ ]
+ },
+ "client/src/Pages/v1/Maintenance/CreateMaintenance": {
+ "path": "client/src/Pages/v1/Maintenance/CreateMaintenance",
+ "fileName": "CreateMaintenance",
+ "cellName": "CreateMaintenance",
+ "cellId": "6f7119ac-d65a-4fdf-a6f2-31052e0f5549",
+ "visible": true,
+ "parentCellId": "8021b55d-2d3a-4416-b598-e134e76c9434",
+ "children": [
+ "client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx",
+ "client/src/Pages/v1/Maintenance/CreateMaintenance/hooks"
+ ]
+ },
+ "client/src/Pages/v1/Maintenance/CreateMaintenance/hooks": {
+ "path": "client/src/Pages/v1/Maintenance/CreateMaintenance/hooks",
+ "fileName": "hooks",
+ "cellName": "hooks",
+ "cellId": "015a17fc-3fcf-45e0-8936-19f5e2ca86a3",
+ "visible": true,
+ "parentCellId": "6f7119ac-d65a-4fdf-a6f2-31052e0f5549",
+ "children": [
+ "client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx"
+ ]
+ },
+ "client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx": {
+ "path": "client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx",
+ "fileName": "useMaintenanceActions.jsx",
+ "cellName": "useMaintenanceActions.jsx",
+ "cellId": "6ad0396e-9b38-4f5d-9ad9-6c9fcb4434f4",
+ "visible": true,
+ "parentCellId": "015a17fc-3fcf-45e0-8936-19f5e2ca86a3",
+ "children": [
+ "client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx-simstep-5e0d7085-099b-478a-afaa-47ad4dd07043"
+ ]
+ },
+ "client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx": {
+ "path": "client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx",
+ "fileName": "index.jsx",
+ "cellName": "index.jsx",
+ "cellId": "92c244d2-e6a7-4804-a643-1e8d36baf9c6",
+ "visible": true,
+ "parentCellId": "6f7119ac-d65a-4fdf-a6f2-31052e0f5549",
+ "children": [
+ "client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx-simstep-2401ef7a-8d8e-4056-868a-547ab213daf1",
+ "client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx-simstep-ebe8fd52-f2a1-4a98-a71b-b04d6f4cdadc"
+ ]
+ },
+ "client/src/Pages/v1/Maintenance/MaintenanceTable": {
+ "path": "client/src/Pages/v1/Maintenance/MaintenanceTable",
+ "fileName": "MaintenanceTable",
+ "cellName": "MaintenanceTable",
+ "cellId": "5ad4d33f-0932-479c-bb57-22ce6405758f",
+ "visible": true,
+ "parentCellId": "8021b55d-2d3a-4416-b598-e134e76c9434",
+ "children": [
+ "client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx"
+ ]
+ },
+ "client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx": {
+ "path": "client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx",
+ "fileName": "index.jsx",
+ "cellName": "index.jsx",
+ "cellId": "e7ec5ca9-6d25-4bf2-9695-639556f7d141",
+ "visible": true,
+ "parentCellId": "5ad4d33f-0932-479c-bb57-22ce6405758f",
+ "children": [
+ "client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx-simstep-acf97639-e291-4ca6-81a6-d0487397f42a"
+ ]
+ },
+ "client/src/Pages/v1/Maintenance/index.jsx": {
+ "path": "client/src/Pages/v1/Maintenance/index.jsx",
+ "fileName": "index.jsx",
+ "cellName": "index.jsx",
+ "cellId": "1eae6263-aaa2-4206-b92e-1e67f529c1c9",
+ "visible": true,
+ "parentCellId": "8021b55d-2d3a-4416-b598-e134e76c9434",
+ "children": [
+ "client/src/Pages/v1/Maintenance/index.jsx-simstep-82b5ea14-bb00-4511-aba4-76196029fd81"
+ ]
+ },
+ "client/src/Pages/v1/Notifications": {
+ "path": "client/src/Pages/v1/Notifications",
+ "fileName": "Notifications",
+ "cellName": "Notifications",
+ "cellId": "b3575a92-df07-4cf7-810f-8e5e0672d666",
+ "visible": true,
+ "parentCellId": "1a083b4a-787c-4eb4-8029-6700de987679",
+ "children": [
+ "client/src/Pages/v1/Notifications/create"
+ ]
+ },
+ "client/src/Pages/v1/Notifications/create": {
+ "path": "client/src/Pages/v1/Notifications/create",
+ "fileName": "create",
+ "cellName": "create",
+ "cellId": "a6fb23f0-73bc-40ee-bb3c-ed93605a2bcb",
+ "visible": true,
+ "parentCellId": "b3575a92-df07-4cf7-810f-8e5e0672d666",
+ "children": [
+ "client/src/Pages/v1/Notifications/create/index.jsx"
+ ]
+ },
+ "client/src/Pages/v1/Notifications/create/index.jsx": {
+ "path": "client/src/Pages/v1/Notifications/create/index.jsx",
+ "fileName": "index.jsx",
+ "cellName": "index.jsx",
+ "cellId": "b71b02fb-d08e-4fcf-b959-de1b9547d9c2",
+ "visible": true,
+ "parentCellId": "a6fb23f0-73bc-40ee-bb3c-ed93605a2bcb",
+ "children": [
+ "client/src/Pages/v1/Notifications/create/index.jsx-simstep-e924ec15-1bea-4e3b-befb-a8bf30cbf740"
+ ]
+ },
+ "client/src/Pages/v1/PageSpeed": {
+ "path": "client/src/Pages/v1/PageSpeed",
+ "fileName": "PageSpeed",
+ "cellName": "PageSpeed",
+ "cellId": "043d3dac-d4bb-449b-a3a7-270d53b66dac",
+ "visible": true,
+ "parentCellId": "1a083b4a-787c-4eb4-8029-6700de987679",
+ "children": [
+ "client/src/Pages/v1/PageSpeed/Create",
+ "client/src/Pages/v1/PageSpeed/Details"
+ ]
+ },
+ "client/src/Pages/v1/PageSpeed/Create": {
+ "path": "client/src/Pages/v1/PageSpeed/Create",
+ "fileName": "Create",
+ "cellName": "Create",
+ "cellId": "414c500b-7b68-42ba-8110-e855efbee750",
+ "visible": true,
+ "parentCellId": "043d3dac-d4bb-449b-a3a7-270d53b66dac",
+ "children": [
+ "client/src/Pages/v1/PageSpeed/Create/index.jsx"
+ ]
+ },
+ "client/src/Pages/v1/PageSpeed/Create/index.jsx": {
+ "path": "client/src/Pages/v1/PageSpeed/Create/index.jsx",
+ "fileName": "index.jsx",
+ "cellName": "index.jsx",
+ "cellId": "5ffe0baa-054a-4e5b-8b29-1254f07ea975",
+ "visible": true,
+ "parentCellId": "414c500b-7b68-42ba-8110-e855efbee750",
+ "children": [
+ "client/src/Pages/v1/PageSpeed/Create/index.jsx-simstep-b0cc6e6a-961e-4ea9-aa8a-6bfa44d6ba12"
+ ]
+ },
+ "client/src/Pages/v1/PageSpeed/Details": {
+ "path": "client/src/Pages/v1/PageSpeed/Details",
+ "fileName": "Details",
+ "cellName": "Details",
+ "cellId": "581b4919-8b0c-4f0d-ae67-ad6de11d8c8b",
+ "visible": true,
+ "parentCellId": "043d3dac-d4bb-449b-a3a7-270d53b66dac",
+ "children": [
+ "client/src/Pages/v1/PageSpeed/Details/index.jsx"
+ ]
+ },
+ "client/src/Pages/v1/PageSpeed/Details/index.jsx": {
+ "path": "client/src/Pages/v1/PageSpeed/Details/index.jsx",
+ "fileName": "index.jsx",
+ "cellName": "index.jsx",
+ "cellId": "c4a7bb58-1d9c-4690-b6b8-460c1cc33702",
+ "visible": true,
+ "parentCellId": "581b4919-8b0c-4f0d-ae67-ad6de11d8c8b",
+ "children": [
+ "client/src/Pages/v1/PageSpeed/Details/index.jsx-simstep-0ec0eb06-05eb-4204-b077-9e47eb2cc316"
+ ]
+ },
+ "client/src/Pages/v1/Settings": {
+ "path": "client/src/Pages/v1/Settings",
+ "fileName": "Settings",
+ "cellName": "Settings",
+ "cellId": "50f77798-f41b-4f5b-b096-7afc921a0419",
+ "visible": true,
+ "parentCellId": "1a083b4a-787c-4eb4-8029-6700de987679",
+ "children": [
+ "client/src/Pages/v1/Settings/index.jsx",
+ "client/src/Pages/v1/Settings/SettingsEmail.jsx"
+ ]
+ },
+ "client/src/Pages/v1/Settings/SettingsEmail.jsx": {
+ "path": "client/src/Pages/v1/Settings/SettingsEmail.jsx",
+ "fileName": "SettingsEmail.jsx",
+ "cellName": "SettingsEmail.jsx",
+ "cellId": "d8aa5b9e-f2a7-44c8-8813-d79c3f3238ee",
+ "visible": true,
+ "parentCellId": "50f77798-f41b-4f5b-b096-7afc921a0419",
+ "children": [
+ "client/src/Pages/v1/Settings/SettingsEmail.jsx-simstep-ae5c5146-4dbb-4612-9d23-086e57f72474"
+ ]
+ },
+ "client/src/Pages/v1/Settings/index.jsx": {
+ "path": "client/src/Pages/v1/Settings/index.jsx",
+ "fileName": "index.jsx",
+ "cellName": "index.jsx",
+ "cellId": "d4b34195-7e1e-47a0-9303-603987c81f95",
+ "visible": true,
+ "parentCellId": "50f77798-f41b-4f5b-b096-7afc921a0419",
+ "children": [
+ "client/src/Pages/v1/Settings/index.jsx-simstep-64e9f1af-13b7-4361-a2a9-3f45760e316d"
+ ]
+ },
+ "client/src/Pages/v1/StatusPage": {
+ "path": "client/src/Pages/v1/StatusPage",
+ "fileName": "StatusPage",
+ "cellName": "StatusPage",
+ "cellId": "9d662180-8b65-4426-ad37-cfed4bc01fb4",
+ "visible": true,
+ "parentCellId": "1a083b4a-787c-4eb4-8029-6700de987679",
+ "children": [
+ "client/src/Pages/v1/StatusPage/Create",
+ "client/src/Pages/v1/StatusPage/Status"
+ ]
+ },
+ "client/src/Pages/v1/StatusPage/Create": {
+ "path": "client/src/Pages/v1/StatusPage/Create",
+ "fileName": "Create",
+ "cellName": "Create",
+ "cellId": "08602a28-f073-484e-9403-7eb798126dd4",
+ "visible": true,
+ "parentCellId": "9d662180-8b65-4426-ad37-cfed4bc01fb4",
+ "children": [
+ "client/src/Pages/v1/StatusPage/Create/index.jsx",
+ "client/src/Pages/v1/StatusPage/Create/Hooks"
+ ]
+ },
+ "client/src/Pages/v1/StatusPage/Create/Hooks": {
+ "path": "client/src/Pages/v1/StatusPage/Create/Hooks",
+ "fileName": "Hooks",
+ "cellName": "Hooks",
+ "cellId": "56f6775e-9843-4ca2-bc8c-1c263b6c35e9",
+ "visible": true,
+ "parentCellId": "08602a28-f073-484e-9403-7eb798126dd4",
+ "children": [
+ "client/src/Pages/v1/StatusPage/Create/Hooks/useCreateStatusPage.jsx"
+ ]
+ },
+ "client/src/Pages/v1/StatusPage/Create/Hooks/useCreateStatusPage.jsx": {
+ "path": "client/src/Pages/v1/StatusPage/Create/Hooks/useCreateStatusPage.jsx",
+ "fileName": "useCreateStatusPage.jsx",
+ "cellName": "useCreateStatusPage.jsx",
+ "cellId": "3945e05f-2d99-4d6d-9a3e-b6c75ea3c45a",
+ "visible": true,
+ "parentCellId": "56f6775e-9843-4ca2-bc8c-1c263b6c35e9",
+ "children": [
+ "client/src/Pages/v1/StatusPage/Create/Hooks/useCreateStatusPage.jsx-simstep-3c99ae6a-b671-4f47-bed0-ddccd8ff5d35"
+ ]
+ },
+ "client/src/Pages/v1/StatusPage/Create/index.jsx": {
+ "path": "client/src/Pages/v1/StatusPage/Create/index.jsx",
+ "fileName": "index.jsx",
+ "cellName": "index.jsx",
+ "cellId": "e2d2bbf6-dffd-4edf-8c5d-34088d9d4745",
+ "visible": true,
+ "parentCellId": "08602a28-f073-484e-9403-7eb798126dd4",
+ "children": [
+ "client/src/Pages/v1/StatusPage/Create/index.jsx-simstep-de7b5d1f-b370-42a4-a410-4178f1e2d3a6"
+ ]
+ },
+ "client/src/Pages/v1/StatusPage/Status": {
+ "path": "client/src/Pages/v1/StatusPage/Status",
+ "fileName": "Status",
+ "cellName": "Status",
+ "cellId": "8ef70566-37d9-48fb-b5e8-37e5b68768a2",
+ "visible": true,
+ "parentCellId": "9d662180-8b65-4426-ad37-cfed4bc01fb4",
+ "children": [
+ "client/src/Pages/v1/StatusPage/Status/index.jsx",
+ "client/src/Pages/v1/StatusPage/Status/Hooks"
+ ]
+ },
+ "client/src/Pages/v1/StatusPage/Status/Hooks": {
+ "path": "client/src/Pages/v1/StatusPage/Status/Hooks",
+ "fileName": "Hooks",
+ "cellName": "Hooks",
+ "cellId": "79da0721-d810-4f7f-9cf9-3ea462bf0bff",
+ "visible": true,
+ "parentCellId": "8ef70566-37d9-48fb-b5e8-37e5b68768a2",
+ "children": [
+ "client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageFetch.jsx"
+ ]
+ },
+ "client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageFetch.jsx": {
+ "path": "client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageFetch.jsx",
+ "fileName": "useStatusPageFetch.jsx",
+ "cellName": "useStatusPageFetch.jsx",
+ "cellId": "5b2cdab8-66f1-40a1-9621-0efaf54e04ae",
+ "visible": true,
+ "parentCellId": "79da0721-d810-4f7f-9cf9-3ea462bf0bff",
+ "children": [
+ "client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageFetch.jsx-simstep-b57bb249-9066-454e-aa8c-89108fdfcc2e"
+ ]
+ },
+ "client/src/Pages/v1/StatusPage/Status/index.jsx": {
+ "path": "client/src/Pages/v1/StatusPage/Status/index.jsx",
+ "fileName": "index.jsx",
+ "cellName": "index.jsx",
+ "cellId": "e1e6f5e0-fd8c-4260-aa4f-cdb096c6b1c3",
+ "visible": true,
+ "parentCellId": "8ef70566-37d9-48fb-b5e8-37e5b68768a2",
+ "children": [
+ "client/src/Pages/v1/StatusPage/Status/index.jsx-simstep-3d51e811-a659-442e-97e0-00798cd15647"
+ ]
+ },
+ "client/src/Pages/v1/Uptime": {
+ "path": "client/src/Pages/v1/Uptime",
+ "fileName": "Uptime",
+ "cellName": "Uptime",
+ "cellId": "7090fcdb-09be-4c85-86fd-e73553bbb2ff",
+ "visible": true,
+ "parentCellId": "1a083b4a-787c-4eb4-8029-6700de987679",
+ "children": [
+ "client/src/Pages/v1/Uptime/Create",
+ "client/src/Pages/v1/Uptime/Details",
+ "client/src/Pages/v1/Uptime/BulkImport"
+ ]
+ },
+ "client/src/Pages/v1/Uptime/BulkImport": {
+ "path": "client/src/Pages/v1/Uptime/BulkImport",
+ "fileName": "BulkImport",
+ "cellName": "BulkImport",
+ "cellId": "56dae13e-5735-4089-a9a4-e18be315aff7",
+ "visible": true,
+ "parentCellId": "7090fcdb-09be-4c85-86fd-e73553bbb2ff",
+ "children": [
+ "client/src/Pages/v1/Uptime/BulkImport/index.jsx",
+ "client/src/Pages/v1/Uptime/BulkImport/Upload.jsx"
+ ]
+ },
+ "client/src/Pages/v1/Uptime/BulkImport/Upload.jsx": {
+ "path": "client/src/Pages/v1/Uptime/BulkImport/Upload.jsx",
+ "fileName": "Upload.jsx",
+ "cellName": "Upload.jsx",
+ "cellId": "cd84476a-1c8b-4781-811f-58fb9944365b",
+ "visible": true,
+ "parentCellId": "56dae13e-5735-4089-a9a4-e18be315aff7",
+ "children": [
+ "client/src/Pages/v1/Uptime/BulkImport/Upload.jsx-simstep-bbd04d55-fc6a-4ba8-bcde-5a4dc263d6eb"
+ ]
+ },
+ "client/src/Pages/v1/Uptime/BulkImport/index.jsx": {
+ "path": "client/src/Pages/v1/Uptime/BulkImport/index.jsx",
+ "fileName": "index.jsx",
+ "cellName": "index.jsx",
+ "cellId": "29b6ad3c-9538-47bc-8796-aab942dc4b71",
+ "visible": true,
+ "parentCellId": "56dae13e-5735-4089-a9a4-e18be315aff7",
+ "children": [
+ "client/src/Pages/v1/Uptime/BulkImport/index.jsx-simstep-0139fcaa-eb46-4176-b7bd-1cb15e7f1f4c",
+ "client/src/Pages/v1/Uptime/BulkImport/index.jsx-simstep-72b3f16d-290d-4a61-8748-2ad90d63ec68"
+ ]
+ },
+ "client/src/Pages/v1/Uptime/Create": {
+ "path": "client/src/Pages/v1/Uptime/Create",
+ "fileName": "Create",
+ "cellName": "Create",
+ "cellId": "3dba5acc-cf25-40b7-ad21-9cd715454cd5",
+ "visible": true,
+ "parentCellId": "7090fcdb-09be-4c85-86fd-e73553bbb2ff",
+ "children": [
+ "client/src/Pages/v1/Uptime/Create/index.jsx"
+ ]
+ },
+ "client/src/Pages/v1/Uptime/Create/index.jsx": {
+ "path": "client/src/Pages/v1/Uptime/Create/index.jsx",
+ "fileName": "index.jsx",
+ "cellName": "index.jsx",
+ "cellId": "391ad99e-3114-4f54-a6ca-fd89b33c7e06",
+ "visible": true,
+ "parentCellId": "3dba5acc-cf25-40b7-ad21-9cd715454cd5",
+ "children": [
+ "client/src/Pages/v1/Uptime/Create/index.jsx-simstep-ab5de0b9-c70d-492c-98d9-8ddee66284ad",
+ "client/src/Pages/v1/Uptime/Create/index.jsx-simstep-e02ddf57-270e-4f71-b4c4-d5183bb7879a"
+ ]
+ },
+ "client/src/Pages/v1/Uptime/Details": {
+ "path": "client/src/Pages/v1/Uptime/Details",
+ "fileName": "Details",
+ "cellName": "Details",
+ "cellId": "273f3009-69d0-46fe-af57-3cd94a24b55b",
+ "visible": true,
+ "parentCellId": "7090fcdb-09be-4c85-86fd-e73553bbb2ff",
+ "children": [
+ "client/src/Pages/v1/Uptime/Details/index.jsx",
+ "client/src/Pages/v1/Uptime/Details/Components"
+ ]
+ },
+ "client/src/Pages/v1/Uptime/Details/Components": {
+ "path": "client/src/Pages/v1/Uptime/Details/Components",
+ "fileName": "Components",
+ "cellName": "Components",
+ "cellId": "6aed78bb-62f1-4ac1-9b9a-9d5977e87404",
+ "visible": true,
+ "parentCellId": "273f3009-69d0-46fe-af57-3cd94a24b55b",
+ "children": [
+ "client/src/Pages/v1/Uptime/Details/Components/ResponseTable"
+ ]
+ },
+ "client/src/Pages/v1/Uptime/Details/Components/ResponseTable": {
+ "path": "client/src/Pages/v1/Uptime/Details/Components/ResponseTable",
+ "fileName": "ResponseTable",
+ "cellName": "ResponseTable",
+ "cellId": "5ac0878f-d238-4763-8464-a85b4885a40f",
+ "visible": true,
+ "parentCellId": "6aed78bb-62f1-4ac1-9b9a-9d5977e87404",
+ "children": [
+ "client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx"
+ ]
+ },
+ "client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx": {
+ "path": "client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx",
+ "fileName": "index.jsx",
+ "cellName": "index.jsx",
+ "cellId": "d3f74ac7-3b9a-4cae-8d77-a0d5d605bb27",
+ "visible": true,
+ "parentCellId": "5ac0878f-d238-4763-8464-a85b4885a40f",
+ "children": [
+ "client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx-simstep-2b1651cd-2e70-4cc6-b2bb-23f94fa73d66"
+ ]
+ },
+ "client/src/Pages/v1/Uptime/Details/index.jsx": {
+ "path": "client/src/Pages/v1/Uptime/Details/index.jsx",
+ "fileName": "index.jsx",
+ "cellName": "index.jsx",
+ "cellId": "1807d5b9-54be-430b-8997-cada4dd9b521",
+ "visible": true,
+ "parentCellId": "273f3009-69d0-46fe-af57-3cd94a24b55b",
+ "children": [
+ "client/src/Pages/v1/Uptime/Details/index.jsx-simstep-35195328-9847-4678-b014-9331553f0d19"
+ ]
+ },
+ "client/src/Routes": {
+ "path": "client/src/Routes",
+ "fileName": "Routes",
+ "cellName": "Routes",
+ "cellId": "987f145b-5df6-4c30-bf1c-2a2d2b0e5cdc",
+ "visible": true,
+ "parentCellId": "d3264577-10f0-42fe-9411-7e9df0754d1e",
+ "children": [
+ "client/src/Routes/index.jsx"
+ ]
+ },
+ "client/src/Routes/index.jsx": {
+ "path": "client/src/Routes/index.jsx",
+ "fileName": "index.jsx",
+ "cellName": "index.jsx",
+ "cellId": "c4d7e7dd-75b1-44f1-a34a-6ed290a8e562",
+ "visible": true,
+ "parentCellId": "987f145b-5df6-4c30-bf1c-2a2d2b0e5cdc",
+ "children": [
+ "client/src/Routes/index.jsx-simstep-132354d7-df7a-4e03-9d00-16d54f836a1b",
+ "client/src/Routes/index.jsx-simstep-a29da01e-8c5a-4e6d-8d3c-e49af528e4fa"
+ ]
+ },
+ "client/src/Utils": {
+ "path": "client/src/Utils",
+ "fileName": "Utils",
+ "cellName": "Utils",
+ "cellId": "02b07589-ec21-4366-b7d2-9071e8f3bad8",
+ "visible": true,
+ "parentCellId": "d3264577-10f0-42fe-9411-7e9df0754d1e",
+ "children": [
+ "client/src/Utils/NetworkService.js"
+ ]
+ },
+ "client/src/Utils/NetworkService.js": {
+ "path": "client/src/Utils/NetworkService.js",
+ "fileName": "NetworkService.js",
+ "cellName": "NetworkService.js",
+ "cellId": "16731e9d-3f73-4055-9d79-5ca9be679f68",
+ "visible": true,
+ "parentCellId": "02b07589-ec21-4366-b7d2-9071e8f3bad8",
+ "children": [
+ "client/src/Utils/NetworkService.js-simstep-14e9986b-c6ce-475e-97ed-da1fa2b6968d",
+ "client/src/Utils/NetworkService.js-simstep-2fc087b3-033d-4cf5-81f2-e35b99a14bce"
+ ]
+ },
+ "server": {
+ "path": "server",
+ "fileName": "server",
+ "cellName": "server",
+ "cellId": "98af25c7-efc2-43d2-a273-c8da4e1038ad",
+ "visible": true,
+ "children": [
+ "server/src"
+ ]
+ },
+ "server/src": {
+ "path": "server/src",
+ "fileName": "src",
+ "cellName": "src",
+ "cellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be",
+ "visible": true,
+ "parentCellId": "98af25c7-efc2-43d2-a273-c8da4e1038ad",
+ "children": [
+ "server/src/routes",
+ "server/src/controllers",
+ "server/src/service",
+ "server/src/db",
+ "server/src/config",
+ "server/src/middleware"
+ ]
+ },
+ "server/src/config": {
+ "path": "server/src/config",
+ "fileName": "config",
+ "cellName": "config",
+ "cellId": "fe47576c-c186-4950-82a5-b2fd24eb2561",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be",
+ "children": [
+ "server/src/config/routes.js"
+ ]
+ },
+ "server/src/config/routes.js": {
+ "path": "server/src/config/routes.js",
+ "fileName": "routes.js",
+ "cellName": "routes.js",
+ "cellId": "cb53d612-9b89-47da-911c-165d0382a7d8",
+ "visible": true,
+ "parentCellId": "fe47576c-c186-4950-82a5-b2fd24eb2561",
+ "children": [
+ "server/src/config/routes.js-simstep-3e93ef65-8e2c-405b-8dc1-56d8e93937b3"
+ ]
+ },
+ "server/src/controllers": {
+ "path": "server/src/controllers",
+ "fileName": "controllers",
+ "cellName": "controllers",
+ "cellId": "478e649c-1d1b-41d2-9b7b-6927e1dec7e1",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be",
+ "children": [
+ "server/src/controllers/v1"
+ ]
+ },
+ "server/src/controllers/v1": {
+ "path": "server/src/controllers/v1",
+ "fileName": "v1",
+ "cellName": "v1",
+ "cellId": "fc591f7a-d4ac-4565-949d-a87a58a79bbc",
+ "visible": true,
+ "parentCellId": "478e649c-1d1b-41d2-9b7b-6927e1dec7e1",
+ "children": [
+ "server/src/controllers/v1/monitorController.js",
+ "server/src/controllers/v1/notificationController.js",
+ "server/src/controllers/v1/statusPageController.js",
+ "server/src/controllers/v1/checkController.js",
+ "server/src/controllers/v1/maintenanceWindowController.js",
+ "server/src/controllers/v1/inviteController.js",
+ "server/src/controllers/v1/settingsController.js",
+ "server/src/controllers/v1/diagnosticController.js"
+ ]
+ },
+ "server/src/controllers/v1/checkController.js": {
+ "path": "server/src/controllers/v1/checkController.js",
+ "fileName": "checkController.js",
+ "cellName": "checkController.js",
+ "cellId": "d983e54f-2fbe-496a-932d-a827e1124804",
+ "visible": true,
+ "parentCellId": "fc591f7a-d4ac-4565-949d-a87a58a79bbc",
+ "children": [
+ "server/src/controllers/v1/checkController.js-simstep-5768fdc5-045c-48ef-b0c9-79769403648f"
+ ]
+ },
+ "server/src/controllers/v1/diagnosticController.js": {
+ "path": "server/src/controllers/v1/diagnosticController.js",
+ "fileName": "diagnosticController.js",
+ "cellName": "diagnosticController.js",
+ "cellId": "5882419e-8a92-4edd-a71a-1252983f9af7",
+ "visible": true,
+ "parentCellId": "fc591f7a-d4ac-4565-949d-a87a58a79bbc"
+ },
+ "server/src/controllers/v1/inviteController.js": {
+ "path": "server/src/controllers/v1/inviteController.js",
+ "fileName": "inviteController.js",
+ "cellName": "inviteController.js",
+ "cellId": "6a186805-9d50-4bc3-9d56-e627a7ec2eb8",
+ "visible": true,
+ "parentCellId": "fc591f7a-d4ac-4565-949d-a87a58a79bbc"
+ },
+ "server/src/controllers/v1/maintenanceWindowController.js": {
+ "path": "server/src/controllers/v1/maintenanceWindowController.js",
+ "fileName": "maintenanceWindowController.js",
+ "cellName": "maintenanceWindowController.js",
+ "cellId": "a0cae18c-3c0e-40d3-ab03-eef7eace65d6",
+ "visible": true,
+ "parentCellId": "fc591f7a-d4ac-4565-949d-a87a58a79bbc",
+ "children": [
+ "server/src/controllers/v1/maintenanceWindowController.js-simstep-95fc1919-339d-4659-98cb-3dc50f662075",
+ "server/src/controllers/v1/maintenanceWindowController.js-simstep-1251da83-5949-4e24-a1d2-fc74ff785aa9"
+ ]
+ },
+ "server/src/controllers/v1/monitorController.js": {
+ "path": "server/src/controllers/v1/monitorController.js",
+ "fileName": "monitorController.js",
+ "cellName": "monitorController.js",
+ "cellId": "341f2784-67ee-4477-9445-a8c240ee6261",
+ "visible": true,
+ "parentCellId": "fc591f7a-d4ac-4565-949d-a87a58a79bbc",
+ "children": [
+ "server/src/controllers/v1/monitorController.js-simstep-38fd069d-9c44-4078-954b-3a0899e9acf4",
+ "server/src/controllers/v1/monitorController.js-simstep-a3799570-64bf-481e-9ffe-31dbad8a36e4",
+ "server/src/controllers/v1/monitorController.js-simstep-f5377571-156f-4c5c-8bab-b6dacdf0fea6",
+ "server/src/controllers/v1/monitorController.js-simstep-d9db3f40-8add-4551-9323-274348b9800f",
+ "server/src/controllers/v1/monitorController.js-simstep-256095b8-5e3a-4c20-abd9-be9e6a9d0bad",
+ "server/src/controllers/v1/monitorController.js-simstep-60abdb27-4078-4678-a010-0f6938d4be48"
+ ]
+ },
+ "server/src/controllers/v1/notificationController.js": {
+ "path": "server/src/controllers/v1/notificationController.js",
+ "fileName": "notificationController.js",
+ "cellName": "notificationController.js",
+ "cellId": "8e5941e1-e577-455f-bbf2-83a418ad5908",
+ "visible": true,
+ "parentCellId": "fc591f7a-d4ac-4565-949d-a87a58a79bbc"
+ },
+ "server/src/controllers/v1/settingsController.js": {
+ "path": "server/src/controllers/v1/settingsController.js",
+ "fileName": "settingsController.js",
+ "cellName": "settingsController.js",
+ "cellId": "a1fea5b0-fb7c-4189-9484-9d3312660767",
+ "visible": true,
+ "parentCellId": "fc591f7a-d4ac-4565-949d-a87a58a79bbc",
+ "children": [
+ "server/src/controllers/v1/settingsController.js-simstep-441b0c45-5f27-478d-8ca8-58fbd5f460ea",
+ "server/src/controllers/v1/settingsController.js-simstep-7632e8d2-4086-4169-ae3f-bfbf8d657987"
+ ]
+ },
+ "server/src/controllers/v1/statusPageController.js": {
+ "path": "server/src/controllers/v1/statusPageController.js",
+ "fileName": "statusPageController.js",
+ "cellName": "statusPageController.js",
+ "cellId": "17a4f435-5a8e-4433-84a6-9ac17362709a",
+ "visible": true,
+ "parentCellId": "fc591f7a-d4ac-4565-949d-a87a58a79bbc",
+ "children": [
+ "server/src/controllers/v1/statusPageController.js-simstep-01694730-8a75-4949-8815-bb4d6e3e2b42",
+ "server/src/controllers/v1/statusPageController.js-simstep-2a115421-5175-4c8f-8cdd-62f3fe148af5",
+ "server/src/controllers/v1/statusPageController.js-simstep-5bdbd179-ce8f-4171-a073-af72a99b4468",
+ "server/src/controllers/v1/statusPageController.js-simstep-83224406-2f1d-4030-9beb-d65c2baf544d"
+ ]
+ },
+ "server/src/db": {
+ "path": "server/src/db",
+ "fileName": "db",
+ "cellName": "db",
+ "cellId": "15ee5d81-a0cf-4614-80b8-7a1123ab20b8",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be",
+ "children": [
+ "server/src/db/v1",
+ "server/src/db/v2"
+ ]
+ },
+ "server/src/db/v1": {
+ "path": "server/src/db/v1",
+ "fileName": "v1",
+ "cellName": "v1",
+ "cellId": "58c5a3fd-1e69-4ca4-a64e-314f5644bdaf",
+ "visible": true,
+ "parentCellId": "15ee5d81-a0cf-4614-80b8-7a1123ab20b8",
+ "children": [
+ "server/src/db/v1/modules"
+ ]
+ },
+ "server/src/db/v1/modules": {
+ "path": "server/src/db/v1/modules",
+ "fileName": "modules",
+ "cellName": "modules",
+ "cellId": "82cb3dbe-c922-438a-a912-255376f4b380",
+ "visible": true,
+ "parentCellId": "58c5a3fd-1e69-4ca4-a64e-314f5644bdaf",
+ "children": [
+ "server/src/db/v1/modules/monitorModule.js",
+ "server/src/db/v1/modules/monitorModuleQueries.js",
+ "server/src/db/v1/modules/notificationModule.js",
+ "server/src/db/v1/modules/statusPageModule.js",
+ "server/src/db/v1/modules/checkModule.js",
+ "server/src/db/v1/modules/maintenanceWindowModule.js",
+ "server/src/db/v1/modules/inviteModule.js"
+ ]
+ },
+ "server/src/db/v1/modules/checkModule.js": {
+ "path": "server/src/db/v1/modules/checkModule.js",
+ "fileName": "checkModule.js",
+ "cellName": "checkModule.js",
+ "cellId": "860b5eec-a1ce-4e1f-a316-e1586f03af96",
+ "visible": true,
+ "parentCellId": "82cb3dbe-c922-438a-a912-255376f4b380"
+ },
+ "server/src/db/v1/modules/inviteModule.js": {
+ "path": "server/src/db/v1/modules/inviteModule.js",
+ "fileName": "inviteModule.js",
+ "cellName": "inviteModule.js",
+ "cellId": "08853f8a-f596-4df0-9190-dad04f54caef",
+ "visible": true,
+ "parentCellId": "82cb3dbe-c922-438a-a912-255376f4b380"
+ },
+ "server/src/db/v1/modules/maintenanceWindowModule.js": {
+ "path": "server/src/db/v1/modules/maintenanceWindowModule.js",
+ "fileName": "maintenanceWindowModule.js",
+ "cellName": "maintenanceWindowModule.js",
+ "cellId": "95ed6c41-ebeb-4c08-98c3-bab8b426789e",
+ "visible": true,
+ "parentCellId": "82cb3dbe-c922-438a-a912-255376f4b380",
+ "children": [
+ "server/src/db/v1/modules/maintenanceWindowModule.js-simstep-a3c47fd4-c701-46c2-9aff-94a667b3e368",
+ "server/src/db/v1/modules/maintenanceWindowModule.js-simstep-9bfbae5a-f487-40ef-86f5-04a1cc3c7662"
+ ]
+ },
+ "server/src/db/v1/modules/monitorModule.js": {
+ "path": "server/src/db/v1/modules/monitorModule.js",
+ "fileName": "monitorModule.js",
+ "cellName": "monitorModule.js",
+ "cellId": "630b3ece-2648-48d5-99b5-ef9611de2ad5",
+ "visible": true,
+ "parentCellId": "82cb3dbe-c922-438a-a912-255376f4b380",
+ "children": [
+ "server/src/db/v1/modules/monitorModule.js-simstep-ca3ad309-323a-418d-a171-c16f56fae23c",
+ "server/src/db/v1/modules/monitorModule.js-simstep-8502ae1f-f029-446e-9dde-59c2aba87a8a",
+ "server/src/db/v1/modules/monitorModule.js-simstep-70f18c21-2918-402e-9c35-1bdce8bc82d4"
+ ]
+ },
+ "server/src/db/v1/modules/monitorModuleQueries.js": {
+ "path": "server/src/db/v1/modules/monitorModuleQueries.js",
+ "fileName": "monitorModuleQueries.js",
+ "cellName": "monitorModuleQueries.js",
+ "cellId": "a0b22a5a-59e1-47e3-8218-a4ad083b1b71",
+ "visible": true,
+ "parentCellId": "82cb3dbe-c922-438a-a912-255376f4b380"
+ },
+ "server/src/db/v1/modules/notificationModule.js": {
+ "path": "server/src/db/v1/modules/notificationModule.js",
+ "fileName": "notificationModule.js",
+ "cellName": "notificationModule.js",
+ "cellId": "62daeac4-9402-448a-972c-cb8cb760671c",
+ "visible": true,
+ "parentCellId": "82cb3dbe-c922-438a-a912-255376f4b380",
+ "children": [
+ "server/src/db/v1/modules/notificationModule.js-simstep-d29ab4ab-be50-4f17-af6e-2c61cec9d0ab"
+ ]
+ },
+ "server/src/db/v1/modules/statusPageModule.js": {
+ "path": "server/src/db/v1/modules/statusPageModule.js",
+ "fileName": "statusPageModule.js",
+ "cellName": "statusPageModule.js",
+ "cellId": "f8541fe3-4bcf-4833-a226-66b5301adf58",
+ "visible": true,
+ "parentCellId": "82cb3dbe-c922-438a-a912-255376f4b380",
+ "children": [
+ "server/src/db/v1/modules/statusPageModule.js-simstep-b6fa93cd-6fca-4d9e-969a-5400e2437ee8",
+ "server/src/db/v1/modules/statusPageModule.js-simstep-ceb044e9-6545-4d04-bf79-ec8146eb8ec9"
+ ]
+ },
+ "server/src/db/v2": {
+ "path": "server/src/db/v2",
+ "fileName": "v2",
+ "cellName": "v2",
+ "cellId": "e2f99143-54b7-44dc-b566-1c2fb901bd16",
+ "visible": true,
+ "parentCellId": "15ee5d81-a0cf-4614-80b8-7a1123ab20b8",
+ "children": [
+ "server/src/db/v2/models"
+ ]
+ },
+ "server/src/db/v2/models": {
+ "path": "server/src/db/v2/models",
+ "fileName": "models",
+ "cellName": "models",
+ "cellId": "b53384d7-b989-4ac7-bd68-1c3621174ef4",
+ "visible": true,
+ "parentCellId": "e2f99143-54b7-44dc-b566-1c2fb901bd16",
+ "children": [
+ "server/src/db/v2/models/checks"
+ ]
+ },
+ "server/src/db/v2/models/checks": {
+ "path": "server/src/db/v2/models/checks",
+ "fileName": "checks",
+ "cellName": "checks",
+ "cellId": "64e3bb0a-67f5-4269-9e7d-94fe91ac78fc",
+ "visible": true,
+ "parentCellId": "b53384d7-b989-4ac7-bd68-1c3621174ef4",
+ "children": [
+ "server/src/db/v2/models/checks/Check.ts"
+ ]
+ },
+ "server/src/db/v2/models/checks/Check.ts": {
+ "path": "server/src/db/v2/models/checks/Check.ts",
+ "fileName": "Check.ts",
+ "cellName": "Check.ts",
+ "cellId": "1425ec43-f0f4-4c8b-a140-176c079219d6",
+ "visible": true,
+ "parentCellId": "64e3bb0a-67f5-4269-9e7d-94fe91ac78fc"
+ },
+ "server/src/middleware": {
+ "path": "server/src/middleware",
+ "fileName": "middleware",
+ "cellName": "middleware",
+ "cellId": "a968db5d-8f6d-4fd7-9df0-8b332b521703",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be",
+ "children": [
+ "server/src/middleware/v1"
+ ]
+ },
+ "server/src/middleware/v1": {
+ "path": "server/src/middleware/v1",
+ "fileName": "v1",
+ "cellName": "v1",
+ "cellId": "09082f73-e215-465f-9206-0a963fb4c7cb",
+ "visible": true,
+ "parentCellId": "a968db5d-8f6d-4fd7-9df0-8b332b521703",
+ "children": [
+ "server/src/middleware/v1/isAllowed.js"
+ ]
+ },
+ "server/src/middleware/v1/isAllowed.js": {
+ "path": "server/src/middleware/v1/isAllowed.js",
+ "fileName": "isAllowed.js",
+ "cellName": "isAllowed.js",
+ "cellId": "001fa2e4-eadd-4474-b2ec-79d7ea43465f",
+ "visible": true,
+ "parentCellId": "09082f73-e215-465f-9206-0a963fb4c7cb",
+ "children": [
+ "server/src/middleware/v1/isAllowed.js-simstep-50dd7316-7d01-43f7-9036-d97d51a9c178"
+ ]
+ },
+ "server/src/routes": {
+ "path": "server/src/routes",
+ "fileName": "routes",
+ "cellName": "routes",
+ "cellId": "ffaaf471-ac3c-4ea3-a5a9-d5255169e54b",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be",
+ "children": [
+ "server/src/routes/v1"
+ ]
+ },
+ "server/src/routes/v1": {
+ "path": "server/src/routes/v1",
+ "fileName": "v1",
+ "cellName": "v1",
+ "cellId": "d3375a20-2776-4771-b806-649bbeb541bd",
+ "visible": true,
+ "parentCellId": "ffaaf471-ac3c-4ea3-a5a9-d5255169e54b",
+ "children": [
+ "server/src/routes/v1/monitorRoute.js",
+ "server/src/routes/v1/notificationRoute.js",
+ "server/src/routes/v1/statusPageRoute.js",
+ "server/src/routes/v1/checkRoute.js",
+ "server/src/routes/v1/maintenanceWindowRoute.js",
+ "server/src/routes/v1/inviteRoute.js"
+ ]
+ },
+ "server/src/routes/v1/checkRoute.js": {
+ "path": "server/src/routes/v1/checkRoute.js",
+ "fileName": "checkRoute.js",
+ "cellName": "checkRoute.js",
+ "cellId": "d18dbe1d-2f2c-4336-bd21-47c1670f7bab",
+ "visible": true,
+ "parentCellId": "d3375a20-2776-4771-b806-649bbeb541bd",
+ "children": [
+ "server/src/routes/v1/checkRoute.js-simstep-c5fd192f-ea28-48a0-b3e7-5bc04f521192"
+ ]
+ },
+ "server/src/routes/v1/inviteRoute.js": {
+ "path": "server/src/routes/v1/inviteRoute.js",
+ "fileName": "inviteRoute.js",
+ "cellName": "inviteRoute.js",
+ "cellId": "9edafb8a-3661-4a7e-8510-a47f3cbc7564",
+ "visible": true,
+ "parentCellId": "d3375a20-2776-4771-b806-649bbeb541bd"
+ },
+ "server/src/routes/v1/maintenanceWindowRoute.js": {
+ "path": "server/src/routes/v1/maintenanceWindowRoute.js",
+ "fileName": "maintenanceWindowRoute.js",
+ "cellName": "maintenanceWindowRoute.js",
+ "cellId": "5f21f109-1967-416f-a649-9b48e78b90f6",
+ "visible": true,
+ "parentCellId": "d3375a20-2776-4771-b806-649bbeb541bd"
+ },
+ "server/src/routes/v1/monitorRoute.js": {
+ "path": "server/src/routes/v1/monitorRoute.js",
+ "fileName": "monitorRoute.js",
+ "cellName": "monitorRoute.js",
+ "cellId": "10611636-ec73-4a26-87a5-654ae64a4843",
+ "visible": true,
+ "parentCellId": "d3375a20-2776-4771-b806-649bbeb541bd",
+ "children": [
+ "server/src/routes/v1/monitorRoute.js-simstep-07bf3d83-047d-4e51-9f96-8c9c64848500",
+ "server/src/routes/v1/monitorRoute.js-simstep-63d8d81f-f436-4594-a6eb-8c0fd2110bc1"
+ ]
+ },
+ "server/src/routes/v1/notificationRoute.js": {
+ "path": "server/src/routes/v1/notificationRoute.js",
+ "fileName": "notificationRoute.js",
+ "cellName": "notificationRoute.js",
+ "cellId": "96250197-23af-4059-9904-fd6751f70ad5",
+ "visible": true,
+ "parentCellId": "d3375a20-2776-4771-b806-649bbeb541bd",
+ "children": [
+ "server/src/routes/v1/notificationRoute.js-simstep-2f9266c9-4cbc-4b50-a280-cf8b5800d540",
+ "server/src/routes/v1/notificationRoute.js-simstep-c1b17b64-90e9-48bc-8927-25d7ebfc3036"
+ ]
+ },
+ "server/src/routes/v1/statusPageRoute.js": {
+ "path": "server/src/routes/v1/statusPageRoute.js",
+ "fileName": "statusPageRoute.js",
+ "cellName": "statusPageRoute.js",
+ "cellId": "773e1065-b0a5-46a9-b0cc-6b963f69f5cc",
+ "visible": true,
+ "parentCellId": "d3375a20-2776-4771-b806-649bbeb541bd",
+ "children": [
+ "server/src/routes/v1/statusPageRoute.js-simstep-84f11567-b3c4-42a2-aa7f-d78c1412099a",
+ "server/src/routes/v1/statusPageRoute.js-simstep-477c6821-b29b-4b38-8930-ced21b8a75e5"
+ ]
+ },
+ "server/src/service": {
+ "path": "server/src/service",
+ "fileName": "service",
+ "cellName": "service",
+ "cellId": "99e9b746-7809-41f4-a433-1de7cc90574b",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be",
+ "children": [
+ "server/src/service/v1",
+ "server/src/service/v2"
+ ]
+ },
+ "server/src/service/v1": {
+ "path": "server/src/service/v1",
+ "fileName": "v1",
+ "cellName": "v1",
+ "cellId": "bbbe53d6-f595-498a-ae45-e08611f39c3d",
+ "visible": true,
+ "parentCellId": "99e9b746-7809-41f4-a433-1de7cc90574b",
+ "children": [
+ "server/src/service/v1/business",
+ "server/src/service/v1/infrastructure"
+ ]
+ },
+ "server/src/service/v1/business": {
+ "path": "server/src/service/v1/business",
+ "fileName": "business",
+ "cellName": "business",
+ "cellId": "87d3b101-9443-4ed4-9c4f-f759d41677eb",
+ "visible": true,
+ "parentCellId": "bbbe53d6-f595-498a-ae45-e08611f39c3d",
+ "children": [
+ "server/src/service/v1/business/monitorService.js",
+ "server/src/service/v1/business/checkService.js",
+ "server/src/service/v1/business/maintenanceWindowService.js",
+ "server/src/service/v1/business/inviteService.js",
+ "server/src/service/v1/business/diagnosticService.js"
+ ]
+ },
+ "server/src/service/v1/business/checkService.js": {
+ "path": "server/src/service/v1/business/checkService.js",
+ "fileName": "checkService.js",
+ "cellName": "checkService.js",
+ "cellId": "f90db867-e1e7-4554-b446-fa78fc6a8578",
+ "visible": true,
+ "parentCellId": "87d3b101-9443-4ed4-9c4f-f759d41677eb",
+ "children": [
+ "server/src/service/v1/business/checkService.js-simstep-83fea05d-8306-4a73-a43f-882cdd14f50f"
+ ]
+ },
+ "server/src/service/v1/business/diagnosticService.js": {
+ "path": "server/src/service/v1/business/diagnosticService.js",
+ "fileName": "diagnosticService.js",
+ "cellName": "diagnosticService.js",
+ "cellId": "643b26c1-3bfb-4c41-8179-96dfc237d99e",
+ "visible": true,
+ "parentCellId": "87d3b101-9443-4ed4-9c4f-f759d41677eb",
+ "children": [
+ "server/src/service/v1/business/diagnosticService.js-simstep-8fb704ca-67d9-437c-90d8-01302634d568"
+ ]
+ },
+ "server/src/service/v1/business/inviteService.js": {
+ "path": "server/src/service/v1/business/inviteService.js",
+ "fileName": "inviteService.js",
+ "cellName": "inviteService.js",
+ "cellId": "570278b7-9b72-4299-9fd9-46b253b48e9b",
+ "visible": true,
+ "parentCellId": "87d3b101-9443-4ed4-9c4f-f759d41677eb",
+ "children": [
+ "server/src/service/v1/business/inviteService.js-simstep-55fe9963-30d2-47f8-859f-b485a77064c4",
+ "server/src/service/v1/business/inviteService.js-simstep-7d1f86f2-c8d5-48fb-a8c1-b337dc56271f"
+ ]
+ },
+ "server/src/service/v1/business/maintenanceWindowService.js": {
+ "path": "server/src/service/v1/business/maintenanceWindowService.js",
+ "fileName": "maintenanceWindowService.js",
+ "cellName": "maintenanceWindowService.js",
+ "cellId": "26b8daff-aa48-4baa-9d6c-05f771bf0990",
+ "visible": true,
+ "parentCellId": "87d3b101-9443-4ed4-9c4f-f759d41677eb",
+ "children": [
+ "server/src/service/v1/business/maintenanceWindowService.js-simstep-6e2cf739-1514-4c37-9874-10b68a47010e"
+ ]
+ },
+ "server/src/service/v1/business/monitorService.js": {
+ "path": "server/src/service/v1/business/monitorService.js",
+ "fileName": "monitorService.js",
+ "cellName": "monitorService.js",
+ "cellId": "b4e479df-9b21-4430-9383-84831f503c2e",
+ "visible": true,
+ "parentCellId": "87d3b101-9443-4ed4-9c4f-f759d41677eb",
+ "children": [
+ "server/src/service/v1/business/monitorService.js-simstep-2c7c3082-e4b6-4525-860f-2ffcd9e86db1",
+ "server/src/service/v1/business/monitorService.js-simstep-66fbf913-e662-4bd9-ada6-077be9e37f07",
+ "server/src/service/v1/business/monitorService.js-simstep-85cd1d5d-7ffb-49c5-aacb-907048003fc5"
+ ]
+ },
+ "server/src/service/v1/infrastructure": {
+ "path": "server/src/service/v1/infrastructure",
+ "fileName": "infrastructure",
+ "cellName": "infrastructure",
+ "cellId": "1670815f-860d-4ea4-92e1-b238a799d3dc",
+ "visible": true,
+ "parentCellId": "bbbe53d6-f595-498a-ae45-e08611f39c3d",
+ "children": [
+ "server/src/service/v1/infrastructure/SuperSimpleQueue",
+ "server/src/service/v1/infrastructure/networkService.js",
+ "server/src/service/v1/infrastructure/statusService.js",
+ "server/src/service/v1/infrastructure/notificationService.js",
+ "server/src/service/v1/infrastructure/notificationUtils.js"
+ ]
+ },
+ "server/src/service/v1/infrastructure/SuperSimpleQueue": {
+ "path": "server/src/service/v1/infrastructure/SuperSimpleQueue",
+ "fileName": "SuperSimpleQueue",
+ "cellName": "SuperSimpleQueue",
+ "cellId": "dda70a36-2d1d-4e46-8bcc-52d0d474bf7b",
+ "visible": true,
+ "parentCellId": "1670815f-860d-4ea4-92e1-b238a799d3dc",
+ "children": [
+ "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js",
+ "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js"
+ ]
+ },
+ "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js": {
+ "path": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js",
+ "fileName": "SuperSimpleQueue.js",
+ "cellName": "SuperSimpleQueue.js",
+ "cellId": "d9c0127e-31aa-4509-b5c9-f7017d10d69a",
+ "visible": true,
+ "parentCellId": "dda70a36-2d1d-4e46-8bcc-52d0d474bf7b",
+ "children": [
+ "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js-simstep-2702bfca-7611-4562-8d3e-a05af8257871"
+ ]
+ },
+ "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js": {
+ "path": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js",
+ "fileName": "SuperSimpleQueueHelper.js",
+ "cellName": "SuperSimpleQueueHelper.js",
+ "cellId": "04c12e9a-e025-457c-a634-5f7472a42985",
+ "visible": true,
+ "parentCellId": "dda70a36-2d1d-4e46-8bcc-52d0d474bf7b",
+ "children": [
+ "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js-simstep-e22a708a-a348-4775-8ee3-f32a35446e24",
+ "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js-simstep-a47affaf-b952-46d6-9709-6120a77690e1",
+ "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js-simstep-2127e749-80b4-499a-ae14-8dc2dcc9db9b",
+ "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js-simstep-6ba6b9d1-5fc3-454e-8744-2022b5192ed9",
+ "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js-simstep-e59fe64e-f06b-4855-b631-923bb5a68540"
+ ]
+ },
+ "server/src/service/v1/infrastructure/networkService.js": {
+ "path": "server/src/service/v1/infrastructure/networkService.js",
+ "fileName": "networkService.js",
+ "cellName": "networkService.js",
+ "cellId": "00406786-9663-440a-96b6-919d544732c0",
+ "visible": true,
+ "parentCellId": "1670815f-860d-4ea4-92e1-b238a799d3dc",
+ "children": [
+ "server/src/service/v1/infrastructure/networkService.js-simstep-84f82fea-e306-4e0b-8222-e9705803d51a",
+ "server/src/service/v1/infrastructure/networkService.js-simstep-ed6e7faf-69dd-4d0b-bda8-137739ed288b"
+ ]
+ },
+ "server/src/service/v1/infrastructure/notificationService.js": {
+ "path": "server/src/service/v1/infrastructure/notificationService.js",
+ "fileName": "notificationService.js",
+ "cellName": "notificationService.js",
+ "cellId": "2ff1f030-2080-48da-bbbe-90302fdfc2f7",
+ "visible": true,
+ "parentCellId": "1670815f-860d-4ea4-92e1-b238a799d3dc",
+ "children": [
+ "server/src/service/v1/infrastructure/notificationService.js-simstep-1a260a75-cf50-4f7a-90b0-c280b512a5cb",
+ "server/src/service/v1/infrastructure/notificationService.js-simstep-f29f9be1-a0cd-43a3-b259-e5105a25ce41"
+ ]
+ },
+ "server/src/service/v1/infrastructure/notificationUtils.js": {
+ "path": "server/src/service/v1/infrastructure/notificationUtils.js",
+ "fileName": "notificationUtils.js",
+ "cellName": "notificationUtils.js",
+ "cellId": "4940977f-933d-4bbc-b19e-749647b44d25",
+ "visible": true,
+ "parentCellId": "1670815f-860d-4ea4-92e1-b238a799d3dc",
+ "children": [
+ "server/src/service/v1/infrastructure/notificationUtils.js-simstep-07fe8489-1d94-4afb-83f8-7cebb0dff49a"
+ ]
+ },
+ "server/src/service/v1/infrastructure/statusService.js": {
+ "path": "server/src/service/v1/infrastructure/statusService.js",
+ "fileName": "statusService.js",
+ "cellName": "statusService.js",
+ "cellId": "fbd7d244-2f37-453c-8c83-a23a260b26cc",
+ "visible": true,
+ "parentCellId": "1670815f-860d-4ea4-92e1-b238a799d3dc",
+ "children": [
+ "server/src/service/v1/infrastructure/statusService.js-simstep-c66a04c6-113e-4391-ab71-93fc612279ef",
+ "server/src/service/v1/infrastructure/statusService.js-simstep-e5b90cf2-9369-4741-9803-c835a81c009b"
+ ]
+ },
+ "server/src/service/v2": {
+ "path": "server/src/service/v2",
+ "fileName": "v2",
+ "cellName": "v2",
+ "cellId": "feefae96-61b8-4ac5-a37c-592ed4b2330e",
+ "visible": true,
+ "parentCellId": "99e9b746-7809-41f4-a433-1de7cc90574b",
+ "children": [
+ "server/src/service/v2/infrastructure",
+ "server/src/service/v2/business"
+ ]
+ },
+ "server/src/service/v2/business": {
+ "path": "server/src/service/v2/business",
+ "fileName": "business",
+ "cellName": "business",
+ "cellId": "7ef79284-534a-466f-a1f1-4e3a2f56be9d",
+ "visible": true,
+ "parentCellId": "feefae96-61b8-4ac5-a37c-592ed4b2330e",
+ "children": [
+ "server/src/service/v2/business/CheckService.ts",
+ "server/src/service/v2/business/MonitorService.ts"
+ ]
+ },
+ "server/src/service/v2/business/CheckService.ts": {
+ "path": "server/src/service/v2/business/CheckService.ts",
+ "fileName": "CheckService.ts",
+ "cellName": "CheckService.ts",
+ "cellId": "f0ebd5da-21bb-4d96-8ff9-ca10409acc80",
+ "visible": true,
+ "parentCellId": "7ef79284-534a-466f-a1f1-4e3a2f56be9d",
+ "children": [
+ "server/src/service/v2/business/CheckService.ts-simstep-01206383-2db8-4b18-84d0-be2a0289c196"
+ ]
+ },
+ "server/src/service/v2/business/MonitorService.ts": {
+ "path": "server/src/service/v2/business/MonitorService.ts",
+ "fileName": "MonitorService.ts",
+ "cellName": "MonitorService.ts",
+ "cellId": "7a3824cd-24c8-4b32-8b1d-e0bf357262c4",
+ "visible": true,
+ "parentCellId": "7ef79284-534a-466f-a1f1-4e3a2f56be9d",
+ "children": [
+ "server/src/service/v2/business/MonitorService.ts-simstep-5616ac6d-c281-4879-82f1-6590c721c8f7"
+ ]
+ },
+ "server/src/service/v2/infrastructure": {
+ "path": "server/src/service/v2/infrastructure",
+ "fileName": "infrastructure",
+ "cellName": "infrastructure",
+ "cellId": "3f8ff792-40c8-4ae6-9af5-ff8b90da2f9f",
+ "visible": true,
+ "parentCellId": "feefae96-61b8-4ac5-a37c-592ed4b2330e",
+ "children": [
+ "server/src/service/v2/infrastructure/JobGenerator.ts",
+ "server/src/service/v2/infrastructure/NotificationService.ts",
+ "server/src/service/v2/infrastructure/NetworkService.ts"
+ ]
+ },
+ "server/src/service/v2/infrastructure/JobGenerator.ts": {
+ "path": "server/src/service/v2/infrastructure/JobGenerator.ts",
+ "fileName": "JobGenerator.ts",
+ "cellName": "JobGenerator.ts",
+ "cellId": "159e569d-31d8-4883-b443-4ec08b21a044",
+ "visible": true,
+ "parentCellId": "3f8ff792-40c8-4ae6-9af5-ff8b90da2f9f",
+ "children": [
+ "server/src/service/v2/infrastructure/JobGenerator.ts-simstep-157cec87-9b76-4a1c-9d85-0a6d3decbaa3",
+ "server/src/service/v2/infrastructure/JobGenerator.ts-simstep-55ad645b-118a-478e-a00c-09ef82357008"
+ ]
+ },
+ "server/src/service/v2/infrastructure/NetworkService.ts": {
+ "path": "server/src/service/v2/infrastructure/NetworkService.ts",
+ "fileName": "NetworkService.ts",
+ "cellName": "NetworkService.ts",
+ "cellId": "50aeed0b-bc13-43f7-bb15-cb1b12b2caa7",
+ "visible": true,
+ "parentCellId": "3f8ff792-40c8-4ae6-9af5-ff8b90da2f9f",
+ "children": [
+ "server/src/service/v2/infrastructure/NetworkService.ts-simstep-53968267-0c6e-42f4-88b3-cd90f44532ae"
+ ]
+ },
+ "server/src/service/v2/infrastructure/NotificationService.ts": {
+ "path": "server/src/service/v2/infrastructure/NotificationService.ts",
+ "fileName": "NotificationService.ts",
+ "cellName": "NotificationService.ts",
+ "cellId": "c6d9907f-0a05-4153-a27f-47c3a1c4c0aa",
+ "visible": true,
+ "parentCellId": "3f8ff792-40c8-4ae6-9af5-ff8b90da2f9f",
+ "children": [
+ "server/src/service/v2/infrastructure/NotificationService.ts-simstep-8d1176f3-c755-401d-9ca8-234689b4b837"
+ ]
+ },
+ "0b23ea66-3e37-4643-b3b9-acb87ea02bae": {
+ "path": "0b23ea66-3e37-4643-b3b9-acb87ea02bae",
+ "cellName": "client",
+ "cellId": "0b23ea66-3e37-4643-b3b9-acb87ea02bae",
+ "visible": true
+ },
+ "98af25c7-efc2-43d2-a273-c8da4e1038ad": {
+ "path": "98af25c7-efc2-43d2-a273-c8da4e1038ad",
+ "cellName": "server",
+ "cellId": "98af25c7-efc2-43d2-a273-c8da4e1038ad",
+ "visible": true
+ },
+ "d3264577-10f0-42fe-9411-7e9df0754d1e": {
+ "path": "d3264577-10f0-42fe-9411-7e9df0754d1e",
+ "cellName": "src",
+ "cellId": "d3264577-10f0-42fe-9411-7e9df0754d1e",
+ "visible": true,
+ "parentCellId": "0b23ea66-3e37-4643-b3b9-acb87ea02bae"
+ },
+ "35864a2c-7bd0-4428-84fc-c0aa90c7f1be": {
+ "path": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be",
+ "cellName": "src",
+ "cellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be",
+ "visible": true,
+ "parentCellId": "98af25c7-efc2-43d2-a273-c8da4e1038ad"
+ },
+ "f0feab85-316a-41b5-a2f1-629220018846": {
+ "path": "f0feab85-316a-41b5-a2f1-629220018846",
+ "cellName": "Pages",
+ "cellId": "f0feab85-316a-41b5-a2f1-629220018846",
+ "visible": true,
+ "parentCellId": "d3264577-10f0-42fe-9411-7e9df0754d1e"
+ },
+ "4295a047-076f-4c07-95dd-b3123b6614f7": {
+ "path": "4295a047-076f-4c07-95dd-b3123b6614f7",
+ "cellName": "Hooks",
+ "cellId": "4295a047-076f-4c07-95dd-b3123b6614f7",
+ "visible": true,
+ "parentCellId": "d3264577-10f0-42fe-9411-7e9df0754d1e"
+ },
+ "02b07589-ec21-4366-b7d2-9071e8f3bad8": {
+ "path": "02b07589-ec21-4366-b7d2-9071e8f3bad8",
+ "cellName": "Utils",
+ "cellId": "02b07589-ec21-4366-b7d2-9071e8f3bad8",
+ "visible": true,
+ "parentCellId": "d3264577-10f0-42fe-9411-7e9df0754d1e"
+ },
+ "ffaaf471-ac3c-4ea3-a5a9-d5255169e54b": {
+ "path": "ffaaf471-ac3c-4ea3-a5a9-d5255169e54b",
+ "cellName": "routes",
+ "cellId": "ffaaf471-ac3c-4ea3-a5a9-d5255169e54b",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "478e649c-1d1b-41d2-9b7b-6927e1dec7e1": {
+ "path": "478e649c-1d1b-41d2-9b7b-6927e1dec7e1",
+ "cellName": "controllers",
+ "cellId": "478e649c-1d1b-41d2-9b7b-6927e1dec7e1",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "99e9b746-7809-41f4-a433-1de7cc90574b": {
+ "path": "99e9b746-7809-41f4-a433-1de7cc90574b",
+ "cellName": "service",
+ "cellId": "99e9b746-7809-41f4-a433-1de7cc90574b",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "15ee5d81-a0cf-4614-80b8-7a1123ab20b8": {
+ "path": "15ee5d81-a0cf-4614-80b8-7a1123ab20b8",
+ "cellName": "db",
+ "cellId": "15ee5d81-a0cf-4614-80b8-7a1123ab20b8",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "1a083b4a-787c-4eb4-8029-6700de987679": {
+ "path": "1a083b4a-787c-4eb4-8029-6700de987679",
+ "cellName": "v1",
+ "cellId": "1a083b4a-787c-4eb4-8029-6700de987679",
+ "visible": true,
+ "parentCellId": "f0feab85-316a-41b5-a2f1-629220018846"
+ },
+ "3bf0c0f9-cbb0-4f0c-8cb3-6e2a4abc0961": {
+ "path": "3bf0c0f9-cbb0-4f0c-8cb3-6e2a4abc0961",
+ "cellName": "v1",
+ "cellId": "3bf0c0f9-cbb0-4f0c-8cb3-6e2a4abc0961",
+ "visible": true,
+ "parentCellId": "4295a047-076f-4c07-95dd-b3123b6614f7"
+ },
+ "16731e9d-3f73-4055-9d79-5ca9be679f68": {
+ "path": "16731e9d-3f73-4055-9d79-5ca9be679f68",
+ "cellName": "NetworkService.js",
+ "cellId": "16731e9d-3f73-4055-9d79-5ca9be679f68",
+ "visible": true,
+ "parentCellId": "02b07589-ec21-4366-b7d2-9071e8f3bad8"
+ },
+ "d3375a20-2776-4771-b806-649bbeb541bd": {
+ "path": "d3375a20-2776-4771-b806-649bbeb541bd",
+ "cellName": "v1",
+ "cellId": "d3375a20-2776-4771-b806-649bbeb541bd",
+ "visible": true,
+ "parentCellId": "ffaaf471-ac3c-4ea3-a5a9-d5255169e54b"
+ },
+ "fc591f7a-d4ac-4565-949d-a87a58a79bbc": {
+ "path": "fc591f7a-d4ac-4565-949d-a87a58a79bbc",
+ "cellName": "v1",
+ "cellId": "fc591f7a-d4ac-4565-949d-a87a58a79bbc",
+ "visible": true,
+ "parentCellId": "478e649c-1d1b-41d2-9b7b-6927e1dec7e1"
+ },
+ "bbbe53d6-f595-498a-ae45-e08611f39c3d": {
+ "path": "bbbe53d6-f595-498a-ae45-e08611f39c3d",
+ "cellName": "v1",
+ "cellId": "bbbe53d6-f595-498a-ae45-e08611f39c3d",
+ "visible": true,
+ "parentCellId": "99e9b746-7809-41f4-a433-1de7cc90574b"
+ },
+ "58c5a3fd-1e69-4ca4-a64e-314f5644bdaf": {
+ "path": "58c5a3fd-1e69-4ca4-a64e-314f5644bdaf",
+ "cellName": "v1",
+ "cellId": "58c5a3fd-1e69-4ca4-a64e-314f5644bdaf",
+ "visible": true,
+ "parentCellId": "15ee5d81-a0cf-4614-80b8-7a1123ab20b8"
+ },
+ "7090fcdb-09be-4c85-86fd-e73553bbb2ff": {
+ "path": "7090fcdb-09be-4c85-86fd-e73553bbb2ff",
+ "cellName": "Uptime",
+ "cellId": "7090fcdb-09be-4c85-86fd-e73553bbb2ff",
+ "visible": true,
+ "parentCellId": "1a083b4a-787c-4eb4-8029-6700de987679"
+ },
+ "595cf0e8-2f86-4543-b51c-b2aeac307f4b": {
+ "path": "595cf0e8-2f86-4543-b51c-b2aeac307f4b",
+ "cellName": "monitorHooks.js",
+ "cellId": "595cf0e8-2f86-4543-b51c-b2aeac307f4b",
+ "visible": true,
+ "parentCellId": "3bf0c0f9-cbb0-4f0c-8cb3-6e2a4abc0961"
+ },
+ "10611636-ec73-4a26-87a5-654ae64a4843": {
+ "path": "10611636-ec73-4a26-87a5-654ae64a4843",
+ "cellName": "monitorRoute.js",
+ "cellId": "10611636-ec73-4a26-87a5-654ae64a4843",
+ "visible": true,
+ "parentCellId": "d3375a20-2776-4771-b806-649bbeb541bd"
+ },
+ "341f2784-67ee-4477-9445-a8c240ee6261": {
+ "path": "341f2784-67ee-4477-9445-a8c240ee6261",
+ "cellName": "monitorController.js",
+ "cellId": "341f2784-67ee-4477-9445-a8c240ee6261",
+ "visible": true,
+ "parentCellId": "fc591f7a-d4ac-4565-949d-a87a58a79bbc"
+ },
+ "87d3b101-9443-4ed4-9c4f-f759d41677eb": {
+ "path": "87d3b101-9443-4ed4-9c4f-f759d41677eb",
+ "cellName": "business",
+ "cellId": "87d3b101-9443-4ed4-9c4f-f759d41677eb",
+ "visible": true,
+ "parentCellId": "bbbe53d6-f595-498a-ae45-e08611f39c3d"
+ },
+ "1670815f-860d-4ea4-92e1-b238a799d3dc": {
+ "path": "1670815f-860d-4ea4-92e1-b238a799d3dc",
+ "cellName": "infrastructure",
+ "cellId": "1670815f-860d-4ea4-92e1-b238a799d3dc",
+ "visible": true,
+ "parentCellId": "bbbe53d6-f595-498a-ae45-e08611f39c3d"
+ },
+ "82cb3dbe-c922-438a-a912-255376f4b380": {
+ "path": "82cb3dbe-c922-438a-a912-255376f4b380",
+ "cellName": "modules",
+ "cellId": "82cb3dbe-c922-438a-a912-255376f4b380",
+ "visible": true,
+ "parentCellId": "58c5a3fd-1e69-4ca4-a64e-314f5644bdaf"
+ },
+ "3dba5acc-cf25-40b7-ad21-9cd715454cd5": {
+ "path": "3dba5acc-cf25-40b7-ad21-9cd715454cd5",
+ "cellName": "Create",
+ "cellId": "3dba5acc-cf25-40b7-ad21-9cd715454cd5",
+ "visible": true,
+ "parentCellId": "7090fcdb-09be-4c85-86fd-e73553bbb2ff"
+ },
+ "b4e479df-9b21-4430-9383-84831f503c2e": {
+ "path": "b4e479df-9b21-4430-9383-84831f503c2e",
+ "cellName": "monitorService.js",
+ "cellId": "b4e479df-9b21-4430-9383-84831f503c2e",
+ "visible": true,
+ "parentCellId": "87d3b101-9443-4ed4-9c4f-f759d41677eb"
+ },
+ "dda70a36-2d1d-4e46-8bcc-52d0d474bf7b": {
+ "path": "dda70a36-2d1d-4e46-8bcc-52d0d474bf7b",
+ "cellName": "SuperSimpleQueue",
+ "cellId": "dda70a36-2d1d-4e46-8bcc-52d0d474bf7b",
+ "visible": true,
+ "parentCellId": "1670815f-860d-4ea4-92e1-b238a799d3dc"
+ },
+ "00406786-9663-440a-96b6-919d544732c0": {
+ "path": "00406786-9663-440a-96b6-919d544732c0",
+ "cellName": "networkService.js",
+ "cellId": "00406786-9663-440a-96b6-919d544732c0",
+ "visible": true,
+ "parentCellId": "1670815f-860d-4ea4-92e1-b238a799d3dc"
+ },
+ "fbd7d244-2f37-453c-8c83-a23a260b26cc": {
+ "path": "fbd7d244-2f37-453c-8c83-a23a260b26cc",
+ "cellName": "statusService.js",
+ "cellId": "fbd7d244-2f37-453c-8c83-a23a260b26cc",
+ "visible": true,
+ "parentCellId": "1670815f-860d-4ea4-92e1-b238a799d3dc"
+ },
+ "2ff1f030-2080-48da-bbbe-90302fdfc2f7": {
+ "path": "2ff1f030-2080-48da-bbbe-90302fdfc2f7",
+ "cellName": "notificationService.js",
+ "cellId": "2ff1f030-2080-48da-bbbe-90302fdfc2f7",
+ "visible": true,
+ "parentCellId": "1670815f-860d-4ea4-92e1-b238a799d3dc"
+ },
+ "630b3ece-2648-48d5-99b5-ef9611de2ad5": {
+ "path": "630b3ece-2648-48d5-99b5-ef9611de2ad5",
+ "cellName": "monitorModule.js",
+ "cellId": "630b3ece-2648-48d5-99b5-ef9611de2ad5",
+ "visible": true,
+ "parentCellId": "82cb3dbe-c922-438a-a912-255376f4b380"
+ },
+ "391ad99e-3114-4f54-a6ca-fd89b33c7e06": {
+ "path": "391ad99e-3114-4f54-a6ca-fd89b33c7e06",
+ "cellName": "index.jsx",
+ "cellId": "391ad99e-3114-4f54-a6ca-fd89b33c7e06",
+ "visible": true,
+ "parentCellId": "3dba5acc-cf25-40b7-ad21-9cd715454cd5"
+ },
+ "d9c0127e-31aa-4509-b5c9-f7017d10d69a": {
+ "path": "d9c0127e-31aa-4509-b5c9-f7017d10d69a",
+ "cellName": "SuperSimpleQueue.js",
+ "cellId": "d9c0127e-31aa-4509-b5c9-f7017d10d69a",
+ "visible": true,
+ "parentCellId": "dda70a36-2d1d-4e46-8bcc-52d0d474bf7b"
+ },
+ "04c12e9a-e025-457c-a634-5f7472a42985": {
+ "path": "04c12e9a-e025-457c-a634-5f7472a42985",
+ "cellName": "SuperSimpleQueueHelper.js",
+ "cellId": "04c12e9a-e025-457c-a634-5f7472a42985",
+ "visible": true,
+ "parentCellId": "dda70a36-2d1d-4e46-8bcc-52d0d474bf7b"
+ },
+ "7a2da928-a65c-49d8-b0a3-c159a6df2bc5": {
+ "path": "7a2da928-a65c-49d8-b0a3-c159a6df2bc5",
+ "cellName": "Uptime Monitor Creation: Display Form - index.jsx:L315-802",
+ "cellId": "7a2da928-a65c-49d8-b0a3-c159a6df2bc5",
+ "visible": true,
+ "parentCellId": "391ad99e-3114-4f54-a6ca-fd89b33c7e06"
+ },
+ "client/src/Pages/v1/Uptime/Create/index.jsx-simstep-ab5de0b9-c70d-492c-98d9-8ddee66284ad": {
+ "path": "client/src/Pages/v1/Uptime/Create/index.jsx-simstep-ab5de0b9-c70d-492c-98d9-8ddee66284ad",
+ "fileName": "index.jsx",
+ "wiki": "The user navigates to the 'Create Uptime Monitor' page. The `UptimeCreate` React component renders the form, allowing the user to select the monitor type (HTTP, Ping, Docker, etc.) and input details like URL, name, and check interval.",
+ "cellName": "Uptime Monitor Creation: Display Form - index.jsx:L315-802",
+ "cellId": "7a2da928-a65c-49d8-b0a3-c159a6df2bc5",
+ "visible": true,
+ "startLine": 315,
+ "endLine": 802,
+ "parentCellId": "391ad99e-3114-4f54-a6ca-fd89b33c7e06",
+ "parentPath": "client/src/Pages/v1/Uptime/Create/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Comprehensive Uptime Monitoring for Various Services",
+ "simStepId": "ab5de0b9-c70d-492c-98d9-8ddee66284ad"
+ }
+ ]
+ },
+ "a81f4ae8-41fe-4d1b-95ce-86abcc67b53c": {
+ "path": "a81f4ae8-41fe-4d1b-95ce-86abcc67b53c",
+ "cellName": "Uptime Monitor Creation: Handle Form Submission and Validation - index.jsx:L170-248",
+ "cellId": "a81f4ae8-41fe-4d1b-95ce-86abcc67b53c",
+ "visible": true,
+ "parentCellId": "391ad99e-3114-4f54-a6ca-fd89b33c7e06"
+ },
+ "client/src/Pages/v1/Uptime/Create/index.jsx-simstep-e02ddf57-270e-4f71-b4c4-d5183bb7879a": {
+ "path": "client/src/Pages/v1/Uptime/Create/index.jsx-simstep-e02ddf57-270e-4f71-b4c4-d5183bb7879a",
+ "fileName": "index.jsx",
+ "wiki": "The `onSubmit` function in `UptimeCreate.jsx` is executed. It prevents the default form submission, constructs a `form` object with monitor data, validates it using `monitorValidation`, and then calls the `createMonitor` function from the `useCreateMonitor` hook.",
+ "cellName": "Uptime Monitor Creation: Handle Form Submission and Validation - index.jsx:L170-248",
+ "cellId": "a81f4ae8-41fe-4d1b-95ce-86abcc67b53c",
+ "visible": true,
+ "startLine": 170,
+ "endLine": 248,
+ "parentCellId": "391ad99e-3114-4f54-a6ca-fd89b33c7e06",
+ "parentPath": "client/src/Pages/v1/Uptime/Create/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Comprehensive Uptime Monitoring for Various Services",
+ "simStepId": "e02ddf57-270e-4f71-b4c4-d5183bb7879a"
+ }
+ ]
+ },
+ "a30ac765-c546-4993-988e-8c8cfdb7532f": {
+ "path": "a30ac765-c546-4993-988e-8c8cfdb7532f",
+ "cellName": "Uptime Monitor Creation: Send API Request - NetworkService.js:L119-121",
+ "cellId": "a30ac765-c546-4993-988e-8c8cfdb7532f",
+ "visible": true,
+ "parentCellId": "16731e9d-3f73-4055-9d79-5ca9be679f68"
+ },
+ "client/src/Utils/NetworkService.js-simstep-14e9986b-c6ce-475e-97ed-da1fa2b6968d": {
+ "path": "client/src/Utils/NetworkService.js-simstep-14e9986b-c6ce-475e-97ed-da1fa2b6968d",
+ "fileName": "NetworkService.js",
+ "wiki": "The `createMonitor` method in `NetworkService.js` makes an HTTP POST request to the `/monitors` endpoint on the server, sending the new monitor's data in the request body.",
+ "cellName": "Uptime Monitor Creation: Send API Request - NetworkService.js:L119-121",
+ "cellId": "a30ac765-c546-4993-988e-8c8cfdb7532f",
+ "visible": true,
+ "startLine": 119,
+ "endLine": 121,
+ "parentCellId": "16731e9d-3f73-4055-9d79-5ca9be679f68",
+ "parentPath": "client/src/Utils/NetworkService.js",
+ "simSteps": [
+ {
+ "simulationKey": "Comprehensive Uptime Monitoring for Various Services",
+ "simStepId": "14e9986b-c6ce-475e-97ed-da1fa2b6968d"
+ }
+ ]
+ },
+ "f0409948-75dc-457f-a02d-6b9774258e48": {
+ "path": "f0409948-75dc-457f-a02d-6b9774258e48",
+ "cellName": "Uptime Monitor Creation: Controller to Service - monitorController.js:L195",
+ "cellId": "f0409948-75dc-457f-a02d-6b9774258e48",
+ "visible": true,
+ "parentCellId": "341f2784-67ee-4477-9445-a8c240ee6261"
+ },
+ "server/src/controllers/v1/monitorController.js-simstep-38fd069d-9c44-4078-954b-3a0899e9acf4": {
+ "path": "server/src/controllers/v1/monitorController.js-simstep-38fd069d-9c44-4078-954b-3a0899e9acf4",
+ "fileName": "monitorController.js",
+ "wiki": "The `MonitorController` extracts the user and team ID from the request and calls the `createMonitor` method in the `MonitorService`, passing along the monitor data from the request body.",
+ "cellName": "Uptime Monitor Creation: Controller to Service - monitorController.js:L195",
+ "cellId": "f0409948-75dc-457f-a02d-6b9774258e48",
+ "visible": true,
+ "startLine": 195,
+ "endLine": 195,
+ "parentCellId": "341f2784-67ee-4477-9445-a8c240ee6261",
+ "parentPath": "server/src/controllers/v1/monitorController.js",
+ "simSteps": [
+ {
+ "simulationKey": "Comprehensive Uptime Monitoring for Various Services",
+ "simStepId": "38fd069d-9c44-4078-954b-3a0899e9acf4"
+ }
+ ]
+ },
+ "2d407bfd-e4b0-46b8-99e7-121c507d525f": {
+ "path": "2d407bfd-e4b0-46b8-99e7-121c507d525f",
+ "cellName": "Uptime Monitor Creation: Save Monitor to Database - monitorModule.js:L454-455",
+ "cellId": "2d407bfd-e4b0-46b8-99e7-121c507d525f",
+ "visible": true,
+ "parentCellId": "630b3ece-2648-48d5-99b5-ef9611de2ad5"
+ },
+ "server/src/db/v1/modules/monitorModule.js-simstep-ca3ad309-323a-418d-a171-c16f56fae23c": {
+ "path": "server/src/db/v1/modules/monitorModule.js-simstep-ca3ad309-323a-418d-a171-c16f56fae23c",
+ "fileName": "monitorModule.js",
+ "wiki": "The `monitorModule` creates a new `Monitor` model instance with the provided data and saves it to the MongoDB database. The newly created document, including its generated `_id`, is returned.",
+ "cellName": "Uptime Monitor Creation: Save Monitor to Database - monitorModule.js:L454-455",
+ "cellId": "2d407bfd-e4b0-46b8-99e7-121c507d525f",
+ "visible": true,
+ "startLine": 454,
+ "endLine": 455,
+ "parentCellId": "630b3ece-2648-48d5-99b5-ef9611de2ad5",
+ "parentPath": "server/src/db/v1/modules/monitorModule.js",
+ "simSteps": [
+ {
+ "simulationKey": "Comprehensive Uptime Monitoring for Various Services",
+ "simStepId": "ca3ad309-323a-418d-a171-c16f56fae23c"
+ }
+ ]
+ },
+ "51e8db5f-2fdd-4ddc-9605-43981db9d940": {
+ "path": "51e8db5f-2fdd-4ddc-9605-43981db9d940",
+ "cellName": "Uptime Monitor Creation: Schedule Job - SuperSimpleQueue.js:L50-57",
+ "cellId": "51e8db5f-2fdd-4ddc-9605-43981db9d940",
+ "visible": true,
+ "parentCellId": "d9c0127e-31aa-4509-b5c9-f7017d10d69a"
+ },
+ "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js-simstep-2702bfca-7611-4562-8d3e-a05af8257871": {
+ "path": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js-simstep-2702bfca-7611-4562-8d3e-a05af8257871",
+ "fileName": "SuperSimpleQueue.js",
+ "wiki": "The `SuperSimpleQueue` receives the new monitor and uses its `scheduler.addJob` method to create a recurring job based on the monitor's interval. The monitor object itself is stored as data for the job.",
+ "cellName": "Uptime Monitor Creation: Schedule Job - SuperSimpleQueue.js:L50-57",
+ "cellId": "51e8db5f-2fdd-4ddc-9605-43981db9d940",
+ "visible": true,
+ "startLine": 50,
+ "endLine": 57,
+ "parentCellId": "d9c0127e-31aa-4509-b5c9-f7017d10d69a",
+ "parentPath": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js",
+ "simSteps": [
+ {
+ "simulationKey": "Comprehensive Uptime Monitoring for Various Services",
+ "simStepId": "2702bfca-7611-4562-8d3e-a05af8257871"
+ }
+ ]
+ },
+ "18c13391-0cf7-4303-8180-0a40cfd64b58": {
+ "path": "18c13391-0cf7-4303-8180-0a40cfd64b58",
+ "cellName": "Service Monitoring: Job Execution - SuperSimpleQueueHelper.js:L27-73",
+ "cellId": "18c13391-0cf7-4303-8180-0a40cfd64b58",
+ "visible": true,
+ "parentCellId": "04c12e9a-e025-457c-a634-5f7472a42985"
+ },
+ "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js-simstep-e22a708a-a348-4775-8ee3-f32a35446e24": {
+ "path": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js-simstep-e22a708a-a348-4775-8ee3-f32a35446e24",
+ "fileName": "SuperSimpleQueueHelper.js",
+ "wiki": "The scheduler triggers a monitor job at the specified interval. The `getMonitorJob` function in `SuperSimpleQueueHelper` is executed, which first checks if the monitor is in a maintenance window.",
+ "cellName": "Service Monitoring: Job Execution - SuperSimpleQueueHelper.js:L27-73",
+ "cellId": "18c13391-0cf7-4303-8180-0a40cfd64b58",
+ "visible": true,
+ "startLine": 27,
+ "endLine": 73,
+ "parentCellId": "04c12e9a-e025-457c-a634-5f7472a42985",
+ "parentPath": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js",
+ "simSteps": [
+ {
+ "simulationKey": "Comprehensive Uptime Monitoring for Various Services",
+ "simStepId": "e22a708a-a348-4775-8ee3-f32a35446e24"
+ }
+ ]
+ },
+ "5c900373-e6ba-44d5-b341-676a32d659c8": {
+ "path": "5c900373-e6ba-44d5-b341-676a32d659c8",
+ "cellName": "Service Monitoring: Perform Network Check - networkService.js:L44-59",
+ "cellId": "5c900373-e6ba-44d5-b341-676a32d659c8",
+ "visible": true,
+ "parentCellId": "00406786-9663-440a-96b6-919d544732c0"
+ },
+ "server/src/service/v1/infrastructure/networkService.js-simstep-84f82fea-e306-4e0b-8222-e9705803d51a": {
+ "path": "server/src/service/v1/infrastructure/networkService.js-simstep-84f82fea-e306-4e0b-8222-e9705803d51a",
+ "fileName": "networkService.js",
+ "wiki": "The `requestStatus` method in the network service acts as a router. Based on the monitor's `type` (e.g., 'http', 'ping', 'docker'), it calls the corresponding specialized request function (e.g., `requestHttp`) to perform the check and constructs a standardized response object.",
+ "cellName": "Service Monitoring: Perform Network Check - networkService.js:L44-59",
+ "cellId": "5c900373-e6ba-44d5-b341-676a32d659c8",
+ "visible": true,
+ "startLine": 44,
+ "endLine": 59,
+ "parentCellId": "00406786-9663-440a-96b6-919d544732c0",
+ "parentPath": "server/src/service/v1/infrastructure/networkService.js",
+ "simSteps": [
+ {
+ "simulationKey": "Comprehensive Uptime Monitoring for Various Services",
+ "simStepId": "84f82fea-e306-4e0b-8222-e9705803d51a"
+ }
+ ]
+ },
+ "45e38f3a-a7c3-4de7-b3dc-589361d793fd": {
+ "path": "45e38f3a-a7c3-4de7-b3dc-589361d793fd",
+ "cellName": "Service Monitoring: Update Monitor Status - statusService.js:L108-183",
+ "cellId": "45e38f3a-a7c3-4de7-b3dc-589361d793fd",
+ "visible": true,
+ "parentCellId": "fbd7d244-2f37-453c-8c83-a23a260b26cc"
+ },
+ "server/src/service/v1/infrastructure/statusService.js-simstep-c66a04c6-113e-4391-ab71-93fc612279ef": {
+ "path": "server/src/service/v1/infrastructure/statusService.js-simstep-c66a04c6-113e-4391-ab71-93fc612279ef",
+ "fileName": "statusService.js",
+ "wiki": "The job handler passes the network response to `statusService.updateStatus`. This service creates a new `Check` document in the database, updates the monitor's status based on a sliding window and threshold, and calculates running statistics.",
+ "cellName": "Service Monitoring: Update Monitor Status - statusService.js:L108-183",
+ "cellId": "45e38f3a-a7c3-4de7-b3dc-589361d793fd",
+ "visible": true,
+ "startLine": 108,
+ "endLine": 183,
+ "parentCellId": "fbd7d244-2f37-453c-8c83-a23a260b26cc",
+ "parentPath": "server/src/service/v1/infrastructure/statusService.js",
+ "simSteps": [
+ {
+ "simulationKey": "Comprehensive Uptime Monitoring for Various Services",
+ "simStepId": "c66a04c6-113e-4391-ab71-93fc612279ef"
+ }
+ ]
+ },
+ "9f3ea7fc-16d3-4d36-97eb-66f8808425cc": {
+ "path": "9f3ea7fc-16d3-4d36-97eb-66f8808425cc",
+ "cellName": "Service Monitoring: Handle Notifications (Conditional) - notificationService.js:L76-92",
+ "cellId": "9f3ea7fc-16d3-4d36-97eb-66f8808425cc",
+ "visible": true,
+ "parentCellId": "2ff1f030-2080-48da-bbbe-90302fdfc2f7"
+ },
+ "server/src/service/v1/infrastructure/notificationService.js-simstep-1a260a75-cf50-4f7a-90b0-c280b512a5cb": {
+ "path": "server/src/service/v1/infrastructure/notificationService.js-simstep-1a260a75-cf50-4f7a-90b0-c280b512a5cb",
+ "fileName": "notificationService.js",
+ "wiki": "If `statusChanged` is true, the job handler calls `notificationService.handleNotifications`. The service identifies the notification channels associated with the monitor and sends out alerts (e.g., email, webhook, Discord) with details about the status change.",
+ "cellName": "Service Monitoring: Handle Notifications (Conditional) - notificationService.js:L76-92",
+ "cellId": "9f3ea7fc-16d3-4d36-97eb-66f8808425cc",
+ "visible": true,
+ "startLine": 76,
+ "endLine": 92,
+ "parentCellId": "2ff1f030-2080-48da-bbbe-90302fdfc2f7",
+ "parentPath": "server/src/service/v1/infrastructure/notificationService.js",
+ "simSteps": [
+ {
+ "simulationKey": "Comprehensive Uptime Monitoring for Various Services",
+ "simStepId": "1a260a75-cf50-4f7a-90b0-c280b512a5cb"
+ }
+ ]
+ },
+ "0e09aa33-d7c7-4c89-9da8-f0937658f4d3": {
+ "path": "0e09aa33-d7c7-4c89-9da8-f0937658f4d3",
+ "cellName": "Uptime Monitor\nCreation: Form\nSubmission",
+ "cellId": "0e09aa33-d7c7-4c89-9da8-f0937658f4d3",
+ "visible": true,
+ "parentCellId": "391ad99e-3114-4f54-a6ca-fd89b33c7e06"
+ },
+ "generated-edge-simstep-888df086-41bb-469d-9c0a-daf74bdecb9f-0e09aa33-d7c7-4c89-9da8-f0937658f4d3": {
+ "path": "generated-edge-simstep-888df086-41bb-469d-9c0a-daf74bdecb9f-0e09aa33-d7c7-4c89-9da8-f0937658f4d3",
+ "fileName": "index.jsx",
+ "cellName": "Uptime Monitor Creation: Form Submission",
+ "cellId": "0e09aa33-d7c7-4c89-9da8-f0937658f4d3",
+ "visible": true,
+ "startLine": 435,
+ "endLine": 443,
+ "parentPath": "client/src/Pages/v1/Uptime/Create/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Comprehensive Uptime Monitoring for Various Services",
+ "simStepId": "888df086-41bb-469d-9c0a-daf74bdecb9f"
+ }
+ ]
+ },
+ "1f6f04a9-0123-4729-b06a-8f3da6d1b58c": {
+ "path": "1f6f04a9-0123-4729-b06a-8f3da6d1b58c",
+ "cellName": "Uptime Monitor\nCreation: Call\nNetwork Service",
+ "cellId": "1f6f04a9-0123-4729-b06a-8f3da6d1b58c",
+ "visible": true,
+ "parentCellId": "d3264577-10f0-42fe-9411-7e9df0754d1e"
+ },
+ "generated-edge-simstep-3fe0afaa-c673-4b52-a0c4-02a3ffd3a075-1f6f04a9-0123-4729-b06a-8f3da6d1b58c": {
+ "path": "generated-edge-simstep-3fe0afaa-c673-4b52-a0c4-02a3ffd3a075-1f6f04a9-0123-4729-b06a-8f3da6d1b58c",
+ "fileName": "index.jsx",
+ "cellName": "Uptime Monitor Creation: Call Network Service",
+ "cellId": "1f6f04a9-0123-4729-b06a-8f3da6d1b58c",
+ "visible": true,
+ "startLine": 306,
+ "endLine": 306,
+ "parentPath": "client/src/Pages/v1/Uptime/Create/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Comprehensive Uptime Monitoring for Various Services",
+ "simStepId": "3fe0afaa-c673-4b52-a0c4-02a3ffd3a075"
+ }
+ ]
+ },
+ "b6d963d5-c613-407a-9ce9-60a04048dea7": {
+ "path": "b6d963d5-c613-407a-9ce9-60a04048dea7",
+ "cellName": "Uptime Monitor\nCreation: API\nRoute to\nController",
+ "cellId": "b6d963d5-c613-407a-9ce9-60a04048dea7",
+ "visible": true
+ },
+ "generated-edge-simstep-13dd0564-44a5-43ea-a163-ce1148cc48e7-b6d963d5-c613-407a-9ce9-60a04048dea7": {
+ "path": "generated-edge-simstep-13dd0564-44a5-43ea-a163-ce1148cc48e7-b6d963d5-c613-407a-9ce9-60a04048dea7",
+ "fileName": "NetworkService.js",
+ "cellName": "Uptime Monitor Creation: API Route to Controller",
+ "cellId": "b6d963d5-c613-407a-9ce9-60a04048dea7",
+ "visible": true,
+ "startLine": 35,
+ "endLine": 35,
+ "parentPath": "client/src/Utils/NetworkService.js",
+ "simSteps": [
+ {
+ "simulationKey": "Comprehensive Uptime Monitoring for Various Services",
+ "simStepId": "13dd0564-44a5-43ea-a163-ce1148cc48e7"
+ }
+ ]
+ },
+ "a6bccd1d-4272-4df7-a7c8-b3979e37b8d7": {
+ "path": "a6bccd1d-4272-4df7-a7c8-b3979e37b8d7",
+ "cellName": "Uptime Monitor\nCreation: Service\nto DB\nModule",
+ "cellId": "a6bccd1d-4272-4df7-a7c8-b3979e37b8d7",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "generated-edge-simstep-a6f4c967-b1a8-4804-bc2e-67f47a7b68f9-a6bccd1d-4272-4df7-a7c8-b3979e37b8d7": {
+ "path": "generated-edge-simstep-a6f4c967-b1a8-4804-bc2e-67f47a7b68f9-a6bccd1d-4272-4df7-a7c8-b3979e37b8d7",
+ "fileName": "monitorController.js",
+ "cellName": "Uptime Monitor Creation: Service to DB Module",
+ "cellId": "a6bccd1d-4272-4df7-a7c8-b3979e37b8d7",
+ "visible": true,
+ "startLine": 74,
+ "endLine": 78,
+ "parentPath": "server/src/controllers/v1/monitorController.js",
+ "simSteps": [
+ {
+ "simulationKey": "Comprehensive Uptime Monitoring for Various Services",
+ "simStepId": "a6f4c967-b1a8-4804-bc2e-67f47a7b68f9"
+ }
+ ]
+ },
+ "de320ac1-0949-4297-be7c-b9d7a14e0fec": {
+ "path": "de320ac1-0949-4297-be7c-b9d7a14e0fec",
+ "cellName": "Uptime Monitor\nCreation: Service\nto Job\nQueue",
+ "cellId": "de320ac1-0949-4297-be7c-b9d7a14e0fec",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "generated-edge-simstep-71ec03b6-1166-4c5d-9ada-0875a0866e30-de320ac1-0949-4297-be7c-b9d7a14e0fec": {
+ "path": "generated-edge-simstep-71ec03b6-1166-4c5d-9ada-0875a0866e30-de320ac1-0949-4297-be7c-b9d7a14e0fec",
+ "fileName": "monitorModule.js",
+ "cellName": "Uptime Monitor Creation: Service to Job Queue",
+ "cellId": "de320ac1-0949-4297-be7c-b9d7a14e0fec",
+ "visible": true,
+ "startLine": 80,
+ "endLine": 80,
+ "parentPath": "server/src/db/v1/modules/monitorModule.js",
+ "simSteps": [
+ {
+ "simulationKey": "Comprehensive Uptime Monitoring for Various Services",
+ "simStepId": "71ec03b6-1166-4c5d-9ada-0875a0866e30"
+ }
+ ]
+ },
+ "38ba9b22-5b26-432d-a856-0eceb792c737": {
+ "path": "38ba9b22-5b26-432d-a856-0eceb792c737",
+ "cellName": "Service Monitoring:\nRequest Service\nStatus",
+ "cellId": "38ba9b22-5b26-432d-a856-0eceb792c737",
+ "visible": true,
+ "parentCellId": "1670815f-860d-4ea4-92e1-b238a799d3dc"
+ },
+ "generated-edge-simstep-1d7580f2-f107-4008-a207-b8ec07998721-38ba9b22-5b26-432d-a856-0eceb792c737": {
+ "path": "generated-edge-simstep-1d7580f2-f107-4008-a207-b8ec07998721-38ba9b22-5b26-432d-a856-0eceb792c737",
+ "fileName": "SuperSimpleQueueHelper.js",
+ "cellName": "Service Monitoring: Request Service Status",
+ "cellId": "38ba9b22-5b26-432d-a856-0eceb792c737",
+ "visible": true,
+ "startLine": 45,
+ "endLine": 45,
+ "parentPath": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js",
+ "simSteps": [
+ {
+ "simulationKey": "Comprehensive Uptime Monitoring for Various Services",
+ "simStepId": "1d7580f2-f107-4008-a207-b8ec07998721"
+ }
+ ]
+ },
+ "5e80734c-7b72-41be-9068-30cda3ffb425": {
+ "path": "5e80734c-7b72-41be-9068-30cda3ffb425",
+ "cellName": "Service Monitoring:\nPropagate Network\nResponse",
+ "cellId": "5e80734c-7b72-41be-9068-30cda3ffb425",
+ "visible": true,
+ "parentCellId": "1670815f-860d-4ea4-92e1-b238a799d3dc"
+ },
+ "generated-edge-simstep-9b0ada30-d3e6-4b2c-b3ff-f50615139588-5e80734c-7b72-41be-9068-30cda3ffb425": {
+ "path": "generated-edge-simstep-9b0ada30-d3e6-4b2c-b3ff-f50615139588-5e80734c-7b72-41be-9068-30cda3ffb425",
+ "fileName": "networkService.js",
+ "cellName": "Service Monitoring: Propagate Network Response",
+ "cellId": "5e80734c-7b72-41be-9068-30cda3ffb425",
+ "visible": true,
+ "startLine": 45,
+ "endLine": 45,
+ "parentPath": "server/src/service/v1/infrastructure/networkService.js",
+ "simSteps": [
+ {
+ "simulationKey": "Comprehensive Uptime Monitoring for Various Services",
+ "simStepId": "9b0ada30-d3e6-4b2c-b3ff-f50615139588"
+ }
+ ]
+ },
+ "0a7e4a72-b644-4bd8-afa1-ae8a1d6c111c": {
+ "path": "0a7e4a72-b644-4bd8-afa1-ae8a1d6c111c",
+ "cellName": "Service Monitoring:\nCheck for\nStatus Change",
+ "cellId": "0a7e4a72-b644-4bd8-afa1-ae8a1d6c111c",
+ "visible": true,
+ "parentCellId": "1670815f-860d-4ea4-92e1-b238a799d3dc"
+ },
+ "generated-edge-simstep-9896c387-4fca-47ad-82eb-7301286decb7-0a7e4a72-b644-4bd8-afa1-ae8a1d6c111c": {
+ "path": "generated-edge-simstep-9896c387-4fca-47ad-82eb-7301286decb7-0a7e4a72-b644-4bd8-afa1-ae8a1d6c111c",
+ "fileName": "statusService.js",
+ "cellName": "Service Monitoring: Check for Status Change",
+ "cellId": "0a7e4a72-b644-4bd8-afa1-ae8a1d6c111c",
+ "visible": true,
+ "startLine": 51,
+ "endLine": 51,
+ "parentPath": "server/src/service/v1/infrastructure/statusService.js",
+ "simSteps": [
+ {
+ "simulationKey": "Comprehensive Uptime Monitoring for Various Services",
+ "simStepId": "9896c387-4fca-47ad-82eb-7301286decb7"
+ }
+ ]
+ },
+ "3f26b33e-477f-41df-a78a-3e3e4f4209ae": {
+ "path": "3f26b33e-477f-41df-a78a-3e3e4f4209ae",
+ "cellName": "Infrastructure",
+ "cellId": "3f26b33e-477f-41df-a78a-3e3e4f4209ae",
+ "visible": true,
+ "parentCellId": "1a083b4a-787c-4eb4-8029-6700de987679"
+ },
+ "74019e8a-3148-4a32-aae2-5afd16f3eb9b": {
+ "path": "74019e8a-3148-4a32-aae2-5afd16f3eb9b",
+ "cellName": "Create",
+ "cellId": "74019e8a-3148-4a32-aae2-5afd16f3eb9b",
+ "visible": true,
+ "parentCellId": "3f26b33e-477f-41df-a78a-3e3e4f4209ae"
+ },
+ "591128f9-0994-4e01-a1ab-f23357f97aff": {
+ "path": "591128f9-0994-4e01-a1ab-f23357f97aff",
+ "cellName": "Details",
+ "cellId": "591128f9-0994-4e01-a1ab-f23357f97aff",
+ "visible": true,
+ "parentCellId": "3f26b33e-477f-41df-a78a-3e3e4f4209ae"
+ },
+ "4940977f-933d-4bbc-b19e-749647b44d25": {
+ "path": "4940977f-933d-4bbc-b19e-749647b44d25",
+ "cellName": "notificationUtils.js",
+ "cellId": "4940977f-933d-4bbc-b19e-749647b44d25",
+ "visible": true,
+ "parentCellId": "1670815f-860d-4ea4-92e1-b238a799d3dc"
+ },
+ "a0b22a5a-59e1-47e3-8218-a4ad083b1b71": {
+ "path": "a0b22a5a-59e1-47e3-8218-a4ad083b1b71",
+ "cellName": "monitorModuleQueries.js",
+ "cellId": "a0b22a5a-59e1-47e3-8218-a4ad083b1b71",
+ "visible": true,
+ "parentCellId": "82cb3dbe-c922-438a-a912-255376f4b380"
+ },
+ "ceded1fc-87a9-43c8-a756-746be3d4ad8d": {
+ "path": "ceded1fc-87a9-43c8-a756-746be3d4ad8d",
+ "cellName": "Components",
+ "cellId": "ceded1fc-87a9-43c8-a756-746be3d4ad8d",
+ "visible": true,
+ "parentCellId": "74019e8a-3148-4a32-aae2-5afd16f3eb9b"
+ },
+ "e292c552-b3df-4bac-bd9a-8f44c260a5aa": {
+ "path": "e292c552-b3df-4bac-bd9a-8f44c260a5aa",
+ "cellName": "index.jsx",
+ "cellId": "e292c552-b3df-4bac-bd9a-8f44c260a5aa",
+ "visible": true,
+ "parentCellId": "591128f9-0994-4e01-a1ab-f23357f97aff"
+ },
+ "e97d4e61-483e-4ac5-85a1-0ff72fcdd449": {
+ "path": "e97d4e61-483e-4ac5-85a1-0ff72fcdd449",
+ "cellName": "Components",
+ "cellId": "e97d4e61-483e-4ac5-85a1-0ff72fcdd449",
+ "visible": true,
+ "parentCellId": "591128f9-0994-4e01-a1ab-f23357f97aff"
+ },
+ "83800be3-2a0e-4419-b379-3f995f4050e7": {
+ "path": "83800be3-2a0e-4419-b379-3f995f4050e7",
+ "cellName": "CustomAlertsSection.jsx",
+ "cellId": "83800be3-2a0e-4419-b379-3f995f4050e7",
+ "visible": true,
+ "parentCellId": "ceded1fc-87a9-43c8-a756-746be3d4ad8d"
+ },
+ "6a7c7720-a399-4d4d-8225-36e0b2f59731": {
+ "path": "6a7c7720-a399-4d4d-8225-36e0b2f59731",
+ "cellName": "GaugeBoxes",
+ "cellId": "6a7c7720-a399-4d4d-8225-36e0b2f59731",
+ "visible": true,
+ "parentCellId": "e97d4e61-483e-4ac5-85a1-0ff72fcdd449"
+ },
+ "8fe3d890-3605-407d-b06c-1d890a354e46": {
+ "path": "8fe3d890-3605-407d-b06c-1d890a354e46",
+ "cellName": "index.jsx",
+ "cellId": "8fe3d890-3605-407d-b06c-1d890a354e46",
+ "visible": true,
+ "parentCellId": "6a7c7720-a399-4d4d-8225-36e0b2f59731"
+ },
+ "9651d084-5c16-4417-89c1-f52ae6572a95": {
+ "path": "9651d084-5c16-4417-89c1-f52ae6572a95",
+ "cellName": "Configuration: User Sets Alert Thresholds - CustomAlertsSection.jsx:L45-53",
+ "cellId": "9651d084-5c16-4417-89c1-f52ae6572a95",
+ "visible": true,
+ "parentCellId": "83800be3-2a0e-4419-b379-3f995f4050e7"
+ },
+ "client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx-simstep-80dd889a-1fc9-4d56-b9cf-5b45bf1110b1": {
+ "path": "client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx-simstep-80dd889a-1fc9-4d56-b9cf-5b45bf1110b1",
+ "fileName": "CustomAlertsSection.jsx",
+ "wiki": "A user navigates to the 'Create Infrastructure Monitor' page to set up monitoring for a new server. They enable alerts for CPU, memory, and disk usage, and define specific thresholds for each metric. For example, an alert is configured to trigger if CPU usage exceeds 80%.",
+ "cellName": "Configuration: User Sets Alert Thresholds - CustomAlertsSection.jsx:L45-53",
+ "cellId": "9651d084-5c16-4417-89c1-f52ae6572a95",
+ "visible": true,
+ "startLine": 45,
+ "endLine": 53,
+ "parentCellId": "83800be3-2a0e-4419-b379-3f995f4050e7",
+ "parentPath": "client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Infrastructure and Hardware Health Monitoring",
+ "simStepId": "80dd889a-1fc9-4d56-b9cf-5b45bf1110b1"
+ }
+ ]
+ },
+ "4c47b5f2-5363-4816-9745-30e61d184c7a": {
+ "path": "4c47b5f2-5363-4816-9745-30e61d184c7a",
+ "cellName": "Automated Check: Backend Fetches Hardware Data - networkService.js:L7-14",
+ "cellId": "4c47b5f2-5363-4816-9745-30e61d184c7a",
+ "visible": true,
+ "parentCellId": "00406786-9663-440a-96b6-919d544732c0"
+ },
+ "server/src/service/v1/infrastructure/networkService.js-simstep-ed6e7faf-69dd-4d0b-bda8-137739ed288b": {
+ "path": "server/src/service/v1/infrastructure/networkService.js-simstep-ed6e7faf-69dd-4d0b-bda8-137739ed288b",
+ "fileName": "networkService.js",
+ "wiki": "The Checkmate server's job queue periodically initiates a check for the newly created hardware monitor. It makes an HTTP request to the monitor's URL, which points to the external 'Capture' agent running on the target server.",
+ "cellName": "Automated Check: Backend Fetches Hardware Data - networkService.js:L7-14",
+ "cellId": "4c47b5f2-5363-4816-9745-30e61d184c7a",
+ "visible": true,
+ "startLine": 7,
+ "endLine": 14,
+ "parentCellId": "00406786-9663-440a-96b6-919d544732c0",
+ "parentPath": "server/src/service/v1/infrastructure/networkService.js",
+ "simSteps": [
+ {
+ "simulationKey": "Infrastructure and Hardware Health Monitoring",
+ "simStepId": "ed6e7faf-69dd-4d0b-bda8-137739ed288b"
+ }
+ ]
+ },
+ "c1bf10c9-8c1a-44f4-8e23-39708f0301e8": {
+ "path": "c1bf10c9-8c1a-44f4-8e23-39708f0301e8",
+ "cellName": "Automated Check: Process and Store Hardware Data - statusService.js:L265-274",
+ "cellId": "c1bf10c9-8c1a-44f4-8e23-39708f0301e8",
+ "visible": true,
+ "parentCellId": "fbd7d244-2f37-453c-8c83-a23a260b26cc"
+ },
+ "server/src/service/v1/infrastructure/statusService.js-simstep-e5b90cf2-9369-4741-9803-c835a81c009b": {
+ "path": "server/src/service/v1/infrastructure/statusService.js-simstep-e5b90cf2-9369-4741-9803-c835a81c009b",
+ "fileName": "statusService.js",
+ "wiki": "The backend's StatusService receives the payload from the Capture agent. It parses the data and constructs a 'check' document. This document, containing the detailed hardware metrics, is then saved to the database for historical tracking and visualization.",
+ "cellName": "Automated Check: Process and Store Hardware Data - statusService.js:L265-274",
+ "cellId": "c1bf10c9-8c1a-44f4-8e23-39708f0301e8",
+ "visible": true,
+ "startLine": 265,
+ "endLine": 274,
+ "parentCellId": "fbd7d244-2f37-453c-8c83-a23a260b26cc",
+ "parentPath": "server/src/service/v1/infrastructure/statusService.js",
+ "simSteps": [
+ {
+ "simulationKey": "Infrastructure and Hardware Health Monitoring",
+ "simStepId": "e5b90cf2-9369-4741-9803-c835a81c009b"
+ }
+ ]
+ },
+ "d4f48f1a-61de-4cde-87d2-4b5215464899": {
+ "path": "d4f48f1a-61de-4cde-87d2-4b5215464899",
+ "cellName": "Automated Check: Evaluate Data Against Thresholds - notificationUtils.js:L95-109",
+ "cellId": "d4f48f1a-61de-4cde-87d2-4b5215464899",
+ "visible": true,
+ "parentCellId": "4940977f-933d-4bbc-b19e-749647b44d25"
+ },
+ "server/src/service/v1/infrastructure/notificationUtils.js-simstep-07fe8489-1d94-4afb-83f8-7cebb0dff49a": {
+ "path": "server/src/service/v1/infrastructure/notificationUtils.js-simstep-07fe8489-1d94-4afb-83f8-7cebb0dff49a",
+ "fileName": "notificationUtils.js",
+ "wiki": "The notification utility function compares the received hardware metrics against the user-defined thresholds stored in the monitor's configuration. In this case, the CPU usage of 85% is greater than the 80% threshold, so an alert condition is met.",
+ "cellName": "Automated Check: Evaluate Data Against Thresholds - notificationUtils.js:L95-109",
+ "cellId": "d4f48f1a-61de-4cde-87d2-4b5215464899",
+ "visible": true,
+ "startLine": 95,
+ "endLine": 109,
+ "parentCellId": "4940977f-933d-4bbc-b19e-749647b44d25",
+ "parentPath": "server/src/service/v1/infrastructure/notificationUtils.js",
+ "simSteps": [
+ {
+ "simulationKey": "Infrastructure and Hardware Health Monitoring",
+ "simStepId": "07fe8489-1d94-4afb-83f8-7cebb0dff49a"
+ }
+ ]
+ },
+ "801fe70a-1a19-406c-8f0d-770b0cb74409": {
+ "path": "801fe70a-1a19-406c-8f0d-770b0cb74409",
+ "cellName": "Visualization: User Views Monitor Details - index.jsx:L23-31",
+ "cellId": "801fe70a-1a19-406c-8f0d-770b0cb74409",
+ "visible": true,
+ "parentCellId": "e292c552-b3df-4bac-bd9a-8f44c260a5aa"
+ },
+ "client/src/Pages/v1/Infrastructure/Details/index.jsx-simstep-c27365db-9db6-4cc9-96f0-4f046fd38562": {
+ "path": "client/src/Pages/v1/Infrastructure/Details/index.jsx-simstep-c27365db-9db6-4cc9-96f0-4f046fd38562",
+ "fileName": "index.jsx",
+ "wiki": "A user navigates to the details page for their 'Production Web Server' monitor in the Checkmate UI to view its performance. The React component for the details page mounts and prepares to fetch data.",
+ "cellName": "Visualization: User Views Monitor Details - index.jsx:L23-31",
+ "cellId": "801fe70a-1a19-406c-8f0d-770b0cb74409",
+ "visible": true,
+ "startLine": 23,
+ "endLine": 31,
+ "parentCellId": "e292c552-b3df-4bac-bd9a-8f44c260a5aa",
+ "parentPath": "client/src/Pages/v1/Infrastructure/Details/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Infrastructure and Hardware Health Monitoring",
+ "simStepId": "c27365db-9db6-4cc9-96f0-4f046fd38562"
+ }
+ ]
+ },
+ "9ef7c423-f4fc-4caf-9bf5-c1aa3002ca03": {
+ "path": "9ef7c423-f4fc-4caf-9bf5-c1aa3002ca03",
+ "cellName": "Backend: Aggregate Historical Data - monitorModule.js:L312-322",
+ "cellId": "9ef7c423-f4fc-4caf-9bf5-c1aa3002ca03",
+ "visible": true,
+ "parentCellId": "630b3ece-2648-48d5-99b5-ef9611de2ad5"
+ },
+ "server/src/db/v1/modules/monitorModule.js-simstep-8502ae1f-f029-446e-9dde-59c2aba87a8a": {
+ "path": "server/src/db/v1/modules/monitorModule.js-simstep-8502ae1f-f029-446e-9dde-59c2aba87a8a",
+ "fileName": "monitorModule.js",
+ "wiki": "The backend receives the request and executes a database aggregation pipeline. This query fetches all 'hardware' checks for the monitor within the given date range and calculates metrics like average CPU usage, average memory usage, and average temperatures, grouped by time intervals.",
+ "cellName": "Backend: Aggregate Historical Data - monitorModule.js:L312-322",
+ "cellId": "9ef7c423-f4fc-4caf-9bf5-c1aa3002ca03",
+ "visible": true,
+ "startLine": 312,
+ "endLine": 322,
+ "parentCellId": "630b3ece-2648-48d5-99b5-ef9611de2ad5",
+ "parentPath": "server/src/db/v1/modules/monitorModule.js",
+ "simSteps": [
+ {
+ "simulationKey": "Infrastructure and Hardware Health Monitoring",
+ "simStepId": "8502ae1f-f029-446e-9dde-59c2aba87a8a"
+ }
+ ]
+ },
+ "9ea2e32d-1ea8-42f2-9a17-613844723ae6": {
+ "path": "9ea2e32d-1ea8-42f2-9a17-613844723ae6",
+ "cellName": "Visualization: Frontend Renders Charts and Gauges - index.jsx:L22-51",
+ "cellId": "9ea2e32d-1ea8-42f2-9a17-613844723ae6",
+ "visible": true,
+ "parentCellId": "8fe3d890-3605-407d-b06c-1d890a354e46"
+ },
+ "client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/index.jsx-simstep-6eedce82-b9c8-4022-95e5-3a6e00628105": {
+ "path": "client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/index.jsx-simstep-6eedce82-b9c8-4022-95e5-3a6e00628105",
+ "fileName": "index.jsx",
+ "wiki": "The frontend components on the Infrastructure Details page receive the aggregated data. The 'GaugeBoxes' component uses the 'latestCheck' data to render real-time gauges for current CPU and memory usage, while the 'AreaChartBoxes' component uses the historical 'checks' data to render time-series charts.",
+ "cellName": "Visualization: Frontend Renders Charts and Gauges - index.jsx:L22-51",
+ "cellId": "9ea2e32d-1ea8-42f2-9a17-613844723ae6",
+ "visible": true,
+ "startLine": 22,
+ "endLine": 51,
+ "parentCellId": "8fe3d890-3605-407d-b06c-1d890a354e46",
+ "parentPath": "client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Infrastructure and Hardware Health Monitoring",
+ "simStepId": "6eedce82-b9c8-4022-95e5-3a6e00628105"
+ }
+ ]
+ },
+ "1c5c9857-bf32-40e4-921b-957c252dac2a": {
+ "path": "1c5c9857-bf32-40e4-921b-957c252dac2a",
+ "cellName": "API Call:\nSubmit Monitor\nConfiguration",
+ "cellId": "1c5c9857-bf32-40e4-921b-957c252dac2a",
+ "visible": true
+ },
+ "generated-edge-simstep-815f96af-c28e-4f80-b4ab-18a6fb23eee0-1c5c9857-bf32-40e4-921b-957c252dac2a": {
+ "path": "generated-edge-simstep-815f96af-c28e-4f80-b4ab-18a6fb23eee0-1c5c9857-bf32-40e4-921b-957c252dac2a",
+ "fileName": "CustomAlertsSection.jsx",
+ "cellName": "API Call: Submit Monitor Configuration",
+ "cellId": "1c5c9857-bf32-40e4-921b-957c252dac2a",
+ "visible": true,
+ "startLine": 386,
+ "endLine": 389,
+ "parentPath": "client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Infrastructure and Hardware Health Monitoring",
+ "simStepId": "815f96af-c28e-4f80-b4ab-18a6fb23eee0"
+ }
+ ]
+ },
+ "a64547c9-d9e6-4941-8e5e-f49add06516b": {
+ "path": "a64547c9-d9e6-4941-8e5e-f49add06516b",
+ "cellName": "Automated Check:\nData Received\nfrom Capture\nAgent",
+ "cellId": "a64547c9-d9e6-4941-8e5e-f49add06516b",
+ "visible": true,
+ "parentCellId": "1670815f-860d-4ea4-92e1-b238a799d3dc"
+ },
+ "generated-edge-simstep-4434dca1-2048-4458-a70b-b70d569e378d-a64547c9-d9e6-4941-8e5e-f49add06516b": {
+ "path": "generated-edge-simstep-4434dca1-2048-4458-a70b-b70d569e378d-a64547c9-d9e6-4941-8e5e-f49add06516b",
+ "fileName": "networkService.js",
+ "cellName": "Automated Check: Data Received from Capture Agent",
+ "cellId": "a64547c9-d9e6-4941-8e5e-f49add06516b",
+ "visible": true,
+ "startLine": 266,
+ "endLine": 273,
+ "parentPath": "server/src/service/v1/infrastructure/networkService.js",
+ "simSteps": [
+ {
+ "simulationKey": "Infrastructure and Hardware Health Monitoring",
+ "simStepId": "4434dca1-2048-4458-a70b-b70d569e378d"
+ }
+ ]
+ },
+ "7a250d09-50c6-4935-b10c-626b0ebe1fba": {
+ "path": "7a250d09-50c6-4935-b10c-626b0ebe1fba",
+ "cellName": "Automated Check:\nPass Data\nfor Alert\nEvaluation",
+ "cellId": "7a250d09-50c6-4935-b10c-626b0ebe1fba",
+ "visible": true,
+ "parentCellId": "1670815f-860d-4ea4-92e1-b238a799d3dc"
+ },
+ "generated-edge-simstep-b019a6d0-a23a-4a1d-bd21-644f01996f14-7a250d09-50c6-4935-b10c-626b0ebe1fba": {
+ "path": "generated-edge-simstep-b019a6d0-a23a-4a1d-bd21-644f01996f14-7a250d09-50c6-4935-b10c-626b0ebe1fba",
+ "fileName": "statusService.js",
+ "cellName": "Automated Check: Pass Data for Alert Evaluation",
+ "cellId": "7a250d09-50c6-4935-b10c-626b0ebe1fba",
+ "visible": true,
+ "startLine": 60,
+ "endLine": 79,
+ "parentPath": "server/src/service/v1/infrastructure/statusService.js",
+ "simSteps": [
+ {
+ "simulationKey": "Infrastructure and Hardware Health Monitoring",
+ "simStepId": "b019a6d0-a23a-4a1d-bd21-644f01996f14"
+ }
+ ]
+ },
+ "3d357de8-9fd2-4d0b-9623-f36fce4cf1de": {
+ "path": "3d357de8-9fd2-4d0b-9623-f36fce4cf1de",
+ "cellName": "Automated Check:\nGenerate Alert\nNotification Content",
+ "cellId": "3d357de8-9fd2-4d0b-9623-f36fce4cf1de",
+ "visible": true
+ },
+ "generated-edge-simstep-c691b3b9-88b3-4c88-a399-ff9fd5866194-3d357de8-9fd2-4d0b-9623-f36fce4cf1de": {
+ "path": "generated-edge-simstep-c691b3b9-88b3-4c88-a399-ff9fd5866194-3d357de8-9fd2-4d0b-9623-f36fce4cf1de",
+ "fileName": "notificationUtils.js",
+ "cellName": "Automated Check: Generate Alert Notification Content",
+ "cellId": "3d357de8-9fd2-4d0b-9623-f36fce4cf1de",
+ "visible": true,
+ "startLine": 177,
+ "endLine": 183,
+ "parentPath": "server/src/service/v1/infrastructure/notificationUtils.js",
+ "simSteps": [
+ {
+ "simulationKey": "Infrastructure and Hardware Health Monitoring",
+ "simStepId": "c691b3b9-88b3-4c88-a399-ff9fd5866194"
+ }
+ ]
+ },
+ "00921831-b1d9-4a5a-ae6f-5ddee7cfc1e4": {
+ "path": "00921831-b1d9-4a5a-ae6f-5ddee7cfc1e4",
+ "cellName": "API Call:\nRequest for\nHistorical Hardware\nData",
+ "cellId": "00921831-b1d9-4a5a-ae6f-5ddee7cfc1e4",
+ "visible": true
+ },
+ "generated-edge-simstep-a5d20172-1c95-4c9e-af39-f86a498734bb-00921831-b1d9-4a5a-ae6f-5ddee7cfc1e4": {
+ "path": "generated-edge-simstep-a5d20172-1c95-4c9e-af39-f86a498734bb-00921831-b1d9-4a5a-ae6f-5ddee7cfc1e4",
+ "fileName": "index.jsx",
+ "cellName": "API Call: Request for Historical Hardware Data",
+ "cellId": "00921831-b1d9-4a5a-ae6f-5ddee7cfc1e4",
+ "visible": true,
+ "startLine": 198,
+ "endLine": 206,
+ "parentPath": "client/src/Pages/v1/Infrastructure/Details/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Infrastructure and Hardware Health Monitoring",
+ "simStepId": "a5d20172-1c95-4c9e-af39-f86a498734bb"
+ }
+ ]
+ },
+ "60308696-6f8a-43c8-b9a1-da199ef500cd": {
+ "path": "60308696-6f8a-43c8-b9a1-da199ef500cd",
+ "cellName": "API Response:\nSend Aggregated\nData to\nFrontend",
+ "cellId": "60308696-6f8a-43c8-b9a1-da199ef500cd",
+ "visible": true
+ },
+ "generated-edge-simstep-627fcb0c-0282-4b4d-8c55-aceb636e4da0-60308696-6f8a-43c8-b9a1-da199ef500cd": {
+ "path": "generated-edge-simstep-627fcb0c-0282-4b4d-8c55-aceb636e4da0-60308696-6f8a-43c8-b9a1-da199ef500cd",
+ "fileName": "monitorModule.js",
+ "cellName": "API Response: Send Aggregated Data to Frontend",
+ "cellId": "60308696-6f8a-43c8-b9a1-da199ef500cd",
+ "visible": true,
+ "startLine": 247,
+ "endLine": 262,
+ "parentPath": "server/src/db/v1/modules/monitorModule.js",
+ "simSteps": [
+ {
+ "simulationKey": "Infrastructure and Hardware Health Monitoring",
+ "simStepId": "627fcb0c-0282-4b4d-8c55-aceb636e4da0"
+ }
+ ]
+ },
+ "feefae96-61b8-4ac5-a37c-592ed4b2330e": {
+ "path": "feefae96-61b8-4ac5-a37c-592ed4b2330e",
+ "cellName": "v2",
+ "cellId": "feefae96-61b8-4ac5-a37c-592ed4b2330e",
+ "visible": true,
+ "parentCellId": "99e9b746-7809-41f4-a433-1de7cc90574b"
+ },
+ "b3575a92-df07-4cf7-810f-8e5e0672d666": {
+ "path": "b3575a92-df07-4cf7-810f-8e5e0672d666",
+ "cellName": "Notifications",
+ "cellId": "b3575a92-df07-4cf7-810f-8e5e0672d666",
+ "visible": true,
+ "parentCellId": "1a083b4a-787c-4eb4-8029-6700de987679"
+ },
+ "cd2428fd-6545-48dd-b685-5261311b7532": {
+ "path": "cd2428fd-6545-48dd-b685-5261311b7532",
+ "cellName": "useNotifications.js",
+ "cellId": "cd2428fd-6545-48dd-b685-5261311b7532",
+ "visible": true,
+ "parentCellId": "3bf0c0f9-cbb0-4f0c-8cb3-6e2a4abc0961"
+ },
+ "96250197-23af-4059-9904-fd6751f70ad5": {
+ "path": "96250197-23af-4059-9904-fd6751f70ad5",
+ "cellName": "notificationRoute.js",
+ "cellId": "96250197-23af-4059-9904-fd6751f70ad5",
+ "visible": true,
+ "parentCellId": "d3375a20-2776-4771-b806-649bbeb541bd"
+ },
+ "8e5941e1-e577-455f-bbf2-83a418ad5908": {
+ "path": "8e5941e1-e577-455f-bbf2-83a418ad5908",
+ "cellName": "notificationController.js",
+ "cellId": "8e5941e1-e577-455f-bbf2-83a418ad5908",
+ "visible": true,
+ "parentCellId": "fc591f7a-d4ac-4565-949d-a87a58a79bbc"
+ },
+ "3f8ff792-40c8-4ae6-9af5-ff8b90da2f9f": {
+ "path": "3f8ff792-40c8-4ae6-9af5-ff8b90da2f9f",
+ "cellName": "infrastructure",
+ "cellId": "3f8ff792-40c8-4ae6-9af5-ff8b90da2f9f",
+ "visible": true,
+ "parentCellId": "feefae96-61b8-4ac5-a37c-592ed4b2330e"
+ },
+ "a6fb23f0-73bc-40ee-bb3c-ed93605a2bcb": {
+ "path": "a6fb23f0-73bc-40ee-bb3c-ed93605a2bcb",
+ "cellName": "create",
+ "cellId": "a6fb23f0-73bc-40ee-bb3c-ed93605a2bcb",
+ "visible": true,
+ "parentCellId": "b3575a92-df07-4cf7-810f-8e5e0672d666"
+ },
+ "62daeac4-9402-448a-972c-cb8cb760671c": {
+ "path": "62daeac4-9402-448a-972c-cb8cb760671c",
+ "cellName": "notificationModule.js",
+ "cellId": "62daeac4-9402-448a-972c-cb8cb760671c",
+ "visible": true,
+ "parentCellId": "82cb3dbe-c922-438a-a912-255376f4b380"
+ },
+ "159e569d-31d8-4883-b443-4ec08b21a044": {
+ "path": "159e569d-31d8-4883-b443-4ec08b21a044",
+ "cellName": "JobGenerator.ts",
+ "cellId": "159e569d-31d8-4883-b443-4ec08b21a044",
+ "visible": true,
+ "parentCellId": "3f8ff792-40c8-4ae6-9af5-ff8b90da2f9f"
+ },
+ "c6d9907f-0a05-4153-a27f-47c3a1c4c0aa": {
+ "path": "c6d9907f-0a05-4153-a27f-47c3a1c4c0aa",
+ "cellName": "NotificationService.ts",
+ "cellId": "c6d9907f-0a05-4153-a27f-47c3a1c4c0aa",
+ "visible": true,
+ "parentCellId": "3f8ff792-40c8-4ae6-9af5-ff8b90da2f9f"
+ },
+ "b71b02fb-d08e-4fcf-b959-de1b9547d9c2": {
+ "path": "b71b02fb-d08e-4fcf-b959-de1b9547d9c2",
+ "cellName": "index.jsx",
+ "cellId": "b71b02fb-d08e-4fcf-b959-de1b9547d9c2",
+ "visible": true,
+ "parentCellId": "a6fb23f0-73bc-40ee-bb3c-ed93605a2bcb"
+ },
+ "15486629-84b3-463a-962b-06083f69e4bf": {
+ "path": "15486629-84b3-463a-962b-06083f69e4bf",
+ "cellName": "Flow 1: Render Notification Creation Form - index.jsx:L157-321",
+ "cellId": "15486629-84b3-463a-962b-06083f69e4bf",
+ "visible": true,
+ "parentCellId": "b71b02fb-d08e-4fcf-b959-de1b9547d9c2"
+ },
+ "client/src/Pages/v1/Notifications/create/index.jsx-simstep-e924ec15-1bea-4e3b-befb-a8bf30cbf740": {
+ "path": "client/src/Pages/v1/Notifications/create/index.jsx-simstep-e924ec15-1bea-4e3b-befb-a8bf30cbf740",
+ "fileName": "index.jsx",
+ "wiki": "User accesses the `/notifications/create` route, which renders the `CreateNotifications` component. This component displays a form for setting up a new notification channel, including fields for name, type, and provider-specific details.",
+ "cellName": "Flow 1: Render Notification Creation Form - index.jsx:L157-321",
+ "cellId": "15486629-84b3-463a-962b-06083f69e4bf",
+ "visible": true,
+ "startLine": 157,
+ "endLine": 321,
+ "parentCellId": "b71b02fb-d08e-4fcf-b959-de1b9547d9c2",
+ "parentPath": "client/src/Pages/v1/Notifications/create/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Multi-Channel Alerting and Notifications",
+ "simStepId": "e924ec15-1bea-4e3b-befb-a8bf30cbf740"
+ }
+ ]
+ },
+ "a07b805e-e55f-46cb-b54b-765b7813b0ca": {
+ "path": "a07b805e-e55f-46cb-b54b-765b7813b0ca",
+ "cellName": "Flow 1: Frontend API Call - useNotifications.js:L14-17",
+ "cellId": "a07b805e-e55f-46cb-b54b-765b7813b0ca",
+ "visible": true,
+ "parentCellId": "cd2428fd-6545-48dd-b685-5261311b7532"
+ },
+ "client/src/Hooks/v1/useNotifications.js-simstep-383c6be8-aa51-4398-9ae3-21035413f73a": {
+ "path": "client/src/Hooks/v1/useNotifications.js-simstep-383c6be8-aa51-4398-9ae3-21035413f73a",
+ "fileName": "useNotifications.js",
+ "wiki": "The `createNotification` hook executes an asynchronous function that calls `networkService.createNotification` to send the new notification data to the backend API.",
+ "cellName": "Flow 1: Frontend API Call - useNotifications.js:L14-17",
+ "cellId": "a07b805e-e55f-46cb-b54b-765b7813b0ca",
+ "visible": true,
+ "startLine": 14,
+ "endLine": 17,
+ "parentCellId": "cd2428fd-6545-48dd-b685-5261311b7532",
+ "parentPath": "client/src/Hooks/v1/useNotifications.js",
+ "simSteps": [
+ {
+ "simulationKey": "Multi-Channel Alerting and Notifications",
+ "simStepId": "383c6be8-aa51-4398-9ae3-21035413f73a"
+ }
+ ]
+ },
+ "d0a4737f-715f-4f50-b6d5-5e4b31f86f93": {
+ "path": "d0a4737f-715f-4f50-b6d5-5e4b31f86f93",
+ "cellName": "Flow 1: Backend Route Handling - notificationRoute.js:L10",
+ "cellId": "d0a4737f-715f-4f50-b6d5-5e4b31f86f93",
+ "visible": true,
+ "parentCellId": "96250197-23af-4059-9904-fd6751f70ad5"
+ },
+ "server/src/routes/v1/notificationRoute.js-simstep-2f9266c9-4cbc-4b50-a280-cf8b5800d540": {
+ "path": "server/src/routes/v1/notificationRoute.js-simstep-2f9266c9-4cbc-4b50-a280-cf8b5800d540",
+ "fileName": "notificationRoute.js",
+ "wiki": "The backend server receives the POST request. The Express router matches the `/notifications` path and forwards the request to the `createNotification` method of the `NotificationController`.",
+ "cellName": "Flow 1: Backend Route Handling - notificationRoute.js:L10",
+ "cellId": "d0a4737f-715f-4f50-b6d5-5e4b31f86f93",
+ "visible": true,
+ "startLine": 10,
+ "endLine": 10,
+ "parentCellId": "96250197-23af-4059-9904-fd6751f70ad5",
+ "parentPath": "server/src/routes/v1/notificationRoute.js",
+ "simSteps": [
+ {
+ "simulationKey": "Multi-Channel Alerting and Notifications",
+ "simStepId": "2f9266c9-4cbc-4b50-a280-cf8b5800d540"
+ }
+ ]
+ },
+ "4a796ccf-40a9-4446-a109-6a6eed52e898": {
+ "path": "4a796ccf-40a9-4446-a109-6a6eed52e898",
+ "cellName": "Flow 1: Database Insertion - notificationModule.js:L10-13",
+ "cellId": "4a796ccf-40a9-4446-a109-6a6eed52e898",
+ "visible": true,
+ "parentCellId": "62daeac4-9402-448a-972c-cb8cb760671c"
+ },
+ "server/src/db/v1/modules/notificationModule.js-simstep-d29ab4ab-be50-4f17-af6e-2c61cec9d0ab": {
+ "path": "server/src/db/v1/modules/notificationModule.js-simstep-d29ab4ab-be50-4f17-af6e-2c61cec9d0ab",
+ "fileName": "notificationModule.js",
+ "wiki": "The `notificationModule` creates a new instance of the `Notification` model with the provided data and saves it to the MongoDB database.",
+ "cellName": "Flow 1: Database Insertion - notificationModule.js:L10-13",
+ "cellId": "4a796ccf-40a9-4446-a109-6a6eed52e898",
+ "visible": true,
+ "startLine": 10,
+ "endLine": 13,
+ "parentCellId": "62daeac4-9402-448a-972c-cb8cb760671c",
+ "parentPath": "server/src/db/v1/modules/notificationModule.js",
+ "simSteps": [
+ {
+ "simulationKey": "Multi-Channel Alerting and Notifications",
+ "simStepId": "d29ab4ab-be50-4f17-af6e-2c61cec9d0ab"
+ }
+ ]
+ },
+ "29c2c56e-9dc0-4304-bfbf-e0c0f8915a39": {
+ "path": "29c2c56e-9dc0-4304-bfbf-e0c0f8915a39",
+ "cellName": "Flow 1: Backend Receives Test Request - notificationRoute.js:L13",
+ "cellId": "29c2c56e-9dc0-4304-bfbf-e0c0f8915a39",
+ "visible": true,
+ "parentCellId": "96250197-23af-4059-9904-fd6751f70ad5"
+ },
+ "server/src/routes/v1/notificationRoute.js-simstep-c1b17b64-90e9-48bc-8927-25d7ebfc3036": {
+ "path": "server/src/routes/v1/notificationRoute.js-simstep-c1b17b64-90e9-48bc-8927-25d7ebfc3036",
+ "fileName": "notificationRoute.js",
+ "wiki": "The client sends a POST request to `/notifications/test`. The router directs this request to the `testNotification` method in the `NotificationController`.",
+ "cellName": "Flow 1: Backend Receives Test Request - notificationRoute.js:L13",
+ "cellId": "29c2c56e-9dc0-4304-bfbf-e0c0f8915a39",
+ "visible": true,
+ "startLine": 13,
+ "endLine": 13,
+ "parentCellId": "96250197-23af-4059-9904-fd6751f70ad5",
+ "parentPath": "server/src/routes/v1/notificationRoute.js",
+ "simSteps": [
+ {
+ "simulationKey": "Multi-Channel Alerting and Notifications",
+ "simStepId": "c1b17b64-90e9-48bc-8927-25d7ebfc3036"
+ }
+ ]
+ },
+ "1cc01e08-4a6c-4526-b8a7-5ee8a06c1709": {
+ "path": "1cc01e08-4a6c-4526-b8a7-5ee8a06c1709",
+ "cellName": "Flow 1: Send Test Notification - notificationService.js:L120-123",
+ "cellId": "1cc01e08-4a6c-4526-b8a7-5ee8a06c1709",
+ "visible": true,
+ "parentCellId": "2ff1f030-2080-48da-bbbe-90302fdfc2f7"
+ },
+ "server/src/service/v1/infrastructure/notificationService.js-simstep-f29f9be1-a0cd-43a3-b259-e5105a25ce41": {
+ "path": "server/src/service/v1/infrastructure/notificationService.js-simstep-f29f9be1-a0cd-43a3-b259-e5105a25ce41",
+ "fileName": "notificationService.js",
+ "wiki": "The `NotificationService` prepares a standard test message and calls its internal `sendNotification` method. This method identifies the notification type (e.g., email, Slack) and uses the corresponding service (e.g., EmailService, NetworkService for webhooks) to dispatch the test message.",
+ "cellName": "Flow 1: Send Test Notification - notificationService.js:L120-123",
+ "cellId": "1cc01e08-4a6c-4526-b8a7-5ee8a06c1709",
+ "visible": true,
+ "startLine": 120,
+ "endLine": 123,
+ "parentCellId": "2ff1f030-2080-48da-bbbe-90302fdfc2f7",
+ "parentPath": "server/src/service/v1/infrastructure/notificationService.js",
+ "simSteps": [
+ {
+ "simulationKey": "Multi-Channel Alerting and Notifications",
+ "simStepId": "f29f9be1-a0cd-43a3-b259-e5105a25ce41"
+ }
+ ]
+ },
+ "28f1eabb-4f24-4944-8a0a-04f41ceb8589": {
+ "path": "28f1eabb-4f24-4944-8a0a-04f41ceb8589",
+ "cellName": "Flow 2: Job Execution and Status Update - JobGenerator.ts:L43-66",
+ "cellId": "28f1eabb-4f24-4944-8a0a-04f41ceb8589",
+ "visible": true,
+ "parentCellId": "159e569d-31d8-4883-b443-4ec08b21a044"
+ },
+ "server/src/service/v2/infrastructure/JobGenerator.ts-simstep-157cec87-9b76-4a1c-9d85-0a6d3decbaa3": {
+ "path": "server/src/service/v2/infrastructure/JobGenerator.ts-simstep-157cec87-9b76-4a1c-9d85-0a6d3decbaa3",
+ "fileName": "JobGenerator.ts",
+ "wiki": "A background job is generated for a monitor. When executed, it performs a network check, saves the result, and updates the monitor's status via `statusService.updateMonitorStatus`.",
+ "cellName": "Flow 2: Job Execution and Status Update - JobGenerator.ts:L43-66",
+ "cellId": "28f1eabb-4f24-4944-8a0a-04f41ceb8589",
+ "visible": true,
+ "startLine": 43,
+ "endLine": 66,
+ "parentCellId": "159e569d-31d8-4883-b443-4ec08b21a044",
+ "parentPath": "server/src/service/v2/infrastructure/JobGenerator.ts",
+ "simSteps": [
+ {
+ "simulationKey": "Multi-Channel Alerting and Notifications",
+ "simStepId": "157cec87-9b76-4a1c-9d85-0a6d3decbaa3"
+ }
+ ]
+ },
+ "31fe5a28-0640-4b52-841b-dd02e2ae646e": {
+ "path": "31fe5a28-0640-4b52-841b-dd02e2ae646e",
+ "cellName": "Flow 2: Initiate Notification Handling - JobGenerator.ts:L61",
+ "cellId": "31fe5a28-0640-4b52-841b-dd02e2ae646e",
+ "visible": true,
+ "parentCellId": "159e569d-31d8-4883-b443-4ec08b21a044"
+ },
+ "server/src/service/v2/infrastructure/JobGenerator.ts-simstep-55ad645b-118a-478e-a00c-09ef82357008": {
+ "path": "server/src/service/v2/infrastructure/JobGenerator.ts-simstep-55ad645b-118a-478e-a00c-09ef82357008",
+ "fileName": "JobGenerator.ts",
+ "wiki": "If the monitor's status has changed, the `handleNotifications` method of the `NotificationService` is invoked with the updated monitor's data.",
+ "cellName": "Flow 2: Initiate Notification Handling - JobGenerator.ts:L61",
+ "cellId": "31fe5a28-0640-4b52-841b-dd02e2ae646e",
+ "visible": true,
+ "startLine": 61,
+ "endLine": 61,
+ "parentCellId": "159e569d-31d8-4883-b443-4ec08b21a044",
+ "parentPath": "server/src/service/v2/infrastructure/JobGenerator.ts",
+ "simSteps": [
+ {
+ "simulationKey": "Multi-Channel Alerting and Notifications",
+ "simStepId": "55ad645b-118a-478e-a00c-09ef82357008"
+ }
+ ]
+ },
+ "78e5cb9f-6a97-4d31-a487-fe7dbce5d7d1": {
+ "path": "78e5cb9f-6a97-4d31-a487-fe7dbce5d7d1",
+ "cellName": "Flow 2: Dispatch Alerts - NotificationService.ts:L28-57",
+ "cellId": "78e5cb9f-6a97-4d31-a487-fe7dbce5d7d1",
+ "visible": true,
+ "parentCellId": "c6d9907f-0a05-4153-a27f-47c3a1c4c0aa"
+ },
+ "server/src/service/v2/infrastructure/NotificationService.ts-simstep-8d1176f3-c755-401d-9ca8-234689b4b837": {
+ "path": "server/src/service/v2/infrastructure/NotificationService.ts-simstep-8d1176f3-c755-401d-9ca8-234689b4b837",
+ "fileName": "NotificationService.ts",
+ "wiki": "The `handleNotifications` method retrieves the full details for each associated notification channel. It then iterates through them, using specific provider services (e.g., `SlackService`, `EmailService`) to format and send a tailored alert message for each channel, notifying users that the monitor is down.",
+ "cellName": "Flow 2: Dispatch Alerts - NotificationService.ts:L28-57",
+ "cellId": "78e5cb9f-6a97-4d31-a487-fe7dbce5d7d1",
+ "visible": true,
+ "startLine": 28,
+ "endLine": 57,
+ "parentCellId": "c6d9907f-0a05-4153-a27f-47c3a1c4c0aa",
+ "parentPath": "server/src/service/v2/infrastructure/NotificationService.ts",
+ "simSteps": [
+ {
+ "simulationKey": "Multi-Channel Alerting and Notifications",
+ "simStepId": "8d1176f3-c755-401d-9ca8-234689b4b837"
+ }
+ ]
+ },
+ "54497b9a-4910-4df4-b016-480b9ac250a7": {
+ "path": "54497b9a-4910-4df4-b016-480b9ac250a7",
+ "cellName": "Flow 1:\nSubmit Form\nData for\nCreation",
+ "cellId": "54497b9a-4910-4df4-b016-480b9ac250a7",
+ "visible": true,
+ "parentCellId": "d3264577-10f0-42fe-9411-7e9df0754d1e"
+ },
+ "generated-edge-simstep-71dbcd41-c6d4-4514-b5a4-6c966ca7ba6f-54497b9a-4910-4df4-b016-480b9ac250a7": {
+ "path": "generated-edge-simstep-71dbcd41-c6d4-4514-b5a4-6c966ca7ba6f-54497b9a-4910-4df4-b016-480b9ac250a7",
+ "fileName": "index.jsx",
+ "cellName": "Flow 1: Submit Form Data for Creation",
+ "cellId": "54497b9a-4910-4df4-b016-480b9ac250a7",
+ "visible": true,
+ "startLine": 96,
+ "endLine": 101,
+ "parentPath": "client/src/Pages/v1/Notifications/create/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Multi-Channel Alerting and Notifications",
+ "simStepId": "71dbcd41-c6d4-4514-b5a4-6c966ca7ba6f"
+ }
+ ]
+ },
+ "dc135d8d-0515-403d-ae58-f3c04647b2ae": {
+ "path": "dc135d8d-0515-403d-ae58-f3c04647b2ae",
+ "cellName": "Flow 1:\nHTTP POST\nRequest",
+ "cellId": "dc135d8d-0515-403d-ae58-f3c04647b2ae",
+ "visible": true
+ },
+ "generated-edge-simstep-162d8d2f-6cd1-45fa-8711-1ee85c5f86ef-dc135d8d-0515-403d-ae58-f3c04647b2ae": {
+ "path": "generated-edge-simstep-162d8d2f-6cd1-45fa-8711-1ee85c5f86ef-dc135d8d-0515-403d-ae58-f3c04647b2ae",
+ "fileName": "useNotifications.js",
+ "cellName": "Flow 1: HTTP POST Request",
+ "cellId": "dc135d8d-0515-403d-ae58-f3c04647b2ae",
+ "visible": true,
+ "startLine": 1064,
+ "endLine": 1066,
+ "parentPath": "client/src/Hooks/v1/useNotifications.js",
+ "simSteps": [
+ {
+ "simulationKey": "Multi-Channel Alerting and Notifications",
+ "simStepId": "162d8d2f-6cd1-45fa-8711-1ee85c5f86ef"
+ }
+ ]
+ },
+ "7b9ebb0e-a6b2-438c-bfed-9c851a1bccbc": {
+ "path": "7b9ebb0e-a6b2-438c-bfed-9c851a1bccbc",
+ "cellName": "Flow 1:\nController to\nDatabase Module",
+ "cellId": "7b9ebb0e-a6b2-438c-bfed-9c851a1bccbc",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "generated-edge-simstep-73e63d2f-2b43-488d-9e86-170f511673f5-7b9ebb0e-a6b2-438c-bfed-9c851a1bccbc": {
+ "path": "generated-edge-simstep-73e63d2f-2b43-488d-9e86-170f511673f5-7b9ebb0e-a6b2-438c-bfed-9c851a1bccbc",
+ "fileName": "notificationRoute.js",
+ "cellName": "Flow 1: Controller to Database Module",
+ "cellId": "7b9ebb0e-a6b2-438c-bfed-9c851a1bccbc",
+ "visible": true,
+ "startLine": 55,
+ "endLine": 55,
+ "parentPath": "server/src/routes/v1/notificationRoute.js",
+ "simSteps": [
+ {
+ "simulationKey": "Multi-Channel Alerting and Notifications",
+ "simStepId": "73e63d2f-2b43-488d-9e86-170f511673f5"
+ }
+ ]
+ },
+ "279155cd-ff31-41a2-8f5f-a538f01bfa66": {
+ "path": "279155cd-ff31-41a2-8f5f-a538f01bfa66",
+ "cellName": "Flow 1:\nUser Clicks\nTest Notification\nButton",
+ "cellId": "279155cd-ff31-41a2-8f5f-a538f01bfa66",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "generated-edge-simstep-d390ef45-f2fe-4806-a45a-cc9c84b48a5a-279155cd-ff31-41a2-8f5f-a538f01bfa66": {
+ "path": "generated-edge-simstep-d390ef45-f2fe-4806-a45a-cc9c84b48a5a-279155cd-ff31-41a2-8f5f-a538f01bfa66",
+ "fileName": "notificationModule.js",
+ "cellName": "Flow 1: User Clicks Test Notification Button",
+ "cellId": "279155cd-ff31-41a2-8f5f-a538f01bfa66",
+ "visible": true,
+ "startLine": 281,
+ "endLine": 285,
+ "parentPath": "server/src/db/v1/modules/notificationModule.js",
+ "simSteps": [
+ {
+ "simulationKey": "Multi-Channel Alerting and Notifications",
+ "simStepId": "d390ef45-f2fe-4806-a45a-cc9c84b48a5a"
+ }
+ ]
+ },
+ "57018b30-e2f4-4eb2-a1da-fe75afbb8c2f": {
+ "path": "57018b30-e2f4-4eb2-a1da-fe75afbb8c2f",
+ "cellName": "Flow 1:\nController to\nNotification Service",
+ "cellId": "57018b30-e2f4-4eb2-a1da-fe75afbb8c2f",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "generated-edge-simstep-6f2bf027-50d9-472e-9569-808a64fce7cf-57018b30-e2f4-4eb2-a1da-fe75afbb8c2f": {
+ "path": "generated-edge-simstep-6f2bf027-50d9-472e-9569-808a64fce7cf-57018b30-e2f4-4eb2-a1da-fe75afbb8c2f",
+ "fileName": "notificationRoute.js",
+ "cellName": "Flow 1: Controller to Notification Service",
+ "cellId": "57018b30-e2f4-4eb2-a1da-fe75afbb8c2f",
+ "visible": true,
+ "startLine": 22,
+ "endLine": 22,
+ "parentPath": "server/src/routes/v1/notificationRoute.js",
+ "simSteps": [
+ {
+ "simulationKey": "Multi-Channel Alerting and Notifications",
+ "simStepId": "6f2bf027-50d9-472e-9569-808a64fce7cf"
+ }
+ ]
+ },
+ "50797935-1447-47a4-891b-9e886e5601fa": {
+ "path": "50797935-1447-47a4-891b-9e886e5601fa",
+ "cellName": "Flow 2:\nStatus Change\nDetected",
+ "cellId": "50797935-1447-47a4-891b-9e886e5601fa",
+ "visible": true,
+ "parentCellId": "159e569d-31d8-4883-b443-4ec08b21a044"
+ },
+ "generated-edge-simstep-b7ec7415-fcae-4077-8c19-302a015b00b5-50797935-1447-47a4-891b-9e886e5601fa": {
+ "path": "generated-edge-simstep-b7ec7415-fcae-4077-8c19-302a015b00b5-50797935-1447-47a4-891b-9e886e5601fa",
+ "fileName": "JobGenerator.ts",
+ "cellName": "Flow 2: Status Change Detected",
+ "cellId": "50797935-1447-47a4-891b-9e886e5601fa",
+ "visible": true,
+ "startLine": 60,
+ "endLine": 60,
+ "parentPath": "server/src/service/v2/infrastructure/JobGenerator.ts",
+ "simSteps": [
+ {
+ "simulationKey": "Multi-Channel Alerting and Notifications",
+ "simStepId": "b7ec7415-fcae-4077-8c19-302a015b00b5"
+ }
+ ]
+ },
+ "c778c6b9-c12e-45c0-8682-2c9e892867b0": {
+ "path": "c778c6b9-c12e-45c0-8682-2c9e892867b0",
+ "cellName": "Flow 2:\nMonitor Data\nPassed to\nNotification Service",
+ "cellId": "c778c6b9-c12e-45c0-8682-2c9e892867b0",
+ "visible": true,
+ "parentCellId": "3f8ff792-40c8-4ae6-9af5-ff8b90da2f9f"
+ },
+ "generated-edge-simstep-b7259d77-c8df-4060-ac03-58728ae3eb54-c778c6b9-c12e-45c0-8682-2c9e892867b0": {
+ "path": "generated-edge-simstep-b7259d77-c8df-4060-ac03-58728ae3eb54-c778c6b9-c12e-45c0-8682-2c9e892867b0",
+ "fileName": "JobGenerator.ts",
+ "cellName": "Flow 2: Monitor Data Passed to Notification Service",
+ "cellId": "c778c6b9-c12e-45c0-8682-2c9e892867b0",
+ "visible": true,
+ "startLine": 7,
+ "endLine": 7,
+ "parentPath": "server/src/service/v2/infrastructure/JobGenerator.ts",
+ "simSteps": [
+ {
+ "simulationKey": "Multi-Channel Alerting and Notifications",
+ "simStepId": "b7259d77-c8df-4060-ac03-58728ae3eb54"
+ }
+ ]
+ },
+ "987f145b-5df6-4c30-bf1c-2a2d2b0e5cdc": {
+ "path": "987f145b-5df6-4c30-bf1c-2a2d2b0e5cdc",
+ "cellName": "Routes",
+ "cellId": "987f145b-5df6-4c30-bf1c-2a2d2b0e5cdc",
+ "visible": true,
+ "parentCellId": "d3264577-10f0-42fe-9411-7e9df0754d1e"
+ },
+ "c4d7e7dd-75b1-44f1-a34a-6ed290a8e562": {
+ "path": "c4d7e7dd-75b1-44f1-a34a-6ed290a8e562",
+ "cellName": "index.jsx",
+ "cellId": "c4d7e7dd-75b1-44f1-a34a-6ed290a8e562",
+ "visible": true,
+ "parentCellId": "987f145b-5df6-4c30-bf1c-2a2d2b0e5cdc"
+ },
+ "9d662180-8b65-4426-ad37-cfed4bc01fb4": {
+ "path": "9d662180-8b65-4426-ad37-cfed4bc01fb4",
+ "cellName": "StatusPage",
+ "cellId": "9d662180-8b65-4426-ad37-cfed4bc01fb4",
+ "visible": true,
+ "parentCellId": "1a083b4a-787c-4eb4-8029-6700de987679"
+ },
+ "773e1065-b0a5-46a9-b0cc-6b963f69f5cc": {
+ "path": "773e1065-b0a5-46a9-b0cc-6b963f69f5cc",
+ "cellName": "statusPageRoute.js",
+ "cellId": "773e1065-b0a5-46a9-b0cc-6b963f69f5cc",
+ "visible": true,
+ "parentCellId": "d3375a20-2776-4771-b806-649bbeb541bd"
+ },
+ "17a4f435-5a8e-4433-84a6-9ac17362709a": {
+ "path": "17a4f435-5a8e-4433-84a6-9ac17362709a",
+ "cellName": "statusPageController.js",
+ "cellId": "17a4f435-5a8e-4433-84a6-9ac17362709a",
+ "visible": true,
+ "parentCellId": "fc591f7a-d4ac-4565-949d-a87a58a79bbc"
+ },
+ "08602a28-f073-484e-9403-7eb798126dd4": {
+ "path": "08602a28-f073-484e-9403-7eb798126dd4",
+ "cellName": "Create",
+ "cellId": "08602a28-f073-484e-9403-7eb798126dd4",
+ "visible": true,
+ "parentCellId": "9d662180-8b65-4426-ad37-cfed4bc01fb4"
+ },
+ "8ef70566-37d9-48fb-b5e8-37e5b68768a2": {
+ "path": "8ef70566-37d9-48fb-b5e8-37e5b68768a2",
+ "cellName": "Status",
+ "cellId": "8ef70566-37d9-48fb-b5e8-37e5b68768a2",
+ "visible": true,
+ "parentCellId": "9d662180-8b65-4426-ad37-cfed4bc01fb4"
+ },
+ "f8541fe3-4bcf-4833-a226-66b5301adf58": {
+ "path": "f8541fe3-4bcf-4833-a226-66b5301adf58",
+ "cellName": "statusPageModule.js",
+ "cellId": "f8541fe3-4bcf-4833-a226-66b5301adf58",
+ "visible": true,
+ "parentCellId": "82cb3dbe-c922-438a-a912-255376f4b380"
+ },
+ "e2d2bbf6-dffd-4edf-8c5d-34088d9d4745": {
+ "path": "e2d2bbf6-dffd-4edf-8c5d-34088d9d4745",
+ "cellName": "index.jsx",
+ "cellId": "e2d2bbf6-dffd-4edf-8c5d-34088d9d4745",
+ "visible": true,
+ "parentCellId": "08602a28-f073-484e-9403-7eb798126dd4"
+ },
+ "56f6775e-9843-4ca2-bc8c-1c263b6c35e9": {
+ "path": "56f6775e-9843-4ca2-bc8c-1c263b6c35e9",
+ "cellName": "Hooks",
+ "cellId": "56f6775e-9843-4ca2-bc8c-1c263b6c35e9",
+ "visible": true,
+ "parentCellId": "08602a28-f073-484e-9403-7eb798126dd4"
+ },
+ "e1e6f5e0-fd8c-4260-aa4f-cdb096c6b1c3": {
+ "path": "e1e6f5e0-fd8c-4260-aa4f-cdb096c6b1c3",
+ "cellName": "index.jsx",
+ "cellId": "e1e6f5e0-fd8c-4260-aa4f-cdb096c6b1c3",
+ "visible": true,
+ "parentCellId": "8ef70566-37d9-48fb-b5e8-37e5b68768a2"
+ },
+ "79da0721-d810-4f7f-9cf9-3ea462bf0bff": {
+ "path": "79da0721-d810-4f7f-9cf9-3ea462bf0bff",
+ "cellName": "Hooks",
+ "cellId": "79da0721-d810-4f7f-9cf9-3ea462bf0bff",
+ "visible": true,
+ "parentCellId": "8ef70566-37d9-48fb-b5e8-37e5b68768a2"
+ },
+ "3945e05f-2d99-4d6d-9a3e-b6c75ea3c45a": {
+ "path": "3945e05f-2d99-4d6d-9a3e-b6c75ea3c45a",
+ "cellName": "useCreateStatusPage.jsx",
+ "cellId": "3945e05f-2d99-4d6d-9a3e-b6c75ea3c45a",
+ "visible": true,
+ "parentCellId": "56f6775e-9843-4ca2-bc8c-1c263b6c35e9"
+ },
+ "5b2cdab8-66f1-40a1-9621-0efaf54e04ae": {
+ "path": "5b2cdab8-66f1-40a1-9621-0efaf54e04ae",
+ "cellName": "useStatusPageFetch.jsx",
+ "cellId": "5b2cdab8-66f1-40a1-9621-0efaf54e04ae",
+ "visible": true,
+ "parentCellId": "79da0721-d810-4f7f-9cf9-3ea462bf0bff"
+ },
+ "00289d11-0015-4b2a-9c5b-dc9fd0f32b80": {
+ "path": "00289d11-0015-4b2a-9c5b-dc9fd0f32b80",
+ "cellName": "Create Status Page: User Initiates Creation - index.jsx:L26-313",
+ "cellId": "00289d11-0015-4b2a-9c5b-dc9fd0f32b80",
+ "visible": true,
+ "parentCellId": "e2d2bbf6-dffd-4edf-8c5d-34088d9d4745"
+ },
+ "client/src/Pages/v1/StatusPage/Create/index.jsx-simstep-de7b5d1f-b370-42a4-a410-4178f1e2d3a6": {
+ "path": "client/src/Pages/v1/StatusPage/Create/index.jsx-simstep-de7b5d1f-b370-42a4-a410-4178f1e2d3a6",
+ "fileName": "index.jsx",
+ "wiki": "The user navigates to the 'Create Status Page' screen. The `CreateStatusPage` component renders the UI, including tabs for configuration, and initializes the form state.",
+ "cellName": "Create Status Page: User Initiates Creation - index.jsx:L26-313",
+ "cellId": "00289d11-0015-4b2a-9c5b-dc9fd0f32b80",
+ "visible": true,
+ "startLine": 26,
+ "endLine": 313,
+ "parentCellId": "e2d2bbf6-dffd-4edf-8c5d-34088d9d4745",
+ "parentPath": "client/src/Pages/v1/StatusPage/Create/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Customizable Public and Private Status Pages",
+ "simStepId": "de7b5d1f-b370-42a4-a410-4178f1e2d3a6"
+ }
+ ]
+ },
+ "aa5d6d07-05f0-45f3-b19a-199af50e35f1": {
+ "path": "aa5d6d07-05f0-45f3-b19a-199af50e35f1",
+ "cellName": "Create Status Page: Client Hook Sends API Request - useCreateStatusPage.jsx:L8-11",
+ "cellId": "aa5d6d07-05f0-45f3-b19a-199af50e35f1",
+ "visible": true,
+ "parentCellId": "3945e05f-2d99-4d6d-9a3e-b6c75ea3c45a"
+ },
+ "client/src/Pages/v1/StatusPage/Create/Hooks/useCreateStatusPage.jsx-simstep-3c99ae6a-b671-4f47-bed0-ddccd8ff5d35": {
+ "path": "client/src/Pages/v1/StatusPage/Create/Hooks/useCreateStatusPage.jsx-simstep-3c99ae6a-b671-4f47-bed0-ddccd8ff5d35",
+ "fileName": "useCreateStatusPage.jsx",
+ "wiki": "The `useCreateStatusPage` hook receives the form data and uses the `networkService` to send a request to the backend API to create or update the status page.",
+ "cellName": "Create Status Page: Client Hook Sends API Request - useCreateStatusPage.jsx:L8-11",
+ "cellId": "aa5d6d07-05f0-45f3-b19a-199af50e35f1",
+ "visible": true,
+ "startLine": 8,
+ "endLine": 11,
+ "parentCellId": "3945e05f-2d99-4d6d-9a3e-b6c75ea3c45a",
+ "parentPath": "client/src/Pages/v1/StatusPage/Create/Hooks/useCreateStatusPage.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Customizable Public and Private Status Pages",
+ "simStepId": "3c99ae6a-b671-4f47-bed0-ddccd8ff5d35"
+ }
+ ]
+ },
+ "3efddcad-588f-4492-9808-b0c81c823c96": {
+ "path": "3efddcad-588f-4492-9808-b0c81c823c96",
+ "cellName": "Create Status Page: Backend Route Handling - statusPageRoute.js:L14",
+ "cellId": "3efddcad-588f-4492-9808-b0c81c823c96",
+ "visible": true,
+ "parentCellId": "773e1065-b0a5-46a9-b0cc-6b963f69f5cc"
+ },
+ "server/src/routes/v1/statusPageRoute.js-simstep-84f11567-b3c4-42a2-aa7f-d78c1412099a": {
+ "path": "server/src/routes/v1/statusPageRoute.js-simstep-84f11567-b3c4-42a2-aa7f-d78c1412099a",
+ "fileName": "statusPageRoute.js",
+ "wiki": "The backend router receives the POST request on `/api/v1/status-page`. It uses `multer` to handle the file upload and forwards the request to the `createStatusPage` method in the `StatusPageController`.",
+ "cellName": "Create Status Page: Backend Route Handling - statusPageRoute.js:L14",
+ "cellId": "3efddcad-588f-4492-9808-b0c81c823c96",
+ "visible": true,
+ "startLine": 14,
+ "endLine": 14,
+ "parentCellId": "773e1065-b0a5-46a9-b0cc-6b963f69f5cc",
+ "parentPath": "server/src/routes/v1/statusPageRoute.js",
+ "simSteps": [
+ {
+ "simulationKey": "Customizable Public and Private Status Pages",
+ "simStepId": "84f11567-b3c4-42a2-aa7f-d78c1412099a"
+ }
+ ]
+ },
+ "f15ef77e-380f-4768-98cb-7f0dd4113841": {
+ "path": "f15ef77e-380f-4768-98cb-7f0dd4113841",
+ "cellName": "Create Status Page: Controller Logic - statusPageController.js:L18-24",
+ "cellId": "f15ef77e-380f-4768-98cb-7f0dd4113841",
+ "visible": true,
+ "parentCellId": "17a4f435-5a8e-4433-84a6-9ac17362709a"
+ },
+ "server/src/controllers/v1/statusPageController.js-simstep-01694730-8a75-4949-8815-bb4d6e3e2b42": {
+ "path": "server/src/controllers/v1/statusPageController.js-simstep-01694730-8a75-4949-8815-bb4d6e3e2b42",
+ "fileName": "statusPageController.js",
+ "wiki": "The `StatusPageController` validates the incoming request body and file. It then calls the `statusPageModule` to persist the new status page in the database.",
+ "cellName": "Create Status Page: Controller Logic - statusPageController.js:L18-24",
+ "cellId": "f15ef77e-380f-4768-98cb-7f0dd4113841",
+ "visible": true,
+ "startLine": 18,
+ "endLine": 24,
+ "parentCellId": "17a4f435-5a8e-4433-84a6-9ac17362709a",
+ "parentPath": "server/src/controllers/v1/statusPageController.js",
+ "simSteps": [
+ {
+ "simulationKey": "Customizable Public and Private Status Pages",
+ "simStepId": "01694730-8a75-4949-8815-bb4d6e3e2b42"
+ }
+ ]
+ },
+ "e234917e-a950-48f5-b864-cd957b2216e1": {
+ "path": "e234917e-a950-48f5-b864-cd957b2216e1",
+ "cellName": "Create Status Page: Database Record Creation - statusPageModule.js:L14-34",
+ "cellId": "e234917e-a950-48f5-b864-cd957b2216e1",
+ "visible": true,
+ "parentCellId": "f8541fe3-4bcf-4833-a226-66b5301adf58"
+ },
+ "server/src/db/v1/modules/statusPageModule.js-simstep-b6fa93cd-6fca-4d9e-969a-5400e2437ee8": {
+ "path": "server/src/db/v1/modules/statusPageModule.js-simstep-b6fa93cd-6fca-4d9e-969a-5400e2437ee8",
+ "fileName": "statusPageModule.js",
+ "wiki": "The `StatusPageModule` creates a new instance of the `StatusPage` Mongoose model, populates it with the provided data including the image buffer, and saves it to the MongoDB database. It also handles duplicate URL errors.",
+ "cellName": "Create Status Page: Database Record Creation - statusPageModule.js:L14-34",
+ "cellId": "e234917e-a950-48f5-b864-cd957b2216e1",
+ "visible": true,
+ "startLine": 14,
+ "endLine": 34,
+ "parentCellId": "f8541fe3-4bcf-4833-a226-66b5301adf58",
+ "parentPath": "server/src/db/v1/modules/statusPageModule.js",
+ "simSteps": [
+ {
+ "simulationKey": "Customizable Public and Private Status Pages",
+ "simStepId": "b6fa93cd-6fca-4d9e-969a-5400e2437ee8"
+ }
+ ]
+ },
+ "058ded1f-83ce-40a7-a99d-d178c81fe557": {
+ "path": "058ded1f-83ce-40a7-a99d-d178c81fe557",
+ "cellName": "Create Status Page: Controller Sends Success Response - statusPageController.js:L25-28",
+ "cellId": "058ded1f-83ce-40a7-a99d-d178c81fe557",
+ "visible": true,
+ "parentCellId": "17a4f435-5a8e-4433-84a6-9ac17362709a"
+ },
+ "server/src/controllers/v1/statusPageController.js-simstep-2a115421-5175-4c8f-8cdd-62f3fe148af5": {
+ "path": "server/src/controllers/v1/statusPageController.js-simstep-2a115421-5175-4c8f-8cdd-62f3fe148af5",
+ "fileName": "statusPageController.js",
+ "wiki": "The controller receives the newly created status page object from the database module and sends a successful JSON response back to the client, including a success message and the new data.",
+ "cellName": "Create Status Page: Controller Sends Success Response - statusPageController.js:L25-28",
+ "cellId": "058ded1f-83ce-40a7-a99d-d178c81fe557",
+ "visible": true,
+ "startLine": 25,
+ "endLine": 28,
+ "parentCellId": "17a4f435-5a8e-4433-84a6-9ac17362709a",
+ "parentPath": "server/src/controllers/v1/statusPageController.js",
+ "simSteps": [
+ {
+ "simulationKey": "Customizable Public and Private Status Pages",
+ "simStepId": "2a115421-5175-4c8f-8cdd-62f3fe148af5"
+ }
+ ]
+ },
+ "30aaee38-f509-4e19-a917-e6d2f4bba61c": {
+ "path": "30aaee38-f509-4e19-a917-e6d2f4bba61c",
+ "cellName": "View Status Page: User Navigates to URL - index.jsx:L148-152",
+ "cellId": "30aaee38-f509-4e19-a917-e6d2f4bba61c",
+ "visible": true,
+ "parentCellId": "c4d7e7dd-75b1-44f1-a34a-6ed290a8e562"
+ },
+ "client/src/Routes/index.jsx-simstep-132354d7-df7a-4e03-9d00-16d54f836a1b": {
+ "path": "client/src/Routes/index.jsx-simstep-132354d7-df7a-4e03-9d00-16d54f836a1b",
+ "fileName": "index.jsx",
+ "wiki": "A user accesses a status page URL (e.g., /status/uptime/my-awesome-service). The React router matches the path and renders the `Status` component, which is responsible for fetching and displaying the page details.",
+ "cellName": "View Status Page: User Navigates to URL - index.jsx:L148-152",
+ "cellId": "30aaee38-f509-4e19-a917-e6d2f4bba61c",
+ "visible": true,
+ "startLine": 148,
+ "endLine": 152,
+ "parentCellId": "c4d7e7dd-75b1-44f1-a34a-6ed290a8e562",
+ "parentPath": "client/src/Routes/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Customizable Public and Private Status Pages",
+ "simStepId": "132354d7-df7a-4e03-9d00-16d54f836a1b"
+ }
+ ]
+ },
+ "ecfb2d04-2681-4f16-af75-632f69e1a868": {
+ "path": "ecfb2d04-2681-4f16-af75-632f69e1a868",
+ "cellName": "View Status Page: Client Hook Sends API Request - useStatusPageFetch.jsx:L15-18",
+ "cellId": "ecfb2d04-2681-4f16-af75-632f69e1a868",
+ "visible": true,
+ "parentCellId": "5b2cdab8-66f1-40a1-9621-0efaf54e04ae"
+ },
+ "client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageFetch.jsx-simstep-b57bb249-9066-454e-aa8c-89108fdfcc2e": {
+ "path": "client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageFetch.jsx-simstep-b57bb249-9066-454e-aa8c-89108fdfcc2e",
+ "fileName": "useStatusPageFetch.jsx",
+ "wiki": "The `useStatusPageFetch` hook uses the `networkService` to send a GET request to the backend API to retrieve the status page data based on its URL.",
+ "cellName": "View Status Page: Client Hook Sends API Request - useStatusPageFetch.jsx:L15-18",
+ "cellId": "ecfb2d04-2681-4f16-af75-632f69e1a868",
+ "visible": true,
+ "startLine": 15,
+ "endLine": 18,
+ "parentCellId": "5b2cdab8-66f1-40a1-9621-0efaf54e04ae",
+ "parentPath": "client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageFetch.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Customizable Public and Private Status Pages",
+ "simStepId": "b57bb249-9066-454e-aa8c-89108fdfcc2e"
+ }
+ ]
+ },
+ "2e26cd2f-1413-4c16-8e63-ea7808ab6c52": {
+ "path": "2e26cd2f-1413-4c16-8e63-ea7808ab6c52",
+ "cellName": "View Status Page: Backend Route Handling - statusPageRoute.js:L16",
+ "cellId": "2e26cd2f-1413-4c16-8e63-ea7808ab6c52",
+ "visible": true,
+ "parentCellId": "773e1065-b0a5-46a9-b0cc-6b963f69f5cc"
+ },
+ "server/src/routes/v1/statusPageRoute.js-simstep-477c6821-b29b-4b38-8930-ced21b8a75e5": {
+ "path": "server/src/routes/v1/statusPageRoute.js-simstep-477c6821-b29b-4b38-8930-ced21b8a75e5",
+ "fileName": "statusPageRoute.js",
+ "wiki": "The backend router receives the GET request on `/api/v1/status-page/:url` and forwards it to the `getStatusPageByUrl` method in the `StatusPageController`.",
+ "cellName": "View Status Page: Backend Route Handling - statusPageRoute.js:L16",
+ "cellId": "2e26cd2f-1413-4c16-8e63-ea7808ab6c52",
+ "visible": true,
+ "startLine": 16,
+ "endLine": 16,
+ "parentCellId": "773e1065-b0a5-46a9-b0cc-6b963f69f5cc",
+ "parentPath": "server/src/routes/v1/statusPageRoute.js",
+ "simSteps": [
+ {
+ "simulationKey": "Customizable Public and Private Status Pages",
+ "simStepId": "477c6821-b29b-4b38-8930-ced21b8a75e5"
+ }
+ ]
+ },
+ "9f217674-4cd7-4ef8-b088-d5620042a7ba": {
+ "path": "9f217674-4cd7-4ef8-b088-d5620042a7ba",
+ "cellName": "View Status Page: Controller Logic - statusPageController.js:L75-78",
+ "cellId": "9f217674-4cd7-4ef8-b088-d5620042a7ba",
+ "visible": true,
+ "parentCellId": "17a4f435-5a8e-4433-84a6-9ac17362709a"
+ },
+ "server/src/controllers/v1/statusPageController.js-simstep-5bdbd179-ce8f-4171-a073-af72a99b4468": {
+ "path": "server/src/controllers/v1/statusPageController.js-simstep-5bdbd179-ce8f-4171-a073-af72a99b4468",
+ "fileName": "statusPageController.js",
+ "wiki": "The `StatusPageController` validates the request parameters and calls the `statusPageModule` to retrieve the status page data from the database.",
+ "cellName": "View Status Page: Controller Logic - statusPageController.js:L75-78",
+ "cellId": "9f217674-4cd7-4ef8-b088-d5620042a7ba",
+ "visible": true,
+ "startLine": 75,
+ "endLine": 78,
+ "parentCellId": "17a4f435-5a8e-4433-84a6-9ac17362709a",
+ "parentPath": "server/src/controllers/v1/statusPageController.js",
+ "simSteps": [
+ {
+ "simulationKey": "Customizable Public and Private Status Pages",
+ "simStepId": "5bdbd179-ce8f-4171-a073-af72a99b4468"
+ }
+ ]
+ },
+ "26da60f9-dde4-44ec-be89-91858cc14f90": {
+ "path": "26da60f9-dde4-44ec-be89-91858cc14f90",
+ "cellName": "View Status Page: Database Record Retrieval - statusPageModule.js:L89-299",
+ "cellId": "26da60f9-dde4-44ec-be89-91858cc14f90",
+ "visible": true,
+ "parentCellId": "f8541fe3-4bcf-4833-a226-66b5301adf58"
+ },
+ "server/src/db/v1/modules/statusPageModule.js-simstep-ceb044e9-6545-4d04-bf79-ec8146eb8ec9": {
+ "path": "server/src/db/v1/modules/statusPageModule.js-simstep-ceb044e9-6545-4d04-bf79-ec8146eb8ec9",
+ "fileName": "statusPageModule.js",
+ "wiki": "The `StatusPageModule` executes a complex MongoDB aggregation pipeline to find the status page by its URL, and then look up and join data for all associated monitors and their recent checks.",
+ "cellName": "View Status Page: Database Record Retrieval - statusPageModule.js:L89-299",
+ "cellId": "26da60f9-dde4-44ec-be89-91858cc14f90",
+ "visible": true,
+ "startLine": 89,
+ "endLine": 299,
+ "parentCellId": "f8541fe3-4bcf-4833-a226-66b5301adf58",
+ "parentPath": "server/src/db/v1/modules/statusPageModule.js",
+ "simSteps": [
+ {
+ "simulationKey": "Customizable Public and Private Status Pages",
+ "simStepId": "ceb044e9-6545-4d04-bf79-ec8146eb8ec9"
+ }
+ ]
+ },
+ "d76e70e0-41f9-46cc-b775-b2027c5a0d5b": {
+ "path": "d76e70e0-41f9-46cc-b775-b2027c5a0d5b",
+ "cellName": "View Status Page: Controller Sends Success Response - statusPageController.js:L79-82",
+ "cellId": "d76e70e0-41f9-46cc-b775-b2027c5a0d5b",
+ "visible": true,
+ "parentCellId": "17a4f435-5a8e-4433-84a6-9ac17362709a"
+ },
+ "server/src/controllers/v1/statusPageController.js-simstep-83224406-2f1d-4030-9beb-d65c2baf544d": {
+ "path": "server/src/controllers/v1/statusPageController.js-simstep-83224406-2f1d-4030-9beb-d65c2baf544d",
+ "fileName": "statusPageController.js",
+ "wiki": "The controller receives the status page data and sends a successful JSON response back to the client.",
+ "cellName": "View Status Page: Controller Sends Success Response - statusPageController.js:L79-82",
+ "cellId": "d76e70e0-41f9-46cc-b775-b2027c5a0d5b",
+ "visible": true,
+ "startLine": 79,
+ "endLine": 82,
+ "parentCellId": "17a4f435-5a8e-4433-84a6-9ac17362709a",
+ "parentPath": "server/src/controllers/v1/statusPageController.js",
+ "simSteps": [
+ {
+ "simulationKey": "Customizable Public and Private Status Pages",
+ "simStepId": "83224406-2f1d-4030-9beb-d65c2baf544d"
+ }
+ ]
+ },
+ "f59b91de-5196-4a43-9d57-3f28402c0672": {
+ "path": "f59b91de-5196-4a43-9d57-3f28402c0672",
+ "cellName": "View Status Page: Component Renders UI - index.jsx:L145-163",
+ "cellId": "f59b91de-5196-4a43-9d57-3f28402c0672",
+ "visible": true,
+ "parentCellId": "e1e6f5e0-fd8c-4260-aa4f-cdb096c6b1c3"
+ },
+ "client/src/Pages/v1/StatusPage/Status/index.jsx-simstep-3d51e811-a659-442e-97e0-00798cd15647": {
+ "path": "client/src/Pages/v1/StatusPage/Status/index.jsx-simstep-3d51e811-a659-442e-97e0-00798cd15647",
+ "fileName": "index.jsx",
+ "wiki": "The `Status` component re-renders with the fetched data. It displays the company name, logo, overall status bar, and a list of monitors with their individual status and historical uptime charts.",
+ "cellName": "View Status Page: Component Renders UI - index.jsx:L145-163",
+ "cellId": "f59b91de-5196-4a43-9d57-3f28402c0672",
+ "visible": true,
+ "startLine": 145,
+ "endLine": 163,
+ "parentCellId": "e1e6f5e0-fd8c-4260-aa4f-cdb096c6b1c3",
+ "parentPath": "client/src/Pages/v1/StatusPage/Status/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Customizable Public and Private Status Pages",
+ "simStepId": "3d51e811-a659-442e-97e0-00798cd15647"
+ }
+ ]
+ },
+ "de23eee2-b60a-4236-a890-3c5b78388bb3": {
+ "path": "de23eee2-b60a-4236-a890-3c5b78388bb3",
+ "cellName": "Create Status\nPage: Form\nSubmission",
+ "cellId": "de23eee2-b60a-4236-a890-3c5b78388bb3",
+ "visible": true,
+ "parentCellId": "08602a28-f073-484e-9403-7eb798126dd4"
+ },
+ "generated-edge-simstep-d419f7b6-7b9b-4494-9fad-83e3ce208ca4-de23eee2-b60a-4236-a890-3c5b78388bb3": {
+ "path": "generated-edge-simstep-d419f7b6-7b9b-4494-9fad-83e3ce208ca4-de23eee2-b60a-4236-a890-3c5b78388bb3",
+ "fileName": "index.jsx",
+ "cellName": "Create Status Page: Form Submission",
+ "cellId": "de23eee2-b60a-4236-a890-3c5b78388bb3",
+ "visible": true,
+ "startLine": 143,
+ "endLine": 159,
+ "parentPath": "client/src/Pages/v1/StatusPage/Create/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Customizable Public and Private Status Pages",
+ "simStepId": "d419f7b6-7b9b-4494-9fad-83e3ce208ca4"
+ }
+ ]
+ },
+ "d7faa713-4204-4a97-a172-d621da36c287": {
+ "path": "d7faa713-4204-4a97-a172-d621da36c287",
+ "cellName": "Create Status\nPage: API\nCall to\nBackend",
+ "cellId": "d7faa713-4204-4a97-a172-d621da36c287",
+ "visible": true
+ },
+ "generated-edge-simstep-8a2ab372-73db-4b9f-bc0e-6a20f15a4d79-d7faa713-4204-4a97-a172-d621da36c287": {
+ "path": "generated-edge-simstep-8a2ab372-73db-4b9f-bc0e-6a20f15a4d79-d7faa713-4204-4a97-a172-d621da36c287",
+ "fileName": "useCreateStatusPage.jsx",
+ "cellName": "Create Status Page: API Call to Backend",
+ "cellId": "d7faa713-4204-4a97-a172-d621da36c287",
+ "visible": true,
+ "startLine": 912,
+ "endLine": 944,
+ "parentPath": "client/src/Pages/v1/StatusPage/Create/Hooks/useCreateStatusPage.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Customizable Public and Private Status Pages",
+ "simStepId": "8a2ab372-73db-4b9f-bc0e-6a20f15a4d79"
+ }
+ ]
+ },
+ "a3f28fa6-8268-4c13-95a0-83e3b1abdcb0": {
+ "path": "a3f28fa6-8268-4c13-95a0-83e3b1abdcb0",
+ "cellName": "Create Status\nPage: Data\nPassed to\nController",
+ "cellId": "a3f28fa6-8268-4c13-95a0-83e3b1abdcb0",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "generated-edge-simstep-0923a533-c572-4da3-ad87-cdfff111f3bb-a3f28fa6-8268-4c13-95a0-83e3b1abdcb0": {
+ "path": "generated-edge-simstep-0923a533-c572-4da3-ad87-cdfff111f3bb-a3f28fa6-8268-4c13-95a0-83e3b1abdcb0",
+ "fileName": "statusPageRoute.js",
+ "cellName": "Create Status Page: Data Passed to Controller",
+ "cellId": "a3f28fa6-8268-4c13-95a0-83e3b1abdcb0",
+ "visible": true,
+ "startLine": 14,
+ "endLine": 14,
+ "parentPath": "server/src/routes/v1/statusPageRoute.js",
+ "simSteps": [
+ {
+ "simulationKey": "Customizable Public and Private Status Pages",
+ "simStepId": "0923a533-c572-4da3-ad87-cdfff111f3bb"
+ }
+ ]
+ },
+ "9735611c-924b-49aa-92e8-10d8a86ac288": {
+ "path": "9735611c-924b-49aa-92e8-10d8a86ac288",
+ "cellName": "Create Status\nPage: Data\nSent to\nDB Module",
+ "cellId": "9735611c-924b-49aa-92e8-10d8a86ac288",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "generated-edge-simstep-67603f03-c088-4ed0-9f18-072435134cdd-9735611c-924b-49aa-92e8-10d8a86ac288": {
+ "path": "generated-edge-simstep-67603f03-c088-4ed0-9f18-072435134cdd-9735611c-924b-49aa-92e8-10d8a86ac288",
+ "fileName": "statusPageController.js",
+ "cellName": "Create Status Page: Data Sent to DB Module",
+ "cellId": "9735611c-924b-49aa-92e8-10d8a86ac288",
+ "visible": true,
+ "startLine": 21,
+ "endLine": 21,
+ "parentPath": "server/src/controllers/v1/statusPageController.js",
+ "simSteps": [
+ {
+ "simulationKey": "Customizable Public and Private Status Pages",
+ "simStepId": "67603f03-c088-4ed0-9f18-072435134cdd"
+ }
+ ]
+ },
+ "40cb7d7b-2888-44de-bb7d-7735af70ba34": {
+ "path": "40cb7d7b-2888-44de-bb7d-7735af70ba34",
+ "cellName": "Create Status\nPage: Database\nResult Returned",
+ "cellId": "40cb7d7b-2888-44de-bb7d-7735af70ba34",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "generated-edge-simstep-ea4b3532-baae-4890-a73e-2ca385d0319e-40cb7d7b-2888-44de-bb7d-7735af70ba34": {
+ "path": "generated-edge-simstep-ea4b3532-baae-4890-a73e-2ca385d0319e-40cb7d7b-2888-44de-bb7d-7735af70ba34",
+ "fileName": "statusPageModule.js",
+ "cellName": "Create Status Page: Database Result Returned",
+ "cellId": "40cb7d7b-2888-44de-bb7d-7735af70ba34",
+ "visible": true,
+ "startLine": 27,
+ "endLine": 27,
+ "parentPath": "server/src/db/v1/modules/statusPageModule.js",
+ "simSteps": [
+ {
+ "simulationKey": "Customizable Public and Private Status Pages",
+ "simStepId": "ea4b3532-baae-4890-a73e-2ca385d0319e"
+ }
+ ]
+ },
+ "c3f83811-8674-4dbb-9f84-79499f97fb1f": {
+ "path": "c3f83811-8674-4dbb-9f84-79499f97fb1f",
+ "cellName": "Create Status\nPage: UI\nReceives Response\nand Redirects",
+ "cellId": "c3f83811-8674-4dbb-9f84-79499f97fb1f",
+ "visible": true
+ },
+ "generated-edge-simstep-210acc07-81c3-4394-b566-41f6031e8eeb-c3f83811-8674-4dbb-9f84-79499f97fb1f": {
+ "path": "generated-edge-simstep-210acc07-81c3-4394-b566-41f6031e8eeb-c3f83811-8674-4dbb-9f84-79499f97fb1f",
+ "fileName": "statusPageController.js",
+ "cellName": "Create Status Page: UI Receives Response and Redirects",
+ "cellId": "c3f83811-8674-4dbb-9f84-79499f97fb1f",
+ "visible": true,
+ "startLine": 151,
+ "endLine": 156,
+ "parentPath": "server/src/controllers/v1/statusPageController.js",
+ "simSteps": [
+ {
+ "simulationKey": "Customizable Public and Private Status Pages",
+ "simStepId": "210acc07-81c3-4394-b566-41f6031e8eeb"
+ }
+ ]
+ },
+ "0575bf87-5362-4988-b895-7e514148d420": {
+ "path": "0575bf87-5362-4988-b895-7e514148d420",
+ "cellName": "View Status\nPage: Component\nTriggers Data\nFetch",
+ "cellId": "0575bf87-5362-4988-b895-7e514148d420",
+ "visible": true,
+ "parentCellId": "d3264577-10f0-42fe-9411-7e9df0754d1e"
+ },
+ "generated-edge-simstep-2ba49e28-1cac-47f5-9118-ab930788736a-0575bf87-5362-4988-b895-7e514148d420": {
+ "path": "generated-edge-simstep-2ba49e28-1cac-47f5-9118-ab930788736a-0575bf87-5362-4988-b895-7e514148d420",
+ "fileName": "index.jsx",
+ "cellName": "View Status Page: Component Triggers Data Fetch",
+ "cellId": "0575bf87-5362-4988-b895-7e514148d420",
+ "visible": true,
+ "startLine": 29,
+ "endLine": 29,
+ "parentPath": "client/src/Routes/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Customizable Public and Private Status Pages",
+ "simStepId": "2ba49e28-1cac-47f5-9118-ab930788736a"
+ }
+ ]
+ },
+ "ae1c9341-0e39-4d91-a9ac-5cc8a627c247": {
+ "path": "ae1c9341-0e39-4d91-a9ac-5cc8a627c247",
+ "cellName": "View Status\nPage: API\nCall to\nBackend",
+ "cellId": "ae1c9341-0e39-4d91-a9ac-5cc8a627c247",
+ "visible": true
+ },
+ "generated-edge-simstep-de0714d2-bf1e-4a1a-a389-60793de8b772-ae1c9341-0e39-4d91-a9ac-5cc8a627c247": {
+ "path": "generated-edge-simstep-de0714d2-bf1e-4a1a-a389-60793de8b772-ae1c9341-0e39-4d91-a9ac-5cc8a627c247",
+ "fileName": "useStatusPageFetch.jsx",
+ "cellName": "View Status Page: API Call to Backend",
+ "cellId": "ae1c9341-0e39-4d91-a9ac-5cc8a627c247",
+ "visible": true,
+ "startLine": 896,
+ "endLine": 902,
+ "parentPath": "client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageFetch.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Customizable Public and Private Status Pages",
+ "simStepId": "de0714d2-bf1e-4a1a-a389-60793de8b772"
+ }
+ ]
+ },
+ "5735f30c-b55f-444a-9236-f3d75f68ed27": {
+ "path": "5735f30c-b55f-444a-9236-f3d75f68ed27",
+ "cellName": "View Status\nPage: Data\nPassed to\nController",
+ "cellId": "5735f30c-b55f-444a-9236-f3d75f68ed27",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "generated-edge-simstep-b79b8922-4de6-477d-8648-37b4bd9a6767-5735f30c-b55f-444a-9236-f3d75f68ed27": {
+ "path": "generated-edge-simstep-b79b8922-4de6-477d-8648-37b4bd9a6767-5735f30c-b55f-444a-9236-f3d75f68ed27",
+ "fileName": "statusPageRoute.js",
+ "cellName": "View Status Page: Data Passed to Controller",
+ "cellId": "5735f30c-b55f-444a-9236-f3d75f68ed27",
+ "visible": true,
+ "startLine": 16,
+ "endLine": 16,
+ "parentPath": "server/src/routes/v1/statusPageRoute.js",
+ "simSteps": [
+ {
+ "simulationKey": "Customizable Public and Private Status Pages",
+ "simStepId": "b79b8922-4de6-477d-8648-37b4bd9a6767"
+ }
+ ]
+ },
+ "dace45d6-fb25-4dae-b940-49ef7d77bbed": {
+ "path": "dace45d6-fb25-4dae-b940-49ef7d77bbed",
+ "cellName": "View Status\nPage: URL\nSent to\nDB Module",
+ "cellId": "dace45d6-fb25-4dae-b940-49ef7d77bbed",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "generated-edge-simstep-788307ef-4cfa-4f5a-b2e9-a0bfc7a378d8-dace45d6-fb25-4dae-b940-49ef7d77bbed": {
+ "path": "generated-edge-simstep-788307ef-4cfa-4f5a-b2e9-a0bfc7a378d8-dace45d6-fb25-4dae-b940-49ef7d77bbed",
+ "fileName": "statusPageController.js",
+ "cellName": "View Status Page: URL Sent to DB Module",
+ "cellId": "dace45d6-fb25-4dae-b940-49ef7d77bbed",
+ "visible": true,
+ "startLine": 78,
+ "endLine": 78,
+ "parentPath": "server/src/controllers/v1/statusPageController.js",
+ "simSteps": [
+ {
+ "simulationKey": "Customizable Public and Private Status Pages",
+ "simStepId": "788307ef-4cfa-4f5a-b2e9-a0bfc7a378d8"
+ }
+ ]
+ },
+ "137a5ab7-1e49-42ec-869d-58f7f2380cb3": {
+ "path": "137a5ab7-1e49-42ec-869d-58f7f2380cb3",
+ "cellName": "View Status\nPage: Database\nResult Returned",
+ "cellId": "137a5ab7-1e49-42ec-869d-58f7f2380cb3",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "generated-edge-simstep-4d3fad92-8fa9-4a8f-be3b-f09eda2f8dd4-137a5ab7-1e49-42ec-869d-58f7f2380cb3": {
+ "path": "generated-edge-simstep-4d3fad92-8fa9-4a8f-be3b-f09eda2f8dd4-137a5ab7-1e49-42ec-869d-58f7f2380cb3",
+ "fileName": "statusPageModule.js",
+ "cellName": "View Status Page: Database Result Returned",
+ "cellId": "137a5ab7-1e49-42ec-869d-58f7f2380cb3",
+ "visible": true,
+ "startLine": 291,
+ "endLine": 291,
+ "parentPath": "server/src/db/v1/modules/statusPageModule.js",
+ "simSteps": [
+ {
+ "simulationKey": "Customizable Public and Private Status Pages",
+ "simStepId": "4d3fad92-8fa9-4a8f-be3b-f09eda2f8dd4"
+ }
+ ]
+ },
+ "04a33e4f-1dfe-4c5a-8b30-01ed0591c380": {
+ "path": "04a33e4f-1dfe-4c5a-8b30-01ed0591c380",
+ "cellName": "View Status\nPage: UI\nReceives Data",
+ "cellId": "04a33e4f-1dfe-4c5a-8b30-01ed0591c380",
+ "visible": true
+ },
+ "generated-edge-simstep-d5f3c300-9777-4e09-bb05-f1281c2ff29a-04a33e4f-1dfe-4c5a-8b30-01ed0591c380": {
+ "path": "generated-edge-simstep-d5f3c300-9777-4e09-bb05-f1281c2ff29a-04a33e4f-1dfe-4c5a-8b30-01ed0591c380",
+ "fileName": "statusPageController.js",
+ "cellName": "View Status Page: UI Receives Data",
+ "cellId": "04a33e4f-1dfe-4c5a-8b30-01ed0591c380",
+ "visible": true,
+ "startLine": 20,
+ "endLine": 26,
+ "parentPath": "server/src/controllers/v1/statusPageController.js",
+ "simSteps": [
+ {
+ "simulationKey": "Customizable Public and Private Status Pages",
+ "simStepId": "d5f3c300-9777-4e09-bb05-f1281c2ff29a"
+ }
+ ]
+ },
+ "933a6cdc-349c-4725-b7ae-14cf780bfde8": {
+ "path": "933a6cdc-349c-4725-b7ae-14cf780bfde8",
+ "cellName": "checkHooks.js",
+ "cellId": "933a6cdc-349c-4725-b7ae-14cf780bfde8",
+ "visible": true,
+ "parentCellId": "3bf0c0f9-cbb0-4f0c-8cb3-6e2a4abc0961"
+ },
+ "d18dbe1d-2f2c-4336-bd21-47c1670f7bab": {
+ "path": "d18dbe1d-2f2c-4336-bd21-47c1670f7bab",
+ "cellName": "checkRoute.js",
+ "cellId": "d18dbe1d-2f2c-4336-bd21-47c1670f7bab",
+ "visible": true,
+ "parentCellId": "d3375a20-2776-4771-b806-649bbeb541bd"
+ },
+ "d983e54f-2fbe-496a-932d-a827e1124804": {
+ "path": "d983e54f-2fbe-496a-932d-a827e1124804",
+ "cellName": "checkController.js",
+ "cellId": "d983e54f-2fbe-496a-932d-a827e1124804",
+ "visible": true,
+ "parentCellId": "fc591f7a-d4ac-4565-949d-a87a58a79bbc"
+ },
+ "273f3009-69d0-46fe-af57-3cd94a24b55b": {
+ "path": "273f3009-69d0-46fe-af57-3cd94a24b55b",
+ "cellName": "Details",
+ "cellId": "273f3009-69d0-46fe-af57-3cd94a24b55b",
+ "visible": true,
+ "parentCellId": "7090fcdb-09be-4c85-86fd-e73553bbb2ff"
+ },
+ "f90db867-e1e7-4554-b446-fa78fc6a8578": {
+ "path": "f90db867-e1e7-4554-b446-fa78fc6a8578",
+ "cellName": "checkService.js",
+ "cellId": "f90db867-e1e7-4554-b446-fa78fc6a8578",
+ "visible": true,
+ "parentCellId": "87d3b101-9443-4ed4-9c4f-f759d41677eb"
+ },
+ "860b5eec-a1ce-4e1f-a316-e1586f03af96": {
+ "path": "860b5eec-a1ce-4e1f-a316-e1586f03af96",
+ "cellName": "checkModule.js",
+ "cellId": "860b5eec-a1ce-4e1f-a316-e1586f03af96",
+ "visible": true,
+ "parentCellId": "82cb3dbe-c922-438a-a912-255376f4b380"
+ },
+ "1807d5b9-54be-430b-8997-cada4dd9b521": {
+ "path": "1807d5b9-54be-430b-8997-cada4dd9b521",
+ "cellName": "index.jsx",
+ "cellId": "1807d5b9-54be-430b-8997-cada4dd9b521",
+ "visible": true,
+ "parentCellId": "273f3009-69d0-46fe-af57-3cd94a24b55b"
+ },
+ "6aed78bb-62f1-4ac1-9b9a-9d5977e87404": {
+ "path": "6aed78bb-62f1-4ac1-9b9a-9d5977e87404",
+ "cellName": "Components",
+ "cellId": "6aed78bb-62f1-4ac1-9b9a-9d5977e87404",
+ "visible": true,
+ "parentCellId": "273f3009-69d0-46fe-af57-3cd94a24b55b"
+ },
+ "5ac0878f-d238-4763-8464-a85b4885a40f": {
+ "path": "5ac0878f-d238-4763-8464-a85b4885a40f",
+ "cellName": "ResponseTable",
+ "cellId": "5ac0878f-d238-4763-8464-a85b4885a40f",
+ "visible": true,
+ "parentCellId": "6aed78bb-62f1-4ac1-9b9a-9d5977e87404"
+ },
+ "d3f74ac7-3b9a-4cae-8d77-a0d5d605bb27": {
+ "path": "d3f74ac7-3b9a-4cae-8d77-a0d5d605bb27",
+ "cellName": "index.jsx",
+ "cellId": "d3f74ac7-3b9a-4cae-8d77-a0d5d605bb27",
+ "visible": true,
+ "parentCellId": "5ac0878f-d238-4763-8464-a85b4885a40f"
+ },
+ "78a8a7c5-41d3-4453-a7f7-ac632bbef288": {
+ "path": "78a8a7c5-41d3-4453-a7f7-ac632bbef288",
+ "cellName": "Navigate to Monitor Details - index.jsx:L27-188",
+ "cellId": "78a8a7c5-41d3-4453-a7f7-ac632bbef288",
+ "visible": true,
+ "parentCellId": "1807d5b9-54be-430b-8997-cada4dd9b521"
+ },
+ "client/src/Pages/v1/Uptime/Details/index.jsx-simstep-35195328-9847-4678-b014-9331553f0d19": {
+ "path": "client/src/Pages/v1/Uptime/Details/index.jsx-simstep-35195328-9847-4678-b014-9331553f0d19",
+ "fileName": "index.jsx",
+ "wiki": "The user navigates to the details page for a specific uptime monitor. The `UptimeDetails` component mounts and initializes its state and hooks.",
+ "cellName": "Navigate to Monitor Details - index.jsx:L27-188",
+ "cellId": "78a8a7c5-41d3-4453-a7f7-ac632bbef288",
+ "visible": true,
+ "startLine": 27,
+ "endLine": 188,
+ "parentCellId": "1807d5b9-54be-430b-8997-cada4dd9b521",
+ "parentPath": "client/src/Pages/v1/Uptime/Details/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Incident History, Analysis, and Management",
+ "simStepId": "35195328-9847-4678-b014-9331553f0d19"
+ }
+ ]
+ },
+ "4a5847d0-91a0-4b4c-8517-2f4ff9a7e8d4": {
+ "path": "4a5847d0-91a0-4b4c-8517-2f4ff9a7e8d4",
+ "cellName": "Fetch Monitor Checks Hook - checkHooks.js:L89-121",
+ "cellId": "4a5847d0-91a0-4b4c-8517-2f4ff9a7e8d4",
+ "visible": true,
+ "parentCellId": "933a6cdc-349c-4725-b7ae-14cf780bfde8"
+ },
+ "client/src/Hooks/v1/checkHooks.js-simstep-8dc4347c-3e9e-40d7-ae6f-54684f2e6956": {
+ "path": "client/src/Hooks/v1/checkHooks.js-simstep-8dc4347c-3e9e-40d7-ae6f-54684f2e6956",
+ "fileName": "checkHooks.js",
+ "wiki": "The `useFetchChecksByMonitor` custom hook executes. It sets the loading state and prepares to call the network service to fetch the data from the backend API.",
+ "cellName": "Fetch Monitor Checks Hook - checkHooks.js:L89-121",
+ "cellId": "4a5847d0-91a0-4b4c-8517-2f4ff9a7e8d4",
+ "visible": true,
+ "startLine": 89,
+ "endLine": 121,
+ "parentCellId": "933a6cdc-349c-4725-b7ae-14cf780bfde8",
+ "parentPath": "client/src/Hooks/v1/checkHooks.js",
+ "simSteps": [
+ {
+ "simulationKey": "Incident History, Analysis, and Management",
+ "simStepId": "8dc4347c-3e9e-40d7-ae6f-54684f2e6956"
+ }
+ ]
+ },
+ "cdd090be-0b62-45b4-baa0-fec01fc3ffd1": {
+ "path": "cdd090be-0b62-45b4-baa0-fec01fc3ffd1",
+ "cellName": "Route Request to Controller - checkRoute.js:L19",
+ "cellId": "cdd090be-0b62-45b4-baa0-fec01fc3ffd1",
+ "visible": true,
+ "parentCellId": "d18dbe1d-2f2c-4336-bd21-47c1670f7bab"
+ },
+ "server/src/routes/v1/checkRoute.js-simstep-c5fd192f-ea28-48a0-b3e7-5bc04f521192": {
+ "path": "server/src/routes/v1/checkRoute.js-simstep-c5fd192f-ea28-48a0-b3e7-5bc04f521192",
+ "fileName": "checkRoute.js",
+ "wiki": "The backend Express router receives the incoming GET request and matches it to the appropriate route, forwarding the request to the `getChecksByMonitor` method in the `CheckController`.",
+ "cellName": "Route Request to Controller - checkRoute.js:L19",
+ "cellId": "cdd090be-0b62-45b4-baa0-fec01fc3ffd1",
+ "visible": true,
+ "startLine": 19,
+ "endLine": 19,
+ "parentCellId": "d18dbe1d-2f2c-4336-bd21-47c1670f7bab",
+ "parentPath": "server/src/routes/v1/checkRoute.js",
+ "simSteps": [
+ {
+ "simulationKey": "Incident History, Analysis, and Management",
+ "simStepId": "c5fd192f-ea28-48a0-b3e7-5bc04f521192"
+ }
+ ]
+ },
+ "499b46a2-f2a2-4ccd-aecd-012eade2bd23": {
+ "path": "499b46a2-f2a2-4ccd-aecd-012eade2bd23",
+ "cellName": "Fetch Checks from Database - checkService.js:L36-45",
+ "cellId": "499b46a2-f2a2-4ccd-aecd-012eade2bd23",
+ "visible": true,
+ "parentCellId": "f90db867-e1e7-4554-b446-fa78fc6a8578"
+ },
+ "server/src/service/v1/business/checkService.js-simstep-83fea05d-8306-4a73-a43f-882cdd14f50f": {
+ "path": "server/src/service/v1/business/checkService.js-simstep-83fea05d-8306-4a73-a43f-882cdd14f50f",
+ "fileName": "checkService.js",
+ "wiki": "The `CheckService` calls the `checkModule` to interact with the database. It passes the necessary parameters to query for the check history.",
+ "cellName": "Fetch Checks from Database - checkService.js:L36-45",
+ "cellId": "499b46a2-f2a2-4ccd-aecd-012eade2bd23",
+ "visible": true,
+ "startLine": 36,
+ "endLine": 45,
+ "parentCellId": "f90db867-e1e7-4554-b446-fa78fc6a8578",
+ "parentPath": "server/src/service/v1/business/checkService.js",
+ "simSteps": [
+ {
+ "simulationKey": "Incident History, Analysis, and Management",
+ "simStepId": "83fea05d-8306-4a73-a43f-882cdd14f50f"
+ }
+ ]
+ },
+ "14df9f01-1358-442d-96f5-eab9a4c4abbf": {
+ "path": "14df9f01-1358-442d-96f5-eab9a4c4abbf",
+ "cellName": "Return Check Data - checkController.js:L87-90",
+ "cellId": "14df9f01-1358-442d-96f5-eab9a4c4abbf",
+ "visible": true,
+ "parentCellId": "d983e54f-2fbe-496a-932d-a827e1124804"
+ },
+ "server/src/controllers/v1/checkController.js-simstep-5768fdc5-045c-48ef-b0c9-79769403648f": {
+ "path": "server/src/controllers/v1/checkController.js-simstep-5768fdc5-045c-48ef-b0c9-79769403648f",
+ "fileName": "checkController.js",
+ "wiki": "The database returns the query results, which flow back up through the `checkModule` and `checkService`. The `checkController` then formats a successful JSON response.",
+ "cellName": "Return Check Data - checkController.js:L87-90",
+ "cellId": "14df9f01-1358-442d-96f5-eab9a4c4abbf",
+ "visible": true,
+ "startLine": 87,
+ "endLine": 90,
+ "parentCellId": "d983e54f-2fbe-496a-932d-a827e1124804",
+ "parentPath": "server/src/controllers/v1/checkController.js",
+ "simSteps": [
+ {
+ "simulationKey": "Incident History, Analysis, and Management",
+ "simStepId": "5768fdc5-045c-48ef-b0c9-79769403648f"
+ }
+ ]
+ },
+ "ceb9cda3-968d-44aa-b223-49902adb93b4": {
+ "path": "ceb9cda3-968d-44aa-b223-49902adb93b4",
+ "cellName": "Update Frontend State - checkHooks.js:L109-110",
+ "cellId": "ceb9cda3-968d-44aa-b223-49902adb93b4",
+ "visible": true,
+ "parentCellId": "933a6cdc-349c-4725-b7ae-14cf780bfde8"
+ },
+ "client/src/Hooks/v1/checkHooks.js-simstep-faba07f3-7460-47ae-924d-f92f5b2e821d": {
+ "path": "client/src/Hooks/v1/checkHooks.js-simstep-faba07f3-7460-47ae-924d-f92f5b2e821d",
+ "fileName": "checkHooks.js",
+ "wiki": "The `useFetchChecksByMonitor` hook receives the data from the API call and updates the component's state with the new list of checks and the total count.",
+ "cellName": "Update Frontend State - checkHooks.js:L109-110",
+ "cellId": "ceb9cda3-968d-44aa-b223-49902adb93b4",
+ "visible": true,
+ "startLine": 109,
+ "endLine": 110,
+ "parentCellId": "933a6cdc-349c-4725-b7ae-14cf780bfde8",
+ "parentPath": "client/src/Hooks/v1/checkHooks.js",
+ "simSteps": [
+ {
+ "simulationKey": "Incident History, Analysis, and Management",
+ "simStepId": "faba07f3-7460-47ae-924d-f92f5b2e821d"
+ }
+ ]
+ },
+ "c996f7ca-9384-4252-b343-7bfc049d14bb": {
+ "path": "c996f7ca-9384-4252-b343-7bfc049d14bb",
+ "cellName": "Render Historical Data Table - index.jsx:L11-92",
+ "cellId": "c996f7ca-9384-4252-b343-7bfc049d14bb",
+ "visible": true,
+ "parentCellId": "d3f74ac7-3b9a-4cae-8d77-a0d5d605bb27"
+ },
+ "client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx-simstep-2b1651cd-2e70-4cc6-b2bb-23f94fa73d66": {
+ "path": "client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx-simstep-2b1651cd-2e70-4cc6-b2bb-23f94fa73d66",
+ "fileName": "index.jsx",
+ "wiki": "The `ResponseTable` component receives the list of checks and renders them in a data table, allowing the user to view the incident history, including status, response time, and when the check occurred.",
+ "cellName": "Render Historical Data Table - index.jsx:L11-92",
+ "cellId": "c996f7ca-9384-4252-b343-7bfc049d14bb",
+ "visible": true,
+ "startLine": 11,
+ "endLine": 92,
+ "parentCellId": "d3f74ac7-3b9a-4cae-8d77-a0d5d605bb27",
+ "parentPath": "client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Incident History, Analysis, and Management",
+ "simStepId": "2b1651cd-2e70-4cc6-b2bb-23f94fa73d66"
+ }
+ ]
+ },
+ "e90e4943-6108-46e3-8a2c-a9a1c7361279": {
+ "path": "e90e4943-6108-46e3-8a2c-a9a1c7361279",
+ "cellName": "Trigger Check\nHistory Fetch",
+ "cellId": "e90e4943-6108-46e3-8a2c-a9a1c7361279",
+ "visible": true,
+ "parentCellId": "d3264577-10f0-42fe-9411-7e9df0754d1e"
+ },
+ "generated-edge-simstep-49979250-1a32-4da5-95e0-58652fe218f9-e90e4943-6108-46e3-8a2c-a9a1c7361279": {
+ "path": "generated-edge-simstep-49979250-1a32-4da5-95e0-58652fe218f9-e90e4943-6108-46e3-8a2c-a9a1c7361279",
+ "fileName": "index.jsx",
+ "cellName": "Trigger Check History Fetch",
+ "cellId": "e90e4943-6108-46e3-8a2c-a9a1c7361279",
+ "visible": true,
+ "startLine": 69,
+ "endLine": 77,
+ "parentPath": "client/src/Pages/v1/Uptime/Details/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Incident History, Analysis, and Management",
+ "simStepId": "49979250-1a32-4da5-95e0-58652fe218f9"
+ }
+ ]
+ },
+ "2b8ae59e-ed8d-4837-9a9b-64eb480f982c": {
+ "path": "2b8ae59e-ed8d-4837-9a9b-64eb480f982c",
+ "cellName": "API Request\nfor Check\nHistory",
+ "cellId": "2b8ae59e-ed8d-4837-9a9b-64eb480f982c",
+ "visible": true
+ },
+ "generated-edge-simstep-336b534d-8d6a-4639-af8e-889b38f51ab1-2b8ae59e-ed8d-4837-9a9b-64eb480f982c": {
+ "path": "generated-edge-simstep-336b534d-8d6a-4639-af8e-889b38f51ab1-2b8ae59e-ed8d-4837-9a9b-64eb480f982c",
+ "fileName": "checkHooks.js",
+ "cellName": "API Request for Check History",
+ "cellId": "2b8ae59e-ed8d-4837-9a9b-64eb480f982c",
+ "visible": true,
+ "startLine": 551,
+ "endLine": 562,
+ "parentPath": "client/src/Hooks/v1/checkHooks.js",
+ "simSteps": [
+ {
+ "simulationKey": "Incident History, Analysis, and Management",
+ "simStepId": "336b534d-8d6a-4639-af8e-889b38f51ab1"
+ }
+ ]
+ },
+ "839ba9f6-8a34-4919-a04a-13fba1795c61": {
+ "path": "839ba9f6-8a34-4919-a04a-13fba1795c61",
+ "cellName": "Forward Request\nto Service\nLayer",
+ "cellId": "839ba9f6-8a34-4919-a04a-13fba1795c61",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "generated-edge-simstep-4f73f0fe-b390-4fe1-a354-eb0857835126-839ba9f6-8a34-4919-a04a-13fba1795c61": {
+ "path": "generated-edge-simstep-4f73f0fe-b390-4fe1-a354-eb0857835126-839ba9f6-8a34-4919-a04a-13fba1795c61",
+ "fileName": "checkRoute.js",
+ "cellName": "Forward Request to Service Layer",
+ "cellId": "839ba9f6-8a34-4919-a04a-13fba1795c61",
+ "visible": true,
+ "startLine": 81,
+ "endLine": 85,
+ "parentPath": "server/src/routes/v1/checkRoute.js",
+ "simSteps": [
+ {
+ "simulationKey": "Incident History, Analysis, and Management",
+ "simStepId": "4f73f0fe-b390-4fe1-a354-eb0857835126"
+ }
+ ]
+ },
+ "42d3bc70-701a-4be0-96b4-d6c2f0519b49": {
+ "path": "42d3bc70-701a-4be0-96b4-d6c2f0519b49",
+ "cellName": "Execute Database\nQuery",
+ "cellId": "42d3bc70-701a-4be0-96b4-d6c2f0519b49",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "generated-edge-simstep-65da1a9e-041c-4656-9c5b-0a92b761a9be-42d3bc70-701a-4be0-96b4-d6c2f0519b49": {
+ "path": "generated-edge-simstep-65da1a9e-041c-4656-9c5b-0a92b761a9be-42d3bc70-701a-4be0-96b4-d6c2f0519b49",
+ "fileName": "checkService.js",
+ "cellName": "Execute Database Query",
+ "cellId": "42d3bc70-701a-4be0-96b4-d6c2f0519b49",
+ "visible": true,
+ "startLine": 28,
+ "endLine": 108,
+ "parentPath": "server/src/service/v1/business/checkService.js",
+ "simSteps": [
+ {
+ "simulationKey": "Incident History, Analysis, and Management",
+ "simStepId": "65da1a9e-041c-4656-9c5b-0a92b761a9be"
+ }
+ ]
+ },
+ "6c5b028c-356c-43c8-a35a-b47448cd8695": {
+ "path": "6c5b028c-356c-43c8-a35a-b47448cd8695",
+ "cellName": "API Response\nwith Check\nHistory",
+ "cellId": "6c5b028c-356c-43c8-a35a-b47448cd8695",
+ "visible": true
+ },
+ "generated-edge-simstep-2c1bdec8-d0e6-4163-a99c-f27899b987c5-6c5b028c-356c-43c8-a35a-b47448cd8695": {
+ "path": "generated-edge-simstep-2c1bdec8-d0e6-4163-a99c-f27899b987c5-6c5b028c-356c-43c8-a35a-b47448cd8695",
+ "fileName": "checkController.js",
+ "cellName": "API Response with Check History",
+ "cellId": "6c5b028c-356c-43c8-a35a-b47448cd8695",
+ "visible": true,
+ "startLine": 560,
+ "endLine": 560,
+ "parentPath": "server/src/controllers/v1/checkController.js",
+ "simSteps": [
+ {
+ "simulationKey": "Incident History, Analysis, and Management",
+ "simStepId": "2c1bdec8-d0e6-4163-a99c-f27899b987c5"
+ }
+ ]
+ },
+ "74b0dd89-9fea-4219-af1f-9571316a9d5a": {
+ "path": "74b0dd89-9fea-4219-af1f-9571316a9d5a",
+ "cellName": "Pass Data\nto UI\nComponents",
+ "cellId": "74b0dd89-9fea-4219-af1f-9571316a9d5a",
+ "visible": true,
+ "parentCellId": "d3264577-10f0-42fe-9411-7e9df0754d1e"
+ },
+ "generated-edge-simstep-9405dce1-0565-4dfa-9539-a048472acda9-74b0dd89-9fea-4219-af1f-9571316a9d5a": {
+ "path": "generated-edge-simstep-9405dce1-0565-4dfa-9539-a048472acda9-74b0dd89-9fea-4219-af1f-9571316a9d5a",
+ "fileName": "checkHooks.js",
+ "cellName": "Pass Data to UI Components",
+ "cellId": "74b0dd89-9fea-4219-af1f-9571316a9d5a",
+ "visible": true,
+ "startLine": 175,
+ "endLine": 186,
+ "parentPath": "client/src/Hooks/v1/checkHooks.js",
+ "simSteps": [
+ {
+ "simulationKey": "Incident History, Analysis, and Management",
+ "simStepId": "9405dce1-0565-4dfa-9539-a048472acda9"
+ }
+ ]
+ },
+ "e2f99143-54b7-44dc-b566-1c2fb901bd16": {
+ "path": "e2f99143-54b7-44dc-b566-1c2fb901bd16",
+ "cellName": "v2",
+ "cellId": "e2f99143-54b7-44dc-b566-1c2fb901bd16",
+ "visible": true,
+ "parentCellId": "15ee5d81-a0cf-4614-80b8-7a1123ab20b8"
+ },
+ "043d3dac-d4bb-449b-a3a7-270d53b66dac": {
+ "path": "043d3dac-d4bb-449b-a3a7-270d53b66dac",
+ "cellName": "PageSpeed",
+ "cellId": "043d3dac-d4bb-449b-a3a7-270d53b66dac",
+ "visible": true,
+ "parentCellId": "1a083b4a-787c-4eb4-8029-6700de987679"
+ },
+ "7ef79284-534a-466f-a1f1-4e3a2f56be9d": {
+ "path": "7ef79284-534a-466f-a1f1-4e3a2f56be9d",
+ "cellName": "business",
+ "cellId": "7ef79284-534a-466f-a1f1-4e3a2f56be9d",
+ "visible": true,
+ "parentCellId": "feefae96-61b8-4ac5-a37c-592ed4b2330e"
+ },
+ "b53384d7-b989-4ac7-bd68-1c3621174ef4": {
+ "path": "b53384d7-b989-4ac7-bd68-1c3621174ef4",
+ "cellName": "models",
+ "cellId": "b53384d7-b989-4ac7-bd68-1c3621174ef4",
+ "visible": true,
+ "parentCellId": "e2f99143-54b7-44dc-b566-1c2fb901bd16"
+ },
+ "414c500b-7b68-42ba-8110-e855efbee750": {
+ "path": "414c500b-7b68-42ba-8110-e855efbee750",
+ "cellName": "Create",
+ "cellId": "414c500b-7b68-42ba-8110-e855efbee750",
+ "visible": true,
+ "parentCellId": "043d3dac-d4bb-449b-a3a7-270d53b66dac"
+ },
+ "581b4919-8b0c-4f0d-ae67-ad6de11d8c8b": {
+ "path": "581b4919-8b0c-4f0d-ae67-ad6de11d8c8b",
+ "cellName": "Details",
+ "cellId": "581b4919-8b0c-4f0d-ae67-ad6de11d8c8b",
+ "visible": true,
+ "parentCellId": "043d3dac-d4bb-449b-a3a7-270d53b66dac"
+ },
+ "50aeed0b-bc13-43f7-bb15-cb1b12b2caa7": {
+ "path": "50aeed0b-bc13-43f7-bb15-cb1b12b2caa7",
+ "cellName": "NetworkService.ts",
+ "cellId": "50aeed0b-bc13-43f7-bb15-cb1b12b2caa7",
+ "visible": true,
+ "parentCellId": "3f8ff792-40c8-4ae6-9af5-ff8b90da2f9f"
+ },
+ "f0ebd5da-21bb-4d96-8ff9-ca10409acc80": {
+ "path": "f0ebd5da-21bb-4d96-8ff9-ca10409acc80",
+ "cellName": "CheckService.ts",
+ "cellId": "f0ebd5da-21bb-4d96-8ff9-ca10409acc80",
+ "visible": true,
+ "parentCellId": "7ef79284-534a-466f-a1f1-4e3a2f56be9d"
+ },
+ "7a3824cd-24c8-4b32-8b1d-e0bf357262c4": {
+ "path": "7a3824cd-24c8-4b32-8b1d-e0bf357262c4",
+ "cellName": "MonitorService.ts",
+ "cellId": "7a3824cd-24c8-4b32-8b1d-e0bf357262c4",
+ "visible": true,
+ "parentCellId": "7ef79284-534a-466f-a1f1-4e3a2f56be9d"
+ },
+ "64e3bb0a-67f5-4269-9e7d-94fe91ac78fc": {
+ "path": "64e3bb0a-67f5-4269-9e7d-94fe91ac78fc",
+ "cellName": "checks",
+ "cellId": "64e3bb0a-67f5-4269-9e7d-94fe91ac78fc",
+ "visible": true,
+ "parentCellId": "b53384d7-b989-4ac7-bd68-1c3621174ef4"
+ },
+ "5ffe0baa-054a-4e5b-8b29-1254f07ea975": {
+ "path": "5ffe0baa-054a-4e5b-8b29-1254f07ea975",
+ "cellName": "index.jsx",
+ "cellId": "5ffe0baa-054a-4e5b-8b29-1254f07ea975",
+ "visible": true,
+ "parentCellId": "414c500b-7b68-42ba-8110-e855efbee750"
+ },
+ "c4a7bb58-1d9c-4690-b6b8-460c1cc33702": {
+ "path": "c4a7bb58-1d9c-4690-b6b8-460c1cc33702",
+ "cellName": "index.jsx",
+ "cellId": "c4a7bb58-1d9c-4690-b6b8-460c1cc33702",
+ "visible": true,
+ "parentCellId": "581b4919-8b0c-4f0d-ae67-ad6de11d8c8b"
+ },
+ "1425ec43-f0f4-4c8b-a140-176c079219d6": {
+ "path": "1425ec43-f0f4-4c8b-a140-176c079219d6",
+ "cellName": "Check.ts",
+ "cellId": "1425ec43-f0f4-4c8b-a140-176c079219d6",
+ "visible": true,
+ "parentCellId": "64e3bb0a-67f5-4269-9e7d-94fe91ac78fc"
+ },
+ "0622b212-3a4a-484d-9b7a-125864d785cd": {
+ "path": "0622b212-3a4a-484d-9b7a-125864d785cd",
+ "cellName": "Create PageSpeed Monitor - index.jsx:L111-143",
+ "cellId": "0622b212-3a4a-484d-9b7a-125864d785cd",
+ "visible": true,
+ "parentCellId": "5ffe0baa-054a-4e5b-8b29-1254f07ea975"
+ },
+ "client/src/Pages/v1/PageSpeed/Create/index.jsx-simstep-b0cc6e6a-961e-4ea9-aa8a-6bfa44d6ba12": {
+ "path": "client/src/Pages/v1/PageSpeed/Create/index.jsx-simstep-b0cc6e6a-961e-4ea9-aa8a-6bfa44d6ba12",
+ "fileName": "index.jsx",
+ "wiki": "The user navigates to the PageSpeed creation page, fills in the details for a new monitor (URL, name, interval), and submits the form. This triggers the `handleSubmit` function, which calls the `createMonitor` hook to send the data to the backend API.",
+ "cellName": "Create PageSpeed Monitor - index.jsx:L111-143",
+ "cellId": "0622b212-3a4a-484d-9b7a-125864d785cd",
+ "visible": true,
+ "startLine": 111,
+ "endLine": 143,
+ "parentCellId": "5ffe0baa-054a-4e5b-8b29-1254f07ea975",
+ "parentPath": "client/src/Pages/v1/PageSpeed/Create/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Website Performance and Page Speed Monitoring",
+ "simStepId": "b0cc6e6a-961e-4ea9-aa8a-6bfa44d6ba12"
+ }
+ ]
+ },
+ "6527fb47-157e-4bba-9aed-adde061c5689": {
+ "path": "6527fb47-157e-4bba-9aed-adde061c5689",
+ "cellName": "Backend Requests PageSpeed Analysis - NetworkService.ts:L129-162",
+ "cellId": "6527fb47-157e-4bba-9aed-adde061c5689",
+ "visible": true,
+ "parentCellId": "50aeed0b-bc13-43f7-bb15-cb1b12b2caa7"
+ },
+ "server/src/service/v2/infrastructure/NetworkService.ts-simstep-53968267-0c6e-42f4-88b3-cd90f44532ae": {
+ "path": "server/src/service/v2/infrastructure/NetworkService.ts-simstep-53968267-0c6e-42f4-88b3-cd90f44532ae",
+ "fileName": "NetworkService.ts",
+ "wiki": "A background job for the newly created monitor triggers the `NetworkService` to request a performance analysis from the Google PageSpeed Insights API. It constructs the API URL using the monitor's target URL and the configured PageSpeed API key.",
+ "cellName": "Backend Requests PageSpeed Analysis - NetworkService.ts:L129-162",
+ "cellId": "6527fb47-157e-4bba-9aed-adde061c5689",
+ "visible": true,
+ "startLine": 129,
+ "endLine": 162,
+ "parentCellId": "50aeed0b-bc13-43f7-bb15-cb1b12b2caa7",
+ "parentPath": "server/src/service/v2/infrastructure/NetworkService.ts",
+ "simSteps": [
+ {
+ "simulationKey": "Website Performance and Page Speed Monitoring",
+ "simStepId": "53968267-0c6e-42f4-88b3-cd90f44532ae"
+ }
+ ]
+ },
+ "f89cbd87-c961-4f8b-92b6-714da57718d5": {
+ "path": "f89cbd87-c961-4f8b-92b6-714da57718d5",
+ "cellName": "Backend Processes Lighthouse Data - CheckService.ts:L82-102",
+ "cellId": "f89cbd87-c961-4f8b-92b6-714da57718d5",
+ "visible": true,
+ "parentCellId": "f0ebd5da-21bb-4d96-8ff9-ca10409acc80"
+ },
+ "server/src/service/v2/business/CheckService.ts-simstep-01206383-2db8-4b18-84d0-be2a0289c196": {
+ "path": "server/src/service/v2/business/CheckService.ts-simstep-01206383-2db8-4b18-84d0-be2a0289c196",
+ "fileName": "CheckService.ts",
+ "wiki": "The `CheckService` receives the raw data from the Google API. The `buildPagespeedCheck` function extracts and transforms the relevant scores and audit details into a structured `Check` object, preparing it for database storage.",
+ "cellName": "Backend Processes Lighthouse Data - CheckService.ts:L82-102",
+ "cellId": "f89cbd87-c961-4f8b-92b6-714da57718d5",
+ "visible": true,
+ "startLine": 82,
+ "endLine": 102,
+ "parentCellId": "f0ebd5da-21bb-4d96-8ff9-ca10409acc80",
+ "parentPath": "server/src/service/v2/business/CheckService.ts",
+ "simSteps": [
+ {
+ "simulationKey": "Website Performance and Page Speed Monitoring",
+ "simStepId": "01206383-2db8-4b18-84d0-be2a0289c196"
+ }
+ ]
+ },
+ "ca6ee27b-1fbf-49de-978a-d6de259cc3d5": {
+ "path": "ca6ee27b-1fbf-49de-978a-d6de259cc3d5",
+ "cellName": "Backend Aggregates Performance Data - MonitorService.ts:L106-120",
+ "cellId": "ca6ee27b-1fbf-49de-978a-d6de259cc3d5",
+ "visible": true,
+ "parentCellId": "7a3824cd-24c8-4b32-8b1d-e0bf357262c4"
+ },
+ "server/src/service/v2/business/MonitorService.ts-simstep-5616ac6d-c281-4879-82f1-6590c721c8f7": {
+ "path": "server/src/service/v2/business/MonitorService.ts-simstep-5616ac6d-c281-4879-82f1-6590c721c8f7",
+ "fileName": "MonitorService.ts",
+ "wiki": "When a user views the details of a PageSpeed monitor, the frontend requests historical data. The `MonitorService` on the backend uses a MongoDB aggregation pipeline to group checks by time intervals and calculate average scores for metrics like performance, SEO, etc. This is defined by helper functions like `getPageSpeedGroup`.",
+ "cellName": "Backend Aggregates Performance Data - MonitorService.ts:L106-120",
+ "cellId": "ca6ee27b-1fbf-49de-978a-d6de259cc3d5",
+ "visible": true,
+ "startLine": 106,
+ "endLine": 120,
+ "parentCellId": "7a3824cd-24c8-4b32-8b1d-e0bf357262c4",
+ "parentPath": "server/src/service/v2/business/MonitorService.ts",
+ "simSteps": [
+ {
+ "simulationKey": "Website Performance and Page Speed Monitoring",
+ "simStepId": "5616ac6d-c281-4879-82f1-6590c721c8f7"
+ }
+ ]
+ },
+ "9ef9e218-4742-487a-9a88-5c70e788ccdc": {
+ "path": "9ef9e218-4742-487a-9a88-5c70e788ccdc",
+ "cellName": "Frontend Renders Performance Charts - index.jsx:L91-126",
+ "cellId": "9ef9e218-4742-487a-9a88-5c70e788ccdc",
+ "visible": true,
+ "parentCellId": "c4a7bb58-1d9c-4690-b6b8-460c1cc33702"
+ },
+ "client/src/Pages/v1/PageSpeed/Details/index.jsx-simstep-0ec0eb06-05eb-4204-b077-9e47eb2cc316": {
+ "path": "client/src/Pages/v1/PageSpeed/Details/index.jsx-simstep-0ec0eb06-05eb-4204-b077-9e47eb2cc316",
+ "fileName": "index.jsx",
+ "wiki": "The `PageSpeedDetails` component receives the historical data from the backend. It then passes this data to specialized child components like `PageSpeedAreaChart` and `PerformanceReport` to render visualizations of the website's performance over time.",
+ "cellName": "Frontend Renders Performance Charts - index.jsx:L91-126",
+ "cellId": "9ef9e218-4742-487a-9a88-5c70e788ccdc",
+ "visible": true,
+ "startLine": 91,
+ "endLine": 126,
+ "parentCellId": "c4a7bb58-1d9c-4690-b6b8-460c1cc33702",
+ "parentPath": "client/src/Pages/v1/PageSpeed/Details/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Website Performance and Page Speed Monitoring",
+ "simStepId": "0ec0eb06-05eb-4204-b077-9e47eb2cc316"
+ }
+ ]
+ },
+ "b8e83b0a-69ff-4c34-a7fe-cb8c9ba85438": {
+ "path": "b8e83b0a-69ff-4c34-a7fe-cb8c9ba85438",
+ "cellName": "API Call:\nCreate Monitor",
+ "cellId": "b8e83b0a-69ff-4c34-a7fe-cb8c9ba85438",
+ "visible": true
+ },
+ "generated-edge-simstep-f8df1cad-6051-466e-a13e-47d710ba1c14-b8e83b0a-69ff-4c34-a7fe-cb8c9ba85438": {
+ "path": "generated-edge-simstep-f8df1cad-6051-466e-a13e-47d710ba1c14-b8e83b0a-69ff-4c34-a7fe-cb8c9ba85438",
+ "fileName": "index.jsx",
+ "cellName": "API Call: Create Monitor",
+ "cellId": "b8e83b0a-69ff-4c34-a7fe-cb8c9ba85438",
+ "visible": true,
+ "startLine": 119,
+ "endLine": 119,
+ "parentPath": "client/src/Pages/v1/PageSpeed/Create/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Website Performance and Page Speed Monitoring",
+ "simStepId": "f8df1cad-6051-466e-a13e-47d710ba1c14"
+ }
+ ]
+ },
+ "0d3c8091-3d91-4e42-9b46-7e35ed726a7a": {
+ "path": "0d3c8091-3d91-4e42-9b46-7e35ed726a7a",
+ "cellName": "Google PageSpeed\nAPI Response",
+ "cellId": "0d3c8091-3d91-4e42-9b46-7e35ed726a7a",
+ "visible": true,
+ "parentCellId": "feefae96-61b8-4ac5-a37c-592ed4b2330e"
+ },
+ "generated-edge-simstep-0b380571-3896-4365-b5dc-187494f02e0d-0d3c8091-3d91-4e42-9b46-7e35ed726a7a": {
+ "path": "generated-edge-simstep-0b380571-3896-4365-b5dc-187494f02e0d-0d3c8091-3d91-4e42-9b46-7e35ed726a7a",
+ "fileName": "NetworkService.ts",
+ "cellName": "Google PageSpeed API Response",
+ "cellId": "0d3c8091-3d91-4e42-9b46-7e35ed726a7a",
+ "visible": true,
+ "startLine": 156,
+ "endLine": 156,
+ "parentPath": "server/src/service/v2/infrastructure/NetworkService.ts",
+ "simSteps": [
+ {
+ "simulationKey": "Website Performance and Page Speed Monitoring",
+ "simStepId": "0b380571-3896-4365-b5dc-187494f02e0d"
+ }
+ ]
+ },
+ "27c7191b-80a2-475b-9371-e106108aff1c": {
+ "path": "27c7191b-80a2-475b-9371-e106108aff1c",
+ "cellName": "Store Processed\nCheck Data",
+ "cellId": "27c7191b-80a2-475b-9371-e106108aff1c",
+ "visible": true,
+ "parentCellId": "7ef79284-534a-466f-a1f1-4e3a2f56be9d"
+ },
+ "generated-edge-simstep-a629a102-0df1-4798-a7a2-45c8f6cdad29-27c7191b-80a2-475b-9371-e106108aff1c": {
+ "path": "generated-edge-simstep-a629a102-0df1-4798-a7a2-45c8f6cdad29-27c7191b-80a2-475b-9371-e106108aff1c",
+ "fileName": "CheckService.ts",
+ "cellName": "Store Processed Check Data",
+ "cellId": "27c7191b-80a2-475b-9371-e106108aff1c",
+ "visible": true,
+ "startLine": 237,
+ "endLine": 268,
+ "parentPath": "server/src/service/v2/business/CheckService.ts",
+ "simSteps": [
+ {
+ "simulationKey": "Website Performance and Page Speed Monitoring",
+ "simStepId": "a629a102-0df1-4798-a7a2-45c8f6cdad29"
+ }
+ ]
+ },
+ "7be432cd-2587-4eab-abdc-f7b9c1923597": {
+ "path": "7be432cd-2587-4eab-abdc-f7b9c1923597",
+ "cellName": "Aggregated Data\nTransmitted to\nFrontend",
+ "cellId": "7be432cd-2587-4eab-abdc-f7b9c1923597",
+ "visible": true
+ },
+ "generated-edge-simstep-4ed43eaf-fa8f-4fce-af87-8a5f74dd5c08-7be432cd-2587-4eab-abdc-f7b9c1923597": {
+ "path": "generated-edge-simstep-4ed43eaf-fa8f-4fce-af87-8a5f74dd5c08-7be432cd-2587-4eab-abdc-f7b9c1923597",
+ "fileName": "MonitorService.ts",
+ "cellName": "Aggregated Data Transmitted to Frontend",
+ "cellId": "7be432cd-2587-4eab-abdc-f7b9c1923597",
+ "visible": true,
+ "startLine": 368,
+ "endLine": 372,
+ "parentPath": "server/src/service/v2/business/MonitorService.ts",
+ "simSteps": [
+ {
+ "simulationKey": "Website Performance and Page Speed Monitoring",
+ "simStepId": "4ed43eaf-fa8f-4fce-af87-8a5f74dd5c08"
+ }
+ ]
+ },
+ "fe47576c-c186-4950-82a5-b2fd24eb2561": {
+ "path": "fe47576c-c186-4950-82a5-b2fd24eb2561",
+ "cellName": "config",
+ "cellId": "fe47576c-c186-4950-82a5-b2fd24eb2561",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "cb53d612-9b89-47da-911c-165d0382a7d8": {
+ "path": "cb53d612-9b89-47da-911c-165d0382a7d8",
+ "cellName": "routes.js",
+ "cellId": "cb53d612-9b89-47da-911c-165d0382a7d8",
+ "visible": true,
+ "parentCellId": "fe47576c-c186-4950-82a5-b2fd24eb2561"
+ },
+ "8021b55d-2d3a-4416-b598-e134e76c9434": {
+ "path": "8021b55d-2d3a-4416-b598-e134e76c9434",
+ "cellName": "Maintenance",
+ "cellId": "8021b55d-2d3a-4416-b598-e134e76c9434",
+ "visible": true,
+ "parentCellId": "1a083b4a-787c-4eb4-8029-6700de987679"
+ },
+ "5f21f109-1967-416f-a649-9b48e78b90f6": {
+ "path": "5f21f109-1967-416f-a649-9b48e78b90f6",
+ "cellName": "maintenanceWindowRoute.js",
+ "cellId": "5f21f109-1967-416f-a649-9b48e78b90f6",
+ "visible": true,
+ "parentCellId": "d3375a20-2776-4771-b806-649bbeb541bd"
+ },
+ "a0cae18c-3c0e-40d3-ab03-eef7eace65d6": {
+ "path": "a0cae18c-3c0e-40d3-ab03-eef7eace65d6",
+ "cellName": "maintenanceWindowController.js",
+ "cellId": "a0cae18c-3c0e-40d3-ab03-eef7eace65d6",
+ "visible": true,
+ "parentCellId": "fc591f7a-d4ac-4565-949d-a87a58a79bbc"
+ },
+ "6f7119ac-d65a-4fdf-a6f2-31052e0f5549": {
+ "path": "6f7119ac-d65a-4fdf-a6f2-31052e0f5549",
+ "cellName": "CreateMaintenance",
+ "cellId": "6f7119ac-d65a-4fdf-a6f2-31052e0f5549",
+ "visible": true,
+ "parentCellId": "8021b55d-2d3a-4416-b598-e134e76c9434"
+ },
+ "1eae6263-aaa2-4206-b92e-1e67f529c1c9": {
+ "path": "1eae6263-aaa2-4206-b92e-1e67f529c1c9",
+ "cellName": "index.jsx",
+ "cellId": "1eae6263-aaa2-4206-b92e-1e67f529c1c9",
+ "visible": true,
+ "parentCellId": "8021b55d-2d3a-4416-b598-e134e76c9434"
+ },
+ "5ad4d33f-0932-479c-bb57-22ce6405758f": {
+ "path": "5ad4d33f-0932-479c-bb57-22ce6405758f",
+ "cellName": "MaintenanceTable",
+ "cellId": "5ad4d33f-0932-479c-bb57-22ce6405758f",
+ "visible": true,
+ "parentCellId": "8021b55d-2d3a-4416-b598-e134e76c9434"
+ },
+ "26b8daff-aa48-4baa-9d6c-05f771bf0990": {
+ "path": "26b8daff-aa48-4baa-9d6c-05f771bf0990",
+ "cellName": "maintenanceWindowService.js",
+ "cellId": "26b8daff-aa48-4baa-9d6c-05f771bf0990",
+ "visible": true,
+ "parentCellId": "87d3b101-9443-4ed4-9c4f-f759d41677eb"
+ },
+ "95ed6c41-ebeb-4c08-98c3-bab8b426789e": {
+ "path": "95ed6c41-ebeb-4c08-98c3-bab8b426789e",
+ "cellName": "maintenanceWindowModule.js",
+ "cellId": "95ed6c41-ebeb-4c08-98c3-bab8b426789e",
+ "visible": true,
+ "parentCellId": "82cb3dbe-c922-438a-a912-255376f4b380"
+ },
+ "92c244d2-e6a7-4804-a643-1e8d36baf9c6": {
+ "path": "92c244d2-e6a7-4804-a643-1e8d36baf9c6",
+ "cellName": "index.jsx",
+ "cellId": "92c244d2-e6a7-4804-a643-1e8d36baf9c6",
+ "visible": true,
+ "parentCellId": "6f7119ac-d65a-4fdf-a6f2-31052e0f5549"
+ },
+ "015a17fc-3fcf-45e0-8936-19f5e2ca86a3": {
+ "path": "015a17fc-3fcf-45e0-8936-19f5e2ca86a3",
+ "cellName": "hooks",
+ "cellId": "015a17fc-3fcf-45e0-8936-19f5e2ca86a3",
+ "visible": true,
+ "parentCellId": "6f7119ac-d65a-4fdf-a6f2-31052e0f5549"
+ },
+ "e7ec5ca9-6d25-4bf2-9695-639556f7d141": {
+ "path": "e7ec5ca9-6d25-4bf2-9695-639556f7d141",
+ "cellName": "index.jsx",
+ "cellId": "e7ec5ca9-6d25-4bf2-9695-639556f7d141",
+ "visible": true,
+ "parentCellId": "5ad4d33f-0932-479c-bb57-22ce6405758f"
+ },
+ "6ad0396e-9b38-4f5d-9ad9-6c9fcb4434f4": {
+ "path": "6ad0396e-9b38-4f5d-9ad9-6c9fcb4434f4",
+ "cellName": "useMaintenanceActions.jsx",
+ "cellId": "6ad0396e-9b38-4f5d-9ad9-6c9fcb4434f4",
+ "visible": true,
+ "parentCellId": "015a17fc-3fcf-45e0-8936-19f5e2ca86a3"
+ },
+ "20335f29-a2ac-4e0c-aba6-28707748ecd4": {
+ "path": "20335f29-a2ac-4e0c-aba6-28707748ecd4",
+ "cellName": "Flow: Create Maintenance Window - User Interaction - index.jsx:L46-131",
+ "cellId": "20335f29-a2ac-4e0c-aba6-28707748ecd4",
+ "visible": true,
+ "parentCellId": "92c244d2-e6a7-4804-a643-1e8d36baf9c6"
+ },
+ "client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx-simstep-2401ef7a-8d8e-4056-868a-547ab213daf1": {
+ "path": "client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx-simstep-2401ef7a-8d8e-4056-868a-547ab213daf1",
+ "fileName": "index.jsx",
+ "wiki": "A user navigates to the 'Create Maintenance Window' page. They fill out a form with details such as a name, start date/time, duration, recurrence, and select the monitors to which this window will apply.",
+ "cellName": "Flow: Create Maintenance Window - User Interaction - index.jsx:L46-131",
+ "cellId": "20335f29-a2ac-4e0c-aba6-28707748ecd4",
+ "visible": true,
+ "startLine": 46,
+ "endLine": 131,
+ "parentCellId": "92c244d2-e6a7-4804-a643-1e8d36baf9c6",
+ "parentPath": "client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Scheduled Maintenance Windows",
+ "simStepId": "2401ef7a-8d8e-4056-868a-547ab213daf1"
+ }
+ ]
+ },
+ "5bdfdfa4-5d93-49fa-b05c-905813e6cf9d": {
+ "path": "5bdfdfa4-5d93-49fa-b05c-905813e6cf9d",
+ "cellName": "Flow: Create Maintenance Window - Prepare API Request - useMaintenanceActions.jsx:L25-58",
+ "cellId": "5bdfdfa4-5d93-49fa-b05c-905813e6cf9d",
+ "visible": true,
+ "parentCellId": "6ad0396e-9b38-4f5d-9ad9-6c9fcb4434f4"
+ },
+ "client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx-simstep-5e0d7085-099b-478a-afaa-47ad4dd07043": {
+ "path": "client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx-simstep-5e0d7085-099b-478a-afaa-47ad4dd07043",
+ "fileName": "useMaintenanceActions.jsx",
+ "wiki": "The `handleSubmitForm` hook processes the form data, calculates start and end timestamps, and constructs a request object. It then determines whether to call `createMaintenanceWindow` or `editMaintenanceWindow` on the network service.",
+ "cellName": "Flow: Create Maintenance Window - Prepare API Request - useMaintenanceActions.jsx:L25-58",
+ "cellId": "5bdfdfa4-5d93-49fa-b05c-905813e6cf9d",
+ "visible": true,
+ "startLine": 25,
+ "endLine": 58,
+ "parentCellId": "6ad0396e-9b38-4f5d-9ad9-6c9fcb4434f4",
+ "parentPath": "client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Scheduled Maintenance Windows",
+ "simStepId": "5e0d7085-099b-478a-afaa-47ad4dd07043"
+ }
+ ]
+ },
+ "a391e8ea-9dbe-4fe4-90df-bf475d8b7ef8": {
+ "path": "a391e8ea-9dbe-4fe4-90df-bf475d8b7ef8",
+ "cellName": "Flow: Create Maintenance Window - API Routing - routes.js:L43",
+ "cellId": "a391e8ea-9dbe-4fe4-90df-bf475d8b7ef8",
+ "visible": true,
+ "parentCellId": "cb53d612-9b89-47da-911c-165d0382a7d8"
+ },
+ "server/src/config/routes.js-simstep-3e93ef65-8e2c-405b-8dc1-56d8e93937b3": {
+ "path": "server/src/config/routes.js-simstep-3e93ef65-8e2c-405b-8dc1-56d8e93937b3",
+ "fileName": "routes.js",
+ "wiki": "The backend Express server receives the request. The routing configuration maps the `/api/v1/maintenance-window` path to the `maintenanceWindowRoutes`, which in turn directs the POST request to the `createMaintenanceWindows` method in the controller.",
+ "cellName": "Flow: Create Maintenance Window - API Routing - routes.js:L43",
+ "cellId": "a391e8ea-9dbe-4fe4-90df-bf475d8b7ef8",
+ "visible": true,
+ "startLine": 43,
+ "endLine": 43,
+ "parentCellId": "cb53d612-9b89-47da-911c-165d0382a7d8",
+ "parentPath": "server/src/config/routes.js",
+ "simSteps": [
+ {
+ "simulationKey": "Scheduled Maintenance Windows",
+ "simStepId": "3e93ef65-8e2c-405b-8dc1-56d8e93937b3"
+ }
+ ]
+ },
+ "0fae6e75-38ea-444d-a1bf-0f820ba443b9": {
+ "path": "0fae6e75-38ea-444d-a1bf-0f820ba443b9",
+ "cellName": "Flow: Create Maintenance Window - Controller Logic - maintenanceWindowController.js:L27-41",
+ "cellId": "0fae6e75-38ea-444d-a1bf-0f820ba443b9",
+ "visible": true,
+ "parentCellId": "a0cae18c-3c0e-40d3-ab03-eef7eace65d6"
+ },
+ "server/src/controllers/v1/maintenanceWindowController.js-simstep-95fc1919-339d-4659-98cb-3dc50f662075": {
+ "path": "server/src/controllers/v1/maintenanceWindowController.js-simstep-95fc1919-339d-4659-98cb-3dc50f662075",
+ "fileName": "maintenanceWindowController.js",
+ "wiki": "The `maintenanceWindowController` validates the request body using Joi, ensures a `teamId` exists on the user object, and then calls the `maintenanceWindowService` to handle the business logic of creating the window.",
+ "cellName": "Flow: Create Maintenance Window - Controller Logic - maintenanceWindowController.js:L27-41",
+ "cellId": "0fae6e75-38ea-444d-a1bf-0f820ba443b9",
+ "visible": true,
+ "startLine": 27,
+ "endLine": 41,
+ "parentCellId": "a0cae18c-3c0e-40d3-ab03-eef7eace65d6",
+ "parentPath": "server/src/controllers/v1/maintenanceWindowController.js",
+ "simSteps": [
+ {
+ "simulationKey": "Scheduled Maintenance Windows",
+ "simStepId": "95fc1919-339d-4659-98cb-3dc50f662075"
+ }
+ ]
+ },
+ "560d268b-ef0f-4cd2-bcfe-9dc0a0e2ce6f": {
+ "path": "560d268b-ef0f-4cd2-bcfe-9dc0a0e2ce6f",
+ "cellName": "Flow: Create Maintenance Window - Business Logic Execution - maintenanceWindowService.js:L18-39",
+ "cellId": "560d268b-ef0f-4cd2-bcfe-9dc0a0e2ce6f",
+ "visible": true,
+ "parentCellId": "26b8daff-aa48-4baa-9d6c-05f771bf0990"
+ },
+ "server/src/service/v1/business/maintenanceWindowService.js-simstep-6e2cf739-1514-4c37-9874-10b68a47010e": {
+ "path": "server/src/service/v1/business/maintenanceWindowService.js-simstep-6e2cf739-1514-4c37-9874-10b68a47010e",
+ "fileName": "maintenanceWindowService.js",
+ "wiki": "The `maintenanceWindowService` verifies that all monitors specified in the request belong to the user's team. It then iterates through each monitor ID and prepares a separate database transaction to create a maintenance window record for each one.",
+ "cellName": "Flow: Create Maintenance Window - Business Logic Execution - maintenanceWindowService.js:L18-39",
+ "cellId": "560d268b-ef0f-4cd2-bcfe-9dc0a0e2ce6f",
+ "visible": true,
+ "startLine": 18,
+ "endLine": 39,
+ "parentCellId": "26b8daff-aa48-4baa-9d6c-05f771bf0990",
+ "parentPath": "server/src/service/v1/business/maintenanceWindowService.js",
+ "simSteps": [
+ {
+ "simulationKey": "Scheduled Maintenance Windows",
+ "simStepId": "6e2cf739-1514-4c37-9874-10b68a47010e"
+ }
+ ]
+ },
+ "3890b98d-a133-4279-a961-5d2facc6a17a": {
+ "path": "3890b98d-a133-4279-a961-5d2facc6a17a",
+ "cellName": "Flow: Create Maintenance Window - Database Record Creation - maintenanceWindowModule.js:L7-22",
+ "cellId": "3890b98d-a133-4279-a961-5d2facc6a17a",
+ "visible": true,
+ "parentCellId": "95ed6c41-ebeb-4c08-98c3-bab8b426789e"
+ },
+ "server/src/db/v1/modules/maintenanceWindowModule.js-simstep-a3c47fd4-c701-46c2-9aff-94a667b3e368": {
+ "path": "server/src/db/v1/modules/maintenanceWindowModule.js-simstep-a3c47fd4-c701-46c2-9aff-94a667b3e368",
+ "fileName": "maintenanceWindowModule.js",
+ "wiki": "The `maintenanceWindowModule` instantiates a new `MaintenanceWindow` Mongoose model. If the window is a one-time event, it sets an `expiry` field to the end time for automatic cleanup by MongoDB's TTL index. Finally, it saves the new document to the database.",
+ "cellName": "Flow: Create Maintenance Window - Database Record Creation - maintenanceWindowModule.js:L7-22",
+ "cellId": "3890b98d-a133-4279-a961-5d2facc6a17a",
+ "visible": true,
+ "startLine": 7,
+ "endLine": 22,
+ "parentCellId": "95ed6c41-ebeb-4c08-98c3-bab8b426789e",
+ "parentPath": "server/src/db/v1/modules/maintenanceWindowModule.js",
+ "simSteps": [
+ {
+ "simulationKey": "Scheduled Maintenance Windows",
+ "simStepId": "a3c47fd4-c701-46c2-9aff-94a667b3e368"
+ }
+ ]
+ },
+ "a5a84b08-8c70-4023-8d08-b74e23aab4d7": {
+ "path": "a5a84b08-8c70-4023-8d08-b74e23aab4d7",
+ "cellName": "Flow: Create Maintenance Window - UI Update - index.jsx:L111-119",
+ "cellId": "a5a84b08-8c70-4023-8d08-b74e23aab4d7",
+ "visible": true,
+ "parentCellId": "92c244d2-e6a7-4804-a643-1e8d36baf9c6"
+ },
+ "client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx-simstep-ebe8fd52-f2a1-4a98-a71b-b04d6f4cdadc": {
+ "path": "client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx-simstep-ebe8fd52-f2a1-4a98-a71b-b04d6f4cdadc",
+ "fileName": "index.jsx",
+ "wiki": "The frontend receives the success response. A success toast message is displayed, and the user is navigated back to the main maintenance windows table.",
+ "cellName": "Flow: Create Maintenance Window - UI Update - index.jsx:L111-119",
+ "cellId": "a5a84b08-8c70-4023-8d08-b74e23aab4d7",
+ "visible": true,
+ "startLine": 111,
+ "endLine": 119,
+ "parentCellId": "92c244d2-e6a7-4804-a643-1e8d36baf9c6",
+ "parentPath": "client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Scheduled Maintenance Windows",
+ "simStepId": "ebe8fd52-f2a1-4a98-a71b-b04d6f4cdadc"
+ }
+ ]
+ },
+ "d145bb4c-e1e0-473a-b6bc-decc932b738b": {
+ "path": "d145bb4c-e1e0-473a-b6bc-decc932b738b",
+ "cellName": "Flow: View Maintenance Windows - Page Load - index.jsx:L25-47",
+ "cellId": "d145bb4c-e1e0-473a-b6bc-decc932b738b",
+ "visible": true,
+ "parentCellId": "1eae6263-aaa2-4206-b92e-1e67f529c1c9"
+ },
+ "client/src/Pages/v1/Maintenance/index.jsx-simstep-82b5ea14-bb00-4511-aba4-76196029fd81": {
+ "path": "client/src/Pages/v1/Maintenance/index.jsx-simstep-82b5ea14-bb00-4511-aba4-76196029fd81",
+ "fileName": "index.jsx",
+ "wiki": "User navigates to the '/maintenance' page. The `Maintenance` component mounts and triggers a `useEffect` hook to fetch the list of maintenance windows.",
+ "cellName": "Flow: View Maintenance Windows - Page Load - index.jsx:L25-47",
+ "cellId": "d145bb4c-e1e0-473a-b6bc-decc932b738b",
+ "visible": true,
+ "startLine": 25,
+ "endLine": 47,
+ "parentCellId": "1eae6263-aaa2-4206-b92e-1e67f529c1c9",
+ "parentPath": "client/src/Pages/v1/Maintenance/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Scheduled Maintenance Windows",
+ "simStepId": "82b5ea14-bb00-4511-aba4-76196029fd81"
+ }
+ ]
+ },
+ "c35a065c-2808-402c-877e-9055d956ce0d": {
+ "path": "c35a065c-2808-402c-877e-9055d956ce0d",
+ "cellName": "Flow: View Maintenance Windows - Controller and Service - maintenanceWindowController.js:L75",
+ "cellId": "c35a065c-2808-402c-877e-9055d956ce0d",
+ "visible": true,
+ "parentCellId": "a0cae18c-3c0e-40d3-ab03-eef7eace65d6"
+ },
+ "server/src/controllers/v1/maintenanceWindowController.js-simstep-1251da83-5949-4e24-a1d2-fc74ff785aa9": {
+ "path": "server/src/controllers/v1/maintenanceWindowController.js-simstep-1251da83-5949-4e24-a1d2-fc74ff785aa9",
+ "fileName": "maintenanceWindowController.js",
+ "wiki": "The backend routes the request to `maintenanceWindowController.getMaintenanceWindowsByTeamId`. This controller calls the corresponding service, which in turn calls the database module to query for the data.",
+ "cellName": "Flow: View Maintenance Windows - Controller and Service - maintenanceWindowController.js:L75",
+ "cellId": "c35a065c-2808-402c-877e-9055d956ce0d",
+ "visible": true,
+ "startLine": 75,
+ "endLine": 75,
+ "parentCellId": "a0cae18c-3c0e-40d3-ab03-eef7eace65d6",
+ "parentPath": "server/src/controllers/v1/maintenanceWindowController.js",
+ "simSteps": [
+ {
+ "simulationKey": "Scheduled Maintenance Windows",
+ "simStepId": "1251da83-5949-4e24-a1d2-fc74ff785aa9"
+ }
+ ]
+ },
+ "27d9160a-4f8d-4dff-a09b-1678bc28cf2c": {
+ "path": "27d9160a-4f8d-4dff-a09b-1678bc28cf2c",
+ "cellName": "Flow: View Maintenance Windows - Data Retrieval - maintenanceWindowModule.js:L37-66",
+ "cellId": "27d9160a-4f8d-4dff-a09b-1678bc28cf2c",
+ "visible": true,
+ "parentCellId": "95ed6c41-ebeb-4c08-98c3-bab8b426789e"
+ },
+ "server/src/db/v1/modules/maintenanceWindowModule.js-simstep-9bfbae5a-f487-40ef-86f5-04a1cc3c7662": {
+ "path": "server/src/db/v1/modules/maintenanceWindowModule.js-simstep-9bfbae5a-f487-40ef-86f5-04a1cc3c7662",
+ "fileName": "maintenanceWindowModule.js",
+ "wiki": "The `maintenanceWindowModule` constructs a MongoDB query to find all maintenance windows for the given `teamId`, applying pagination (skip, limit) and sorting. It executes the query and returns the results along with the total count.",
+ "cellName": "Flow: View Maintenance Windows - Data Retrieval - maintenanceWindowModule.js:L37-66",
+ "cellId": "27d9160a-4f8d-4dff-a09b-1678bc28cf2c",
+ "visible": true,
+ "startLine": 37,
+ "endLine": 66,
+ "parentCellId": "95ed6c41-ebeb-4c08-98c3-bab8b426789e",
+ "parentPath": "server/src/db/v1/modules/maintenanceWindowModule.js",
+ "simSteps": [
+ {
+ "simulationKey": "Scheduled Maintenance Windows",
+ "simStepId": "9bfbae5a-f487-40ef-86f5-04a1cc3c7662"
+ }
+ ]
+ },
+ "970e2002-7c0e-40ef-80d8-e5e6d0a60735": {
+ "path": "970e2002-7c0e-40ef-80d8-e5e6d0a60735",
+ "cellName": "Flow: View Maintenance Windows - UI Rendering - index.jsx:L195-201",
+ "cellId": "970e2002-7c0e-40ef-80d8-e5e6d0a60735",
+ "visible": true,
+ "parentCellId": "e7ec5ca9-6d25-4bf2-9695-639556f7d141"
+ },
+ "client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx-simstep-acf97639-e291-4ca6-81a6-d0487397f42a": {
+ "path": "client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx-simstep-acf97639-e291-4ca6-81a6-d0487397f42a",
+ "fileName": "index.jsx",
+ "wiki": "The client receives the data and updates its state. The `MaintenanceTable` component then renders the list of maintenance windows in a table, displaying their names, schedules, and providing actions like edit or delete.",
+ "cellName": "Flow: View Maintenance Windows - UI Rendering - index.jsx:L195-201",
+ "cellId": "970e2002-7c0e-40ef-80d8-e5e6d0a60735",
+ "visible": true,
+ "startLine": 195,
+ "endLine": 201,
+ "parentCellId": "e7ec5ca9-6d25-4bf2-9695-639556f7d141",
+ "parentPath": "client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Scheduled Maintenance Windows",
+ "simStepId": "acf97639-e291-4ca6-81a6-d0487397f42a"
+ }
+ ]
+ },
+ "23630a1f-7452-4373-a864-7ec7821137f2": {
+ "path": "23630a1f-7452-4373-a864-7ec7821137f2",
+ "cellName": "Flow: Monitor Suppression - Job Execution Start - SuperSimpleQueueHelper.js:L30-45",
+ "cellId": "23630a1f-7452-4373-a864-7ec7821137f2",
+ "visible": true,
+ "parentCellId": "04c12e9a-e025-457c-a634-5f7472a42985"
+ },
+ "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js-simstep-a47affaf-b952-46d6-9709-6120a77690e1": {
+ "path": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js-simstep-a47affaf-b952-46d6-9709-6120a77690e1",
+ "fileName": "SuperSimpleQueueHelper.js",
+ "wiki": "The `SuperSimpleQueue` processing system picks up a job to check a specific monitor. The job handler is retrieved from `SuperSimpleQueueHelper`.",
+ "cellName": "Flow: Monitor Suppression - Job Execution Start - SuperSimpleQueueHelper.js:L30-45",
+ "cellId": "23630a1f-7452-4373-a864-7ec7821137f2",
+ "visible": true,
+ "startLine": 30,
+ "endLine": 45,
+ "parentCellId": "04c12e9a-e025-457c-a634-5f7472a42985",
+ "parentPath": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js",
+ "simSteps": [
+ {
+ "simulationKey": "Scheduled Maintenance Windows",
+ "simStepId": "a47affaf-b952-46d6-9709-6120a77690e1"
+ }
+ ]
+ },
+ "fc78b5b6-7fad-4f8f-a5a3-c7c38931daba": {
+ "path": "fc78b5b6-7fad-4f8f-a5a3-c7c38931daba",
+ "cellName": "Flow: Monitor Suppression - Fetching Maintenance Windows - SuperSimpleQueueHelper.js:L80-83",
+ "cellId": "fc78b5b6-7fad-4f8f-a5a3-c7c38931daba",
+ "visible": true,
+ "parentCellId": "04c12e9a-e025-457c-a634-5f7472a42985"
+ },
+ "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js-simstep-2127e749-80b4-499a-ae14-8dc2dcc9db9b": {
+ "path": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js-simstep-2127e749-80b4-499a-ae14-8dc2dcc9db9b",
+ "fileName": "SuperSimpleQueueHelper.js",
+ "wiki": "The `isInMaintenanceWindow` function calls the database module to fetch all maintenance windows associated with the given monitor ID and team ID.",
+ "cellName": "Flow: Monitor Suppression - Fetching Maintenance Windows - SuperSimpleQueueHelper.js:L80-83",
+ "cellId": "fc78b5b6-7fad-4f8f-a5a3-c7c38931daba",
+ "visible": true,
+ "startLine": 80,
+ "endLine": 83,
+ "parentCellId": "04c12e9a-e025-457c-a634-5f7472a42985",
+ "parentPath": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js",
+ "simSteps": [
+ {
+ "simulationKey": "Scheduled Maintenance Windows",
+ "simStepId": "2127e749-80b4-499a-ae14-8dc2dcc9db9b"
+ }
+ ]
+ },
+ "0fe32416-db43-49b9-9438-3796ba1a0add": {
+ "path": "0fe32416-db43-49b9-9438-3796ba1a0add",
+ "cellName": "Flow: Monitor Suppression - Time Evaluation - SuperSimpleQueueHelper.js:L85-113",
+ "cellId": "0fe32416-db43-49b9-9438-3796ba1a0add",
+ "visible": true,
+ "parentCellId": "04c12e9a-e025-457c-a634-5f7472a42985"
+ },
+ "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js-simstep-6ba6b9d1-5fc3-454e-8744-2022b5192ed9": {
+ "path": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js-simstep-6ba6b9d1-5fc3-454e-8744-2022b5192ed9",
+ "fileName": "SuperSimpleQueueHelper.js",
+ "wiki": "The `isInMaintenanceWindow` function iterates through the returned windows. For each active window, it checks if the current time (`new Date()`) is between the `start` and `end` times.",
+ "cellName": "Flow: Monitor Suppression - Time Evaluation - SuperSimpleQueueHelper.js:L85-113",
+ "cellId": "0fe32416-db43-49b9-9438-3796ba1a0add",
+ "visible": true,
+ "startLine": 85,
+ "endLine": 113,
+ "parentCellId": "04c12e9a-e025-457c-a634-5f7472a42985",
+ "parentPath": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js",
+ "simSteps": [
+ {
+ "simulationKey": "Scheduled Maintenance Windows",
+ "simStepId": "6ba6b9d1-5fc3-454e-8744-2022b5192ed9"
+ }
+ ]
+ },
+ "01e042c3-40b4-416a-8d9e-6d6fd323d60b": {
+ "path": "01e042c3-40b4-416a-8d9e-6d6fd323d60b",
+ "cellName": "Flow: Monitor Suppression - Conditional Job Termination - SuperSimpleQueueHelper.js:L37-44",
+ "cellId": "01e042c3-40b4-416a-8d9e-6d6fd323d60b",
+ "visible": true,
+ "parentCellId": "04c12e9a-e025-457c-a634-5f7472a42985"
+ },
+ "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js-simstep-e59fe64e-f06b-4855-b631-923bb5a68540": {
+ "path": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js-simstep-e59fe64e-f06b-4855-b631-923bb5a68540",
+ "fileName": "SuperSimpleQueueHelper.js",
+ "wiki": "If the `isInMaintenanceWindow` function returns true, the `getMonitorJob` logs that the monitor is in a maintenance window and terminates its execution for this cycle, effectively suppressing the check.",
+ "cellName": "Flow: Monitor Suppression - Conditional Job Termination - SuperSimpleQueueHelper.js:L37-44",
+ "cellId": "01e042c3-40b4-416a-8d9e-6d6fd323d60b",
+ "visible": true,
+ "startLine": 37,
+ "endLine": 44,
+ "parentCellId": "04c12e9a-e025-457c-a634-5f7472a42985",
+ "parentPath": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js",
+ "simSteps": [
+ {
+ "simulationKey": "Scheduled Maintenance Windows",
+ "simStepId": "e59fe64e-f06b-4855-b631-923bb5a68540"
+ }
+ ]
+ },
+ "d3bdc6ff-875e-472e-88c8-eb10b20c526e": {
+ "path": "d3bdc6ff-875e-472e-88c8-eb10b20c526e",
+ "cellName": "Flow: Create\nMaintenance Window\n- Form\nSubmission",
+ "cellId": "d3bdc6ff-875e-472e-88c8-eb10b20c526e",
+ "visible": true,
+ "parentCellId": "6f7119ac-d65a-4fdf-a6f2-31052e0f5549"
+ },
+ "generated-edge-simstep-f881435b-3bc2-453d-b6bf-56ad3a12f120-d3bdc6ff-875e-472e-88c8-eb10b20c526e": {
+ "path": "generated-edge-simstep-f881435b-3bc2-453d-b6bf-56ad3a12f120-d3bdc6ff-875e-472e-88c8-eb10b20c526e",
+ "fileName": "index.jsx",
+ "cellName": "Flow: Create Maintenance Window - Form Submission",
+ "cellId": "d3bdc6ff-875e-472e-88c8-eb10b20c526e",
+ "visible": true,
+ "startLine": 108,
+ "endLine": 109,
+ "parentPath": "client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Scheduled Maintenance Windows",
+ "simStepId": "f881435b-3bc2-453d-b6bf-56ad3a12f120"
+ }
+ ]
+ },
+ "fd5e4447-123b-4b2d-8bce-1d1ae7cb00af": {
+ "path": "fd5e4447-123b-4b2d-8bce-1d1ae7cb00af",
+ "cellName": "Flow: Create\nMaintenance Window\n- API\nCall",
+ "cellId": "fd5e4447-123b-4b2d-8bce-1d1ae7cb00af",
+ "visible": true
+ },
+ "generated-edge-simstep-0660b48b-139f-4ecf-b7d9-f12f544e0ab5-fd5e4447-123b-4b2d-8bce-1d1ae7cb00af": {
+ "path": "generated-edge-simstep-0660b48b-139f-4ecf-b7d9-f12f544e0ab5-fd5e4447-123b-4b2d-8bce-1d1ae7cb00af",
+ "fileName": "useMaintenanceActions.jsx",
+ "cellName": "Flow: Create Maintenance Window - API Call",
+ "cellId": "fd5e4447-123b-4b2d-8bce-1d1ae7cb00af",
+ "visible": true,
+ "startLine": 762,
+ "endLine": 766,
+ "parentPath": "client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Scheduled Maintenance Windows",
+ "simStepId": "0660b48b-139f-4ecf-b7d9-f12f544e0ab5"
+ }
+ ]
+ },
+ "e2588541-3ffc-47c6-8f8a-41d30ea7fc72": {
+ "path": "e2588541-3ffc-47c6-8f8a-41d30ea7fc72",
+ "cellName": "Flow: Create\nMaintenance Window\n- Controller\nInvocation",
+ "cellId": "e2588541-3ffc-47c6-8f8a-41d30ea7fc72",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "generated-edge-simstep-e4cc5ce8-6da9-4281-9204-29c061ab13ce-e2588541-3ffc-47c6-8f8a-41d30ea7fc72": {
+ "path": "generated-edge-simstep-e4cc5ce8-6da9-4281-9204-29c061ab13ce-e2588541-3ffc-47c6-8f8a-41d30ea7fc72",
+ "fileName": "routes.js",
+ "cellName": "Flow: Create Maintenance Window - Controller Invocation",
+ "cellId": "e2588541-3ffc-47c6-8f8a-41d30ea7fc72",
+ "visible": true,
+ "startLine": 10,
+ "endLine": 10,
+ "parentPath": "server/src/config/routes.js",
+ "simSteps": [
+ {
+ "simulationKey": "Scheduled Maintenance Windows",
+ "simStepId": "e4cc5ce8-6da9-4281-9204-29c061ab13ce"
+ }
+ ]
+ },
+ "e1d59cf6-0d96-4e0a-ad17-2a7984a9157f": {
+ "path": "e1d59cf6-0d96-4e0a-ad17-2a7984a9157f",
+ "cellName": "Flow: Create\nMaintenance Window\n- Service\nLayer Call",
+ "cellId": "e1d59cf6-0d96-4e0a-ad17-2a7984a9157f",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "generated-edge-simstep-c3d1a95c-e93d-499a-8830-a6d870902414-e1d59cf6-0d96-4e0a-ad17-2a7984a9157f": {
+ "path": "generated-edge-simstep-c3d1a95c-e93d-499a-8830-a6d870902414-e1d59cf6-0d96-4e0a-ad17-2a7984a9157f",
+ "fileName": "maintenanceWindowController.js",
+ "cellName": "Flow: Create Maintenance Window - Service Layer Call",
+ "cellId": "e1d59cf6-0d96-4e0a-ad17-2a7984a9157f",
+ "visible": true,
+ "startLine": 35,
+ "endLine": 35,
+ "parentPath": "server/src/controllers/v1/maintenanceWindowController.js",
+ "simSteps": [
+ {
+ "simulationKey": "Scheduled Maintenance Windows",
+ "simStepId": "c3d1a95c-e93d-499a-8830-a6d870902414"
+ }
+ ]
+ },
+ "12d0ab8a-392f-48fa-9efa-06ffe1f3a252": {
+ "path": "12d0ab8a-392f-48fa-9efa-06ffe1f3a252",
+ "cellName": "Flow: Create\nMaintenance Window\n- Database\nModule Call",
+ "cellId": "12d0ab8a-392f-48fa-9efa-06ffe1f3a252",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "generated-edge-simstep-80aed28e-68b6-47bf-8bc9-ededb4824066-12d0ab8a-392f-48fa-9efa-06ffe1f3a252": {
+ "path": "generated-edge-simstep-80aed28e-68b6-47bf-8bc9-ededb4824066-12d0ab8a-392f-48fa-9efa-06ffe1f3a252",
+ "fileName": "maintenanceWindowService.js",
+ "cellName": "Flow: Create Maintenance Window - Database Module Call",
+ "cellId": "12d0ab8a-392f-48fa-9efa-06ffe1f3a252",
+ "visible": true,
+ "startLine": 28,
+ "endLine": 37,
+ "parentPath": "server/src/service/v1/business/maintenanceWindowService.js",
+ "simSteps": [
+ {
+ "simulationKey": "Scheduled Maintenance Windows",
+ "simStepId": "80aed28e-68b6-47bf-8bc9-ededb4824066"
+ }
+ ]
+ },
+ "fcf4eef4-394c-4872-9124-f7a0b32cdc3b": {
+ "path": "fcf4eef4-394c-4872-9124-f7a0b32cdc3b",
+ "cellName": "Flow: Create\nMaintenance Window\n- API\nResponse",
+ "cellId": "fcf4eef4-394c-4872-9124-f7a0b32cdc3b",
+ "visible": true
+ },
+ "generated-edge-simstep-69fdc2a9-837f-466c-9562-a12319019580-fcf4eef4-394c-4872-9124-f7a0b32cdc3b": {
+ "path": "generated-edge-simstep-69fdc2a9-837f-466c-9562-a12319019580-fcf4eef4-394c-4872-9124-f7a0b32cdc3b",
+ "fileName": "maintenanceWindowModule.js",
+ "cellName": "Flow: Create Maintenance Window - API Response",
+ "cellId": "fcf4eef4-394c-4872-9124-f7a0b32cdc3b",
+ "visible": true,
+ "startLine": 37,
+ "endLine": 39,
+ "parentPath": "server/src/db/v1/modules/maintenanceWindowModule.js",
+ "simSteps": [
+ {
+ "simulationKey": "Scheduled Maintenance Windows",
+ "simStepId": "69fdc2a9-837f-466c-9562-a12319019580"
+ }
+ ]
+ },
+ "7f921125-500f-4902-abc3-936ecac033d8": {
+ "path": "7f921125-500f-4902-abc3-936ecac033d8",
+ "cellName": "Flow: View\nMaintenance Windows\n- API\nCall",
+ "cellId": "7f921125-500f-4902-abc3-936ecac033d8",
+ "visible": true
+ },
+ "generated-edge-simstep-7e20cd6c-d24e-4540-86d6-d6a9fa087314-7f921125-500f-4902-abc3-936ecac033d8": {
+ "path": "generated-edge-simstep-7e20cd6c-d24e-4540-86d6-d6a9fa087314-7f921125-500f-4902-abc3-936ecac033d8",
+ "fileName": "index.jsx",
+ "cellName": "Flow: View Maintenance Windows - API Call",
+ "cellId": "7f921125-500f-4902-abc3-936ecac033d8",
+ "visible": true,
+ "startLine": 824,
+ "endLine": 824,
+ "parentPath": "client/src/Pages/v1/Maintenance/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Scheduled Maintenance Windows",
+ "simStepId": "7e20cd6c-d24e-4540-86d6-d6a9fa087314"
+ }
+ ]
+ },
+ "c3127c74-43f1-42d7-8da6-b0a494c3658f": {
+ "path": "c3127c74-43f1-42d7-8da6-b0a494c3658f",
+ "cellName": "Flow: View\nMaintenance Windows\n- Database\nQuery",
+ "cellId": "c3127c74-43f1-42d7-8da6-b0a494c3658f",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "generated-edge-simstep-feb8bd4f-339f-4132-a709-89c63c4efa9c-c3127c74-43f1-42d7-8da6-b0a494c3658f": {
+ "path": "generated-edge-simstep-feb8bd4f-339f-4132-a709-89c63c4efa9c-c3127c74-43f1-42d7-8da6-b0a494c3658f",
+ "fileName": "maintenanceWindowController.js",
+ "cellName": "Flow: View Maintenance Windows - Database Query",
+ "cellId": "c3127c74-43f1-42d7-8da6-b0a494c3658f",
+ "visible": true,
+ "startLine": 48,
+ "endLine": 48,
+ "parentPath": "server/src/controllers/v1/maintenanceWindowController.js",
+ "simSteps": [
+ {
+ "simulationKey": "Scheduled Maintenance Windows",
+ "simStepId": "feb8bd4f-339f-4132-a709-89c63c4efa9c"
+ }
+ ]
+ },
+ "6c9979cd-1cc1-4227-b535-2ec3a10ca27e": {
+ "path": "6c9979cd-1cc1-4227-b535-2ec3a10ca27e",
+ "cellName": "Flow: View\nMaintenance Windows\n- Data\nReturn to\nFrontend",
+ "cellId": "6c9979cd-1cc1-4227-b535-2ec3a10ca27e",
+ "visible": true
+ },
+ "generated-edge-simstep-eda86dca-dea6-4671-a079-7d212f4f18c4-6c9979cd-1cc1-4227-b535-2ec3a10ca27e": {
+ "path": "generated-edge-simstep-eda86dca-dea6-4671-a079-7d212f4f18c4-6c9979cd-1cc1-4227-b535-2ec3a10ca27e",
+ "fileName": "maintenanceWindowModule.js",
+ "cellName": "Flow: View Maintenance Windows - Data Return to Frontend",
+ "cellId": "6c9979cd-1cc1-4227-b535-2ec3a10ca27e",
+ "visible": true,
+ "startLine": 77,
+ "endLine": 80,
+ "parentPath": "server/src/db/v1/modules/maintenanceWindowModule.js",
+ "simSteps": [
+ {
+ "simulationKey": "Scheduled Maintenance Windows",
+ "simStepId": "eda86dca-dea6-4671-a079-7d212f4f18c4"
+ }
+ ]
+ },
+ "c99291bb-7316-440e-9a7f-323ee39e26b8": {
+ "path": "c99291bb-7316-440e-9a7f-323ee39e26b8",
+ "cellName": "Flow: Monitor\nSuppression -\nMaintenance Check\nCall",
+ "cellId": "c99291bb-7316-440e-9a7f-323ee39e26b8",
+ "visible": true,
+ "parentCellId": "04c12e9a-e025-457c-a634-5f7472a42985"
+ },
+ "generated-edge-simstep-87583096-f1ec-4160-ae52-5238f32ed0ab-c99291bb-7316-440e-9a7f-323ee39e26b8": {
+ "path": "generated-edge-simstep-87583096-f1ec-4160-ae52-5238f32ed0ab-c99291bb-7316-440e-9a7f-323ee39e26b8",
+ "fileName": "SuperSimpleQueueHelper.js",
+ "cellName": "Flow: Monitor Suppression - Maintenance Check Call",
+ "cellId": "c99291bb-7316-440e-9a7f-323ee39e26b8",
+ "visible": true,
+ "startLine": 36,
+ "endLine": 36,
+ "parentPath": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js",
+ "simSteps": [
+ {
+ "simulationKey": "Scheduled Maintenance Windows",
+ "simStepId": "87583096-f1ec-4160-ae52-5238f32ed0ab"
+ }
+ ]
+ },
+ "8a13e585-2310-420b-9e92-211b6d65f201": {
+ "path": "8a13e585-2310-420b-9e92-211b6d65f201",
+ "cellName": "Flow: Monitor\nSuppression -\nDatabase Result\nReturn",
+ "cellId": "8a13e585-2310-420b-9e92-211b6d65f201",
+ "visible": true,
+ "parentCellId": "04c12e9a-e025-457c-a634-5f7472a42985"
+ },
+ "generated-edge-simstep-e6542e14-556d-482a-8c35-f7b03997be49-8a13e585-2310-420b-9e92-211b6d65f201": {
+ "path": "generated-edge-simstep-e6542e14-556d-482a-8c35-f7b03997be49-8a13e585-2310-420b-9e92-211b6d65f201",
+ "fileName": "SuperSimpleQueueHelper.js",
+ "cellName": "Flow: Monitor Suppression - Database Result Return",
+ "cellId": "8a13e585-2310-420b-9e92-211b6d65f201",
+ "visible": true,
+ "startLine": 69,
+ "endLine": 76,
+ "parentPath": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js",
+ "simSteps": [
+ {
+ "simulationKey": "Scheduled Maintenance Windows",
+ "simStepId": "e6542e14-556d-482a-8c35-f7b03997be49"
+ }
+ ]
+ },
+ "40f4d9df-4c0f-4384-901c-8d7117e3b538": {
+ "path": "40f4d9df-4c0f-4384-901c-8d7117e3b538",
+ "cellName": "Flow: Monitor\nSuppression -\nReturn Suppression\nFlag",
+ "cellId": "40f4d9df-4c0f-4384-901c-8d7117e3b538",
+ "visible": true,
+ "parentCellId": "04c12e9a-e025-457c-a634-5f7472a42985"
+ },
+ "generated-edge-simstep-a715d98f-60bf-4f32-aae6-21ee4e09f4da-40f4d9df-4c0f-4384-901c-8d7117e3b538": {
+ "path": "generated-edge-simstep-a715d98f-60bf-4f32-aae6-21ee4e09f4da-40f4d9df-4c0f-4384-901c-8d7117e3b538",
+ "fileName": "SuperSimpleQueueHelper.js",
+ "cellName": "Flow: Monitor Suppression - Return Suppression Flag",
+ "cellId": "40f4d9df-4c0f-4384-901c-8d7117e3b538",
+ "visible": true,
+ "startLine": 114,
+ "endLine": 114,
+ "parentPath": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js",
+ "simSteps": [
+ {
+ "simulationKey": "Scheduled Maintenance Windows",
+ "simStepId": "a715d98f-60bf-4f32-aae6-21ee4e09f4da"
+ }
+ ]
+ },
+ "a968db5d-8f6d-4fd7-9df0-8b332b521703": {
+ "path": "a968db5d-8f6d-4fd7-9df0-8b332b521703",
+ "cellName": "middleware",
+ "cellId": "a968db5d-8f6d-4fd7-9df0-8b332b521703",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "09082f73-e215-465f-9206-0a963fb4c7cb": {
+ "path": "09082f73-e215-465f-9206-0a963fb4c7cb",
+ "cellName": "v1",
+ "cellId": "09082f73-e215-465f-9206-0a963fb4c7cb",
+ "visible": true,
+ "parentCellId": "a968db5d-8f6d-4fd7-9df0-8b332b521703"
+ },
+ "3cad7666-0d1a-4e31-b6ca-70d9c3a46b19": {
+ "path": "3cad7666-0d1a-4e31-b6ca-70d9c3a46b19",
+ "cellName": "Account",
+ "cellId": "3cad7666-0d1a-4e31-b6ca-70d9c3a46b19",
+ "visible": true,
+ "parentCellId": "1a083b4a-787c-4eb4-8029-6700de987679"
+ },
+ "9edafb8a-3661-4a7e-8510-a47f3cbc7564": {
+ "path": "9edafb8a-3661-4a7e-8510-a47f3cbc7564",
+ "cellName": "inviteRoute.js",
+ "cellId": "9edafb8a-3661-4a7e-8510-a47f3cbc7564",
+ "visible": true,
+ "parentCellId": "d3375a20-2776-4771-b806-649bbeb541bd"
+ },
+ "001fa2e4-eadd-4474-b2ec-79d7ea43465f": {
+ "path": "001fa2e4-eadd-4474-b2ec-79d7ea43465f",
+ "cellName": "isAllowed.js",
+ "cellId": "001fa2e4-eadd-4474-b2ec-79d7ea43465f",
+ "visible": true,
+ "parentCellId": "09082f73-e215-465f-9206-0a963fb4c7cb"
+ },
+ "6a186805-9d50-4bc3-9d56-e627a7ec2eb8": {
+ "path": "6a186805-9d50-4bc3-9d56-e627a7ec2eb8",
+ "cellName": "inviteController.js",
+ "cellId": "6a186805-9d50-4bc3-9d56-e627a7ec2eb8",
+ "visible": true,
+ "parentCellId": "fc591f7a-d4ac-4565-949d-a87a58a79bbc"
+ },
+ "33960806-1b9e-4d61-a163-e4bebf82d87a": {
+ "path": "33960806-1b9e-4d61-a163-e4bebf82d87a",
+ "cellName": "index.jsx",
+ "cellId": "33960806-1b9e-4d61-a163-e4bebf82d87a",
+ "visible": true,
+ "parentCellId": "3cad7666-0d1a-4e31-b6ca-70d9c3a46b19"
+ },
+ "425a4c15-043f-4d05-8832-da00e18f97b9": {
+ "path": "425a4c15-043f-4d05-8832-da00e18f97b9",
+ "cellName": "components",
+ "cellId": "425a4c15-043f-4d05-8832-da00e18f97b9",
+ "visible": true,
+ "parentCellId": "3cad7666-0d1a-4e31-b6ca-70d9c3a46b19"
+ },
+ "570278b7-9b72-4299-9fd9-46b253b48e9b": {
+ "path": "570278b7-9b72-4299-9fd9-46b253b48e9b",
+ "cellName": "inviteService.js",
+ "cellId": "570278b7-9b72-4299-9fd9-46b253b48e9b",
+ "visible": true,
+ "parentCellId": "87d3b101-9443-4ed4-9c4f-f759d41677eb"
+ },
+ "08853f8a-f596-4df0-9190-dad04f54caef": {
+ "path": "08853f8a-f596-4df0-9190-dad04f54caef",
+ "cellName": "inviteModule.js",
+ "cellId": "08853f8a-f596-4df0-9190-dad04f54caef",
+ "visible": true,
+ "parentCellId": "82cb3dbe-c922-438a-a912-255376f4b380"
+ },
+ "1145f351-2257-411d-86f7-30bf8c26ad68": {
+ "path": "1145f351-2257-411d-86f7-30bf8c26ad68",
+ "cellName": "TeamPanel.jsx",
+ "cellId": "1145f351-2257-411d-86f7-30bf8c26ad68",
+ "visible": true,
+ "parentCellId": "425a4c15-043f-4d05-8832-da00e18f97b9"
+ },
+ "3de2d413-5e9b-49f4-9409-74be32c52e0f": {
+ "path": "3de2d413-5e9b-49f4-9409-74be32c52e0f",
+ "cellName": "Client: Conditional UI Rendering Based on Role - index.jsx:L37-41",
+ "cellId": "3de2d413-5e9b-49f4-9409-74be32c52e0f",
+ "visible": true,
+ "parentCellId": "33960806-1b9e-4d61-a163-e4bebf82d87a"
+ },
+ "client/src/Pages/v1/Account/index.jsx-simstep-2d5a952a-ce62-4948-9309-96b28ad74e59": {
+ "path": "client/src/Pages/v1/Account/index.jsx-simstep-2d5a952a-ce62-4948-9309-96b28ad74e59",
+ "fileName": "index.jsx",
+ "wiki": "A user with 'superadmin' or 'admin' role navigates to the Account page. The application checks the user's role stored in the Redux state to conditionally render the 'Team' management tab. Non-admin users will not see this option.",
+ "cellName": "Client: Conditional UI Rendering Based on Role - index.jsx:L37-41",
+ "cellId": "3de2d413-5e9b-49f4-9409-74be32c52e0f",
+ "visible": true,
+ "startLine": 37,
+ "endLine": 41,
+ "parentCellId": "33960806-1b9e-4d61-a163-e4bebf82d87a",
+ "parentPath": "client/src/Pages/v1/Account/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "User and Team Management with Role-Based Access",
+ "simStepId": "2d5a952a-ce62-4948-9309-96b28ad74e59"
+ }
+ ]
+ },
+ "b27f1bba-c655-40c6-a1dc-71e78c590c92": {
+ "path": "b27f1bba-c655-40c6-a1dc-71e78c590c92",
+ "cellName": "Client: Initiate User Invitation - TeamPanel.jsx:L348-354",
+ "cellId": "b27f1bba-c655-40c6-a1dc-71e78c590c92",
+ "visible": true,
+ "parentCellId": "1145f351-2257-411d-86f7-30bf8c26ad68"
+ },
+ "client/src/Pages/v1/Account/components/TeamPanel.jsx-simstep-eebaca25-d588-49c4-a35c-9abc82fde580": {
+ "path": "client/src/Pages/v1/Account/components/TeamPanel.jsx-simstep-eebaca25-d588-49c4-a35c-9abc82fde580",
+ "fileName": "TeamPanel.jsx",
+ "wiki": "In the `TeamPanel`, the admin clicks the 'Add Team Member' button, then selects 'Invite a team member'. A dialog opens, prompting for the new user's email and role. After filling the form and clicking 'E-mail token', the `handleInviteMember` function is triggered to start the invitation process.",
+ "cellName": "Client: Initiate User Invitation - TeamPanel.jsx:L348-354",
+ "cellId": "b27f1bba-c655-40c6-a1dc-71e78c590c92",
+ "visible": true,
+ "startLine": 348,
+ "endLine": 354,
+ "parentCellId": "1145f351-2257-411d-86f7-30bf8c26ad68",
+ "parentPath": "client/src/Pages/v1/Account/components/TeamPanel.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "User and Team Management with Role-Based Access",
+ "simStepId": "eebaca25-d588-49c4-a35c-9abc82fde580"
+ }
+ ]
+ },
+ "8b3cb6c2-ed57-4510-be15-decb8e3c89e1": {
+ "path": "8b3cb6c2-ed57-4510-be15-decb8e3c89e1",
+ "cellName": "Server: Role-Based Access Control Middleware - isAllowed.js:L39-44",
+ "cellId": "8b3cb6c2-ed57-4510-be15-decb8e3c89e1",
+ "visible": true,
+ "parentCellId": "001fa2e4-eadd-4474-b2ec-79d7ea43465f"
+ },
+ "server/src/middleware/v1/isAllowed.js-simstep-50dd7316-7d01-43f7-9036-d97d51a9c178": {
+ "path": "server/src/middleware/v1/isAllowed.js-simstep-50dd7316-7d01-43f7-9036-d97d51a9c178",
+ "fileName": "isAllowed.js",
+ "wiki": "The server receives the request. Before reaching the main controller, the `isAllowed` middleware intercepts the request. It verifies the JWT from the request header and checks if the user's role (e.g., 'superadmin') is present in the list of allowed roles for this route (`['admin', 'superadmin']`).",
+ "cellName": "Server: Role-Based Access Control Middleware - isAllowed.js:L39-44",
+ "cellId": "8b3cb6c2-ed57-4510-be15-decb8e3c89e1",
+ "visible": true,
+ "startLine": 39,
+ "endLine": 44,
+ "parentCellId": "001fa2e4-eadd-4474-b2ec-79d7ea43465f",
+ "parentPath": "server/src/middleware/v1/isAllowed.js",
+ "simSteps": [
+ {
+ "simulationKey": "User and Team Management with Role-Based Access",
+ "simStepId": "50dd7316-7d01-43f7-9036-d97d51a9c178"
+ }
+ ]
+ },
+ "ae858158-8db6-4917-8fda-0966a953f6ed": {
+ "path": "ae858158-8db6-4917-8fda-0966a953f6ed",
+ "cellName": "Server: Process Invitation in Service Layer - inviteService.js:L24-28",
+ "cellId": "ae858158-8db6-4917-8fda-0966a953f6ed",
+ "visible": true,
+ "parentCellId": "570278b7-9b72-4299-9fd9-46b253b48e9b"
+ },
+ "server/src/service/v1/business/inviteService.js-simstep-55fe9963-30d2-47f8-859f-b485a77064c4": {
+ "path": "server/src/service/v1/business/inviteService.js-simstep-55fe9963-30d2-47f8-859f-b485a77064c4",
+ "fileName": "inviteService.js",
+ "wiki": "The `inviteController` calls the `inviteService`. The service layer handles the business logic: it first requests an invitation token from the database module and then instructs the email service to send the invitation.",
+ "cellName": "Server: Process Invitation in Service Layer - inviteService.js:L24-28",
+ "cellId": "ae858158-8db6-4917-8fda-0966a953f6ed",
+ "visible": true,
+ "startLine": 24,
+ "endLine": 28,
+ "parentCellId": "570278b7-9b72-4299-9fd9-46b253b48e9b",
+ "parentPath": "server/src/service/v1/business/inviteService.js",
+ "simSteps": [
+ {
+ "simulationKey": "User and Team Management with Role-Based Access",
+ "simStepId": "55fe9963-30d2-47f8-859f-b485a77064c4"
+ }
+ ]
+ },
+ "a7be2d0b-0227-4228-a0a6-074f7324fd89": {
+ "path": "a7be2d0b-0227-4228-a0a6-074f7324fd89",
+ "cellName": "Server: Dispatch Invitation Email - inviteService.js:L26-28",
+ "cellId": "a7be2d0b-0227-4228-a0a6-074f7324fd89",
+ "visible": true,
+ "parentCellId": "570278b7-9b72-4299-9fd9-46b253b48e9b"
+ },
+ "server/src/service/v1/business/inviteService.js-simstep-7d1f86f2-c8d5-48fb-a8c1-b337dc56271f": {
+ "path": "server/src/service/v1/business/inviteService.js-simstep-7d1f86f2-c8d5-48fb-a8c1-b337dc56271f",
+ "fileName": "inviteService.js",
+ "wiki": "After successfully storing the token, the `inviteService` uses the `emailService` to compose and send an email to the new user. The email contains a unique registration link including the generated token.",
+ "cellName": "Server: Dispatch Invitation Email - inviteService.js:L26-28",
+ "cellId": "a7be2d0b-0227-4228-a0a6-074f7324fd89",
+ "visible": true,
+ "startLine": 26,
+ "endLine": 28,
+ "parentCellId": "570278b7-9b72-4299-9fd9-46b253b48e9b",
+ "parentPath": "server/src/service/v1/business/inviteService.js",
+ "simSteps": [
+ {
+ "simulationKey": "User and Team Management with Role-Based Access",
+ "simStepId": "7d1f86f2-c8d5-48fb-a8c1-b337dc56271f"
+ }
+ ]
+ },
+ "02d87257-883c-4822-a421-823c983532ec": {
+ "path": "02d87257-883c-4822-a421-823c983532ec",
+ "cellName": "Client: Display Success Feedback - TeamPanel.jsx:L145-149",
+ "cellId": "02d87257-883c-4822-a421-823c983532ec",
+ "visible": true,
+ "parentCellId": "1145f351-2257-411d-86f7-30bf8c26ad68"
+ },
+ "client/src/Pages/v1/Account/components/TeamPanel.jsx-simstep-700f35e0-b39d-417c-b0a8-0612feade977": {
+ "path": "client/src/Pages/v1/Account/components/TeamPanel.jsx-simstep-700f35e0-b39d-417c-b0a8-0612feade977",
+ "fileName": "TeamPanel.jsx",
+ "wiki": "The client application receives the successful API response. It then closes the invitation dialog and displays a toast notification to the admin, confirming that the invitation has been sent.",
+ "cellName": "Client: Display Success Feedback - TeamPanel.jsx:L145-149",
+ "cellId": "02d87257-883c-4822-a421-823c983532ec",
+ "visible": true,
+ "startLine": 145,
+ "endLine": 149,
+ "parentCellId": "1145f351-2257-411d-86f7-30bf8c26ad68",
+ "parentPath": "client/src/Pages/v1/Account/components/TeamPanel.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "User and Team Management with Role-Based Access",
+ "simStepId": "700f35e0-b39d-417c-b0a8-0612feade977"
+ }
+ ]
+ },
+ "c441312f-f3c6-48c6-b1b9-7c12749d91bb": {
+ "path": "c441312f-f3c6-48c6-b1b9-7c12749d91bb",
+ "cellName": "Client: User\nNavigates to\nTeam Panel",
+ "cellId": "c441312f-f3c6-48c6-b1b9-7c12749d91bb",
+ "visible": true,
+ "parentCellId": "3cad7666-0d1a-4e31-b6ca-70d9c3a46b19"
+ },
+ "generated-edge-simstep-10f80ada-2d0a-4e98-bd5d-057e7d4fe6de-c441312f-f3c6-48c6-b1b9-7c12749d91bb": {
+ "path": "generated-edge-simstep-10f80ada-2d0a-4e98-bd5d-057e7d4fe6de-c441312f-f3c6-48c6-b1b9-7c12749d91bb",
+ "fileName": "index.jsx",
+ "cellName": "Client: User Navigates to Team Panel",
+ "cellId": "c441312f-f3c6-48c6-b1b9-7c12749d91bb",
+ "visible": true,
+ "startLine": 98,
+ "endLine": 98,
+ "parentPath": "client/src/Pages/v1/Account/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "User and Team Management with Role-Based Access",
+ "simStepId": "10f80ada-2d0a-4e98-bd5d-057e7d4fe6de"
+ }
+ ]
+ },
+ "3530244f-2689-444e-bcbf-611db2e3c000": {
+ "path": "3530244f-2689-444e-bcbf-611db2e3c000",
+ "cellName": "API Call:\nSend Invitation\nRequest",
+ "cellId": "3530244f-2689-444e-bcbf-611db2e3c000",
+ "visible": true
+ },
+ "generated-edge-simstep-16d87608-4458-430c-b459-cd9b1d413fa6-3530244f-2689-444e-bcbf-611db2e3c000": {
+ "path": "generated-edge-simstep-16d87608-4458-430c-b459-cd9b1d413fa6-3530244f-2689-444e-bcbf-611db2e3c000",
+ "fileName": "TeamPanel.jsx",
+ "cellName": "API Call: Send Invitation Request",
+ "cellId": "3530244f-2689-444e-bcbf-611db2e3c000",
+ "visible": true,
+ "startLine": 14,
+ "endLine": 14,
+ "parentPath": "client/src/Pages/v1/Account/components/TeamPanel.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "User and Team Management with Role-Based Access",
+ "simStepId": "16d87608-4458-430c-b459-cd9b1d413fa6"
+ }
+ ]
+ },
+ "2ee1c4e8-90fb-4470-ad9f-6ffed5420853": {
+ "path": "2ee1c4e8-90fb-4470-ad9f-6ffed5420853",
+ "cellName": "Server: Forward\nRequest to\nController",
+ "cellId": "2ee1c4e8-90fb-4470-ad9f-6ffed5420853",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "generated-edge-simstep-6c024d4c-178b-45fc-8bca-4fc6c00549a4-2ee1c4e8-90fb-4470-ad9f-6ffed5420853": {
+ "path": "generated-edge-simstep-6c024d4c-178b-45fc-8bca-4fc6c00549a4-2ee1c4e8-90fb-4470-ad9f-6ffed5420853",
+ "fileName": "isAllowed.js",
+ "cellName": "Server: Forward Request to Controller",
+ "cellId": "2ee1c4e8-90fb-4470-ad9f-6ffed5420853",
+ "visible": true,
+ "startLine": 61,
+ "endLine": 68,
+ "parentPath": "server/src/middleware/v1/isAllowed.js",
+ "simSteps": [
+ {
+ "simulationKey": "User and Team Management with Role-Based Access",
+ "simStepId": "6c024d4c-178b-45fc-8bca-4fc6c00549a4"
+ }
+ ]
+ },
+ "bdd52e6f-ddf7-4966-b3af-957f9553082f": {
+ "path": "bdd52e6f-ddf7-4966-b3af-957f9553082f",
+ "cellName": "Database Query:\nCreate and\nStore Invite\nToken",
+ "cellId": "bdd52e6f-ddf7-4966-b3af-957f9553082f",
+ "visible": true,
+ "parentCellId": "570278b7-9b72-4299-9fd9-46b253b48e9b"
+ },
+ "generated-edge-simstep-c796f8ad-d548-45f3-8d62-67f59c6c1aa4-bdd52e6f-ddf7-4966-b3af-957f9553082f": {
+ "path": "generated-edge-simstep-c796f8ad-d548-45f3-8d62-67f59c6c1aa4-bdd52e6f-ddf7-4966-b3af-957f9553082f",
+ "fileName": "inviteService.js",
+ "cellName": "Database Query: Create and Store Invite Token",
+ "cellId": "bdd52e6f-ddf7-4966-b3af-957f9553082f",
+ "visible": true,
+ "startLine": 23,
+ "endLine": 26,
+ "parentPath": "server/src/service/v1/business/inviteService.js",
+ "simSteps": [
+ {
+ "simulationKey": "User and Team Management with Role-Based Access",
+ "simStepId": "c796f8ad-d548-45f3-8d62-67f59c6c1aa4"
+ }
+ ]
+ },
+ "52732a37-82b8-40a7-b12d-dc1b82380b2d": {
+ "path": "52732a37-82b8-40a7-b12d-dc1b82380b2d",
+ "cellName": "API Response:\nSend Confirmation\nto Client",
+ "cellId": "52732a37-82b8-40a7-b12d-dc1b82380b2d",
+ "visible": true
+ },
+ "generated-edge-simstep-6ba5094d-d1e6-4ec3-8917-28cdb30f2ef9-52732a37-82b8-40a7-b12d-dc1b82380b2d": {
+ "path": "generated-edge-simstep-6ba5094d-d1e6-4ec3-8917-28cdb30f2ef9-52732a37-82b8-40a7-b12d-dc1b82380b2d",
+ "fileName": "inviteService.js",
+ "cellName": "API Response: Send Confirmation to Client",
+ "cellId": "52732a37-82b8-40a7-b12d-dc1b82380b2d",
+ "visible": true,
+ "startLine": 69,
+ "endLine": 72,
+ "parentPath": "server/src/service/v1/business/inviteService.js",
+ "simSteps": [
+ {
+ "simulationKey": "User and Team Management with Role-Based Access",
+ "simStepId": "6ba5094d-d1e6-4ec3-8917-28cdb30f2ef9"
+ }
+ ]
+ },
+ "56dae13e-5735-4089-a9a4-e18be315aff7": {
+ "path": "56dae13e-5735-4089-a9a4-e18be315aff7",
+ "cellName": "BulkImport",
+ "cellId": "56dae13e-5735-4089-a9a4-e18be315aff7",
+ "visible": true,
+ "parentCellId": "7090fcdb-09be-4c85-86fd-e73553bbb2ff"
+ },
+ "29b6ad3c-9538-47bc-8796-aab942dc4b71": {
+ "path": "29b6ad3c-9538-47bc-8796-aab942dc4b71",
+ "cellName": "index.jsx",
+ "cellId": "29b6ad3c-9538-47bc-8796-aab942dc4b71",
+ "visible": true,
+ "parentCellId": "56dae13e-5735-4089-a9a4-e18be315aff7"
+ },
+ "cd84476a-1c8b-4781-811f-58fb9944365b": {
+ "path": "cd84476a-1c8b-4781-811f-58fb9944365b",
+ "cellName": "Upload.jsx",
+ "cellId": "cd84476a-1c8b-4781-811f-58fb9944365b",
+ "visible": true,
+ "parentCellId": "56dae13e-5735-4089-a9a4-e18be315aff7"
+ },
+ "c4caeba8-ed9a-4cbc-9888-38cc8ca55362": {
+ "path": "c4caeba8-ed9a-4cbc-9888-38cc8ca55362",
+ "cellName": "Flow 1: CSV Import - Navigate to Bulk Import Page - index.jsx:L88-91",
+ "cellId": "c4caeba8-ed9a-4cbc-9888-38cc8ca55362",
+ "visible": true,
+ "parentCellId": "c4d7e7dd-75b1-44f1-a34a-6ed290a8e562"
+ },
+ "client/src/Routes/index.jsx-simstep-a29da01e-8c5a-4e6d-8d3c-e49af528e4fa": {
+ "path": "client/src/Routes/index.jsx-simstep-a29da01e-8c5a-4e6d-8d3c-e49af528e4fa",
+ "fileName": "index.jsx",
+ "wiki": "The user navigates to the \"/uptime/bulk-import\" URL, which is routed to the BulkImport component for rendering the UI.",
+ "cellName": "Flow 1: CSV Import - Navigate to Bulk Import Page - index.jsx:L88-91",
+ "cellId": "c4caeba8-ed9a-4cbc-9888-38cc8ca55362",
+ "visible": true,
+ "startLine": 88,
+ "endLine": 91,
+ "parentCellId": "c4d7e7dd-75b1-44f1-a34a-6ed290a8e562",
+ "parentPath": "client/src/Routes/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Bulk Monitor Management",
+ "simStepId": "a29da01e-8c5a-4e6d-8d3c-e49af528e4fa"
+ }
+ ]
+ },
+ "6f1948fa-e84a-4f8e-8f3b-8f24c511b3d8": {
+ "path": "6f1948fa-e84a-4f8e-8f3b-8f24c511b3d8",
+ "cellName": "Flow 1: CSV Import - User Selects File - Upload.jsx:L23-31",
+ "cellId": "6f1948fa-e84a-4f8e-8f3b-8f24c511b3d8",
+ "visible": true,
+ "parentCellId": "cd84476a-1c8b-4781-811f-58fb9944365b"
+ },
+ "client/src/Pages/v1/Uptime/BulkImport/Upload.jsx-simstep-bbd04d55-fc6a-4ba8-bcde-5a4dc263d6eb": {
+ "path": "client/src/Pages/v1/Uptime/BulkImport/Upload.jsx-simstep-bbd04d55-fc6a-4ba8-bcde-5a4dc263d6eb",
+ "fileName": "Upload.jsx",
+ "wiki": "The user clicks the 'Select File' button, triggering the file input. When a file is chosen, the `handleFileChange` function validates that it's a CSV file and updates the component's state.",
+ "cellName": "Flow 1: CSV Import - User Selects File - Upload.jsx:L23-31",
+ "cellId": "6f1948fa-e84a-4f8e-8f3b-8f24c511b3d8",
+ "visible": true,
+ "startLine": 23,
+ "endLine": 31,
+ "parentCellId": "cd84476a-1c8b-4781-811f-58fb9944365b",
+ "parentPath": "client/src/Pages/v1/Uptime/BulkImport/Upload.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Bulk Monitor Management",
+ "simStepId": "bbd04d55-fc6a-4ba8-bcde-5a4dc263d6eb"
+ }
+ ]
+ },
+ "dd2f9356-fad3-47d4-81d5-a5e038298e2b": {
+ "path": "dd2f9356-fad3-47d4-81d5-a5e038298e2b",
+ "cellName": "Flow 1: CSV Import - Submit Upload - index.jsx:L32-45",
+ "cellId": "dd2f9356-fad3-47d4-81d5-a5e038298e2b",
+ "visible": true,
+ "parentCellId": "29b6ad3c-9538-47bc-8796-aab942dc4b71"
+ },
+ "client/src/Pages/v1/Uptime/BulkImport/index.jsx-simstep-0139fcaa-eb46-4176-b7bd-1cb15e7f1f4c": {
+ "path": "client/src/Pages/v1/Uptime/BulkImport/index.jsx-simstep-0139fcaa-eb46-4176-b7bd-1cb15e7f1f4c",
+ "fileName": "index.jsx",
+ "wiki": "The user clicks the submit button, which executes the `handleSubmit` function. This function calls the `createBulkMonitors` function provided by the `useCreateBulkMonitors` custom hook.",
+ "cellName": "Flow 1: CSV Import - Submit Upload - index.jsx:L32-45",
+ "cellId": "dd2f9356-fad3-47d4-81d5-a5e038298e2b",
+ "visible": true,
+ "startLine": 32,
+ "endLine": 45,
+ "parentCellId": "29b6ad3c-9538-47bc-8796-aab942dc4b71",
+ "parentPath": "client/src/Pages/v1/Uptime/BulkImport/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Bulk Monitor Management",
+ "simStepId": "0139fcaa-eb46-4176-b7bd-1cb15e7f1f4c"
+ }
+ ]
+ },
+ "5967e576-f542-4c0b-a3f4-9446ae992721": {
+ "path": "5967e576-f542-4c0b-a3f4-9446ae992721",
+ "cellName": "Flow 1: CSV Import - Send API Request - NetworkService.js:L978-983",
+ "cellId": "5967e576-f542-4c0b-a3f4-9446ae992721",
+ "visible": true,
+ "parentCellId": "16731e9d-3f73-4055-9d79-5ca9be679f68"
+ },
+ "client/src/Utils/NetworkService.js-simstep-2fc087b3-033d-4cf5-81f2-e35b99a14bce": {
+ "path": "client/src/Utils/NetworkService.js-simstep-2fc087b3-033d-4cf5-81f2-e35b99a14bce",
+ "fileName": "NetworkService.js",
+ "wiki": "The NetworkService sends the `FormData` containing the CSV file to the backend via a POST request to the `/monitors/bulk` endpoint. The `Content-Type` is set to `multipart/form-data`.",
+ "cellName": "Flow 1: CSV Import - Send API Request - NetworkService.js:L978-983",
+ "cellId": "5967e576-f542-4c0b-a3f4-9446ae992721",
+ "visible": true,
+ "startLine": 978,
+ "endLine": 983,
+ "parentCellId": "16731e9d-3f73-4055-9d79-5ca9be679f68",
+ "parentPath": "client/src/Utils/NetworkService.js",
+ "simSteps": [
+ {
+ "simulationKey": "Bulk Monitor Management",
+ "simStepId": "2fc087b3-033d-4cf5-81f2-e35b99a14bce"
+ }
+ ]
+ },
+ "c1ac3c26-996a-43ef-9c34-4c2776188784": {
+ "path": "c1ac3c26-996a-43ef-9c34-4c2776188784",
+ "cellName": "Flow 1: CSV Import - Server Receives Request - monitorRoute.js:L46",
+ "cellId": "c1ac3c26-996a-43ef-9c34-4c2776188784",
+ "visible": true,
+ "parentCellId": "10611636-ec73-4a26-87a5-654ae64a4843"
+ },
+ "server/src/routes/v1/monitorRoute.js-simstep-07bf3d83-047d-4e51-9f96-8c9c64848500": {
+ "path": "server/src/routes/v1/monitorRoute.js-simstep-07bf3d83-047d-4e51-9f96-8c9c64848500",
+ "fileName": "monitorRoute.js",
+ "wiki": "The server's monitor route receives the request. The `multer` middleware processes the file upload, making the file available in `req.file`. The request is then passed to the `createBulkMonitors` controller method.",
+ "cellName": "Flow 1: CSV Import - Server Receives Request - monitorRoute.js:L46",
+ "cellId": "c1ac3c26-996a-43ef-9c34-4c2776188784",
+ "visible": true,
+ "startLine": 46,
+ "endLine": 46,
+ "parentCellId": "10611636-ec73-4a26-87a5-654ae64a4843",
+ "parentPath": "server/src/routes/v1/monitorRoute.js",
+ "simSteps": [
+ {
+ "simulationKey": "Bulk Monitor Management",
+ "simStepId": "07bf3d83-047d-4e51-9f96-8c9c64848500"
+ }
+ ]
+ },
+ "0e520a6d-34bf-467b-a894-e73979364478": {
+ "path": "0e520a6d-34bf-467b-a894-e73979364478",
+ "cellName": "Flow 1: CSV Import - Controller Processes Data - monitorController.js:L228-232",
+ "cellId": "0e520a6d-34bf-467b-a894-e73979364478",
+ "visible": true,
+ "parentCellId": "341f2784-67ee-4477-9445-a8c240ee6261"
+ },
+ "server/src/controllers/v1/monitorController.js-simstep-a3799570-64bf-481e-9ffe-31dbad8a36e4": {
+ "path": "server/src/controllers/v1/monitorController.js-simstep-a3799570-64bf-481e-9ffe-31dbad8a36e4",
+ "fileName": "monitorController.js",
+ "wiki": "The controller validates the presence of the file and extracts the file data (buffer), `userId`, and `teamId`. It then calls the `monitorService.createBulkMonitors` method to handle the core logic.",
+ "cellName": "Flow 1: CSV Import - Controller Processes Data - monitorController.js:L228-232",
+ "cellId": "0e520a6d-34bf-467b-a894-e73979364478",
+ "visible": true,
+ "startLine": 228,
+ "endLine": 232,
+ "parentCellId": "341f2784-67ee-4477-9445-a8c240ee6261",
+ "parentPath": "server/src/controllers/v1/monitorController.js",
+ "simSteps": [
+ {
+ "simulationKey": "Bulk Monitor Management",
+ "simStepId": "a3799570-64bf-481e-9ffe-31dbad8a36e4"
+ }
+ ]
+ },
+ "6139e487-1dd5-405a-ba7d-ededec4b282e": {
+ "path": "6139e487-1dd5-405a-ba7d-ededec4b282e",
+ "cellName": "Flow 1: CSV Import - Service Parses CSV - monitorService.js:L85-134",
+ "cellId": "6139e487-1dd5-405a-ba7d-ededec4b282e",
+ "visible": true,
+ "parentCellId": "b4e479df-9b21-4430-9383-84831f503c2e"
+ },
+ "server/src/service/v1/business/monitorService.js-simstep-2c7c3082-e4b6-4525-860f-2ffcd9e86db1": {
+ "path": "server/src/service/v1/business/monitorService.js-simstep-2c7c3082-e4b6-4525-860f-2ffcd9e86db1",
+ "fileName": "monitorService.js",
+ "wiki": "The monitor service uses the `papaparse` library to parse the CSV file buffer into a JSON object array. In the `complete` callback, it enriches each record with `userId` and `teamId`.",
+ "cellName": "Flow 1: CSV Import - Service Parses CSV - monitorService.js:L85-134",
+ "cellId": "6139e487-1dd5-405a-ba7d-ededec4b282e",
+ "visible": true,
+ "startLine": 85,
+ "endLine": 134,
+ "parentCellId": "b4e479df-9b21-4430-9383-84831f503c2e",
+ "parentPath": "server/src/service/v1/business/monitorService.js",
+ "simSteps": [
+ {
+ "simulationKey": "Bulk Monitor Management",
+ "simStepId": "2c7c3082-e4b6-4525-860f-2ffcd9e86db1"
+ }
+ ]
+ },
+ "d9d519af-5c4d-4494-929e-a869581c1dc4": {
+ "path": "d9d519af-5c4d-4494-929e-a869581c1dc4",
+ "cellName": "Flow 1: CSV Import - Database Bulk Save - monitorModule.js:L465-468",
+ "cellId": "d9d519af-5c4d-4494-929e-a869581c1dc4",
+ "visible": true,
+ "parentCellId": "630b3ece-2648-48d5-99b5-ef9611de2ad5"
+ },
+ "server/src/db/v1/modules/monitorModule.js-simstep-70f18c21-2918-402e-9c35-1bdce8bc82d4": {
+ "path": "server/src/db/v1/modules/monitorModule.js-simstep-70f18c21-2918-402e-9c35-1bdce8bc82d4",
+ "fileName": "monitorModule.js",
+ "wiki": "The `monitorModule` uses `Monitor.bulkSave` to efficiently insert all the new monitor documents into the MongoDB database in a single operation.",
+ "cellName": "Flow 1: CSV Import - Database Bulk Save - monitorModule.js:L465-468",
+ "cellId": "d9d519af-5c4d-4494-929e-a869581c1dc4",
+ "visible": true,
+ "startLine": 465,
+ "endLine": 468,
+ "parentCellId": "630b3ece-2648-48d5-99b5-ef9611de2ad5",
+ "parentPath": "server/src/db/v1/modules/monitorModule.js",
+ "simSteps": [
+ {
+ "simulationKey": "Bulk Monitor Management",
+ "simStepId": "70f18c21-2918-402e-9c35-1bdce8bc82d4"
+ }
+ ]
+ },
+ "1e1d975f-7f0f-4132-bd71-2563b35e99b3": {
+ "path": "1e1d975f-7f0f-4132-bd71-2563b35e99b3",
+ "cellName": "Flow 1: CSV Import - Enqueue Monitor Jobs - monitorService.js:L128-132",
+ "cellId": "1e1d975f-7f0f-4132-bd71-2563b35e99b3",
+ "visible": true,
+ "parentCellId": "b4e479df-9b21-4430-9383-84831f503c2e"
+ },
+ "server/src/service/v1/business/monitorService.js-simstep-66fbf913-e662-4bd9-ada6-077be9e37f07": {
+ "path": "server/src/service/v1/business/monitorService.js-simstep-66fbf913-e662-4bd9-ada6-077be9e37f07",
+ "fileName": "monitorService.js",
+ "wiki": "After the monitors are saved, the service iterates through them and adds a new job to the `jobQueue` for each one, scheduling them for their first check. It then resolves the promise, returning the data up the call stack.",
+ "cellName": "Flow 1: CSV Import - Enqueue Monitor Jobs - monitorService.js:L128-132",
+ "cellId": "1e1d975f-7f0f-4132-bd71-2563b35e99b3",
+ "visible": true,
+ "startLine": 128,
+ "endLine": 132,
+ "parentCellId": "b4e479df-9b21-4430-9383-84831f503c2e",
+ "parentPath": "server/src/service/v1/business/monitorService.js",
+ "simSteps": [
+ {
+ "simulationKey": "Bulk Monitor Management",
+ "simStepId": "66fbf913-e662-4bd9-ada6-077be9e37f07"
+ }
+ ]
+ },
+ "72b55fa1-d944-4e46-ac98-f6a6cb201afa": {
+ "path": "72b55fa1-d944-4e46-ac98-f6a6cb201afa",
+ "cellName": "Flow 1: CSV Import - Send Success Response - monitorController.js:L232-235",
+ "cellId": "72b55fa1-d944-4e46-ac98-f6a6cb201afa",
+ "visible": true,
+ "parentCellId": "341f2784-67ee-4477-9445-a8c240ee6261"
+ },
+ "server/src/controllers/v1/monitorController.js-simstep-f5377571-156f-4c5c-8bab-b6dacdf0fea6": {
+ "path": "server/src/controllers/v1/monitorController.js-simstep-f5377571-156f-4c5c-8bab-b6dacdf0fea6",
+ "fileName": "monitorController.js",
+ "wiki": "The controller constructs a successful JSON response containing a message and the data for the newly created monitors, and sends it back to the client.",
+ "cellName": "Flow 1: CSV Import - Send Success Response - monitorController.js:L232-235",
+ "cellId": "72b55fa1-d944-4e46-ac98-f6a6cb201afa",
+ "visible": true,
+ "startLine": 232,
+ "endLine": 235,
+ "parentCellId": "341f2784-67ee-4477-9445-a8c240ee6261",
+ "parentPath": "server/src/controllers/v1/monitorController.js",
+ "simSteps": [
+ {
+ "simulationKey": "Bulk Monitor Management",
+ "simStepId": "f5377571-156f-4c5c-8bab-b6dacdf0fea6"
+ }
+ ]
+ },
+ "ae3b7e23-ed64-40aa-9760-389f0a869f8d": {
+ "path": "ae3b7e23-ed64-40aa-9760-389f0a869f8d",
+ "cellName": "Flow 1: CSV Import - Client Handles Success - index.jsx:L37-41",
+ "cellId": "ae3b7e23-ed64-40aa-9760-389f0a869f8d",
+ "visible": true,
+ "parentCellId": "29b6ad3c-9538-47bc-8796-aab942dc4b71"
+ },
+ "client/src/Pages/v1/Uptime/BulkImport/index.jsx-simstep-72b3f16d-290d-4a61-8748-2ad90d63ec68": {
+ "path": "client/src/Pages/v1/Uptime/BulkImport/index.jsx-simstep-72b3f16d-290d-4a61-8748-2ad90d63ec68",
+ "fileName": "index.jsx",
+ "wiki": "The client-side promise resolves. The `handleSubmit` function in the `BulkImport` component receives the success status, displays a success toast message, and navigates the user to the main uptime monitoring page.",
+ "cellName": "Flow 1: CSV Import - Client Handles Success - index.jsx:L37-41",
+ "cellId": "ae3b7e23-ed64-40aa-9760-389f0a869f8d",
+ "visible": true,
+ "startLine": 37,
+ "endLine": 41,
+ "parentCellId": "29b6ad3c-9538-47bc-8796-aab942dc4b71",
+ "parentPath": "client/src/Pages/v1/Uptime/BulkImport/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Bulk Monitor Management",
+ "simStepId": "72b3f16d-290d-4a61-8748-2ad90d63ec68"
+ }
+ ]
+ },
+ "26d76516-fd7e-4ae8-956a-d077de4257fa": {
+ "path": "26d76516-fd7e-4ae8-956a-d077de4257fa",
+ "cellName": "Flow 2: CSV Export - Initiate Export - monitorRoute.js:L45",
+ "cellId": "26d76516-fd7e-4ae8-956a-d077de4257fa",
+ "visible": true,
+ "parentCellId": "10611636-ec73-4a26-87a5-654ae64a4843"
+ },
+ "server/src/routes/v1/monitorRoute.js-simstep-63d8d81f-f436-4594-a6eb-8c0fd2110bc1": {
+ "path": "server/src/routes/v1/monitorRoute.js-simstep-63d8d81f-f436-4594-a6eb-8c0fd2110bc1",
+ "fileName": "monitorRoute.js",
+ "wiki": "An admin user clicks an 'Export to CSV' button in the UI. This action triggers a call to the `/monitors/export` API endpoint to begin the export process.",
+ "cellName": "Flow 2: CSV Export - Initiate Export - monitorRoute.js:L45",
+ "cellId": "26d76516-fd7e-4ae8-956a-d077de4257fa",
+ "visible": true,
+ "startLine": 45,
+ "endLine": 45,
+ "parentCellId": "10611636-ec73-4a26-87a5-654ae64a4843",
+ "parentPath": "server/src/routes/v1/monitorRoute.js",
+ "simSteps": [
+ {
+ "simulationKey": "Bulk Monitor Management",
+ "simStepId": "63d8d81f-f436-4594-a6eb-8c0fd2110bc1"
+ }
+ ]
+ },
+ "a3106388-6a3d-4b68-a3fc-3761a0d54f98": {
+ "path": "a3106388-6a3d-4b68-a3fc-3761a0d54f98",
+ "cellName": "Flow 2: CSV Export - Controller Handles Request - monitorController.js:L427-434",
+ "cellId": "a3106388-6a3d-4b68-a3fc-3761a0d54f98",
+ "visible": true,
+ "parentCellId": "341f2784-67ee-4477-9445-a8c240ee6261"
+ },
+ "server/src/controllers/v1/monitorController.js-simstep-d9db3f40-8add-4551-9323-274348b9800f": {
+ "path": "server/src/controllers/v1/monitorController.js-simstep-d9db3f40-8add-4551-9323-274348b9800f",
+ "fileName": "monitorController.js",
+ "wiki": "The `exportMonitorsToCSV` method in the monitor controller is invoked. It retrieves the `teamId` from the authenticated user's session and calls the monitor service to perform the export.",
+ "cellName": "Flow 2: CSV Export - Controller Handles Request - monitorController.js:L427-434",
+ "cellId": "a3106388-6a3d-4b68-a3fc-3761a0d54f98",
+ "visible": true,
+ "startLine": 427,
+ "endLine": 434,
+ "parentCellId": "341f2784-67ee-4477-9445-a8c240ee6261",
+ "parentPath": "server/src/controllers/v1/monitorController.js",
+ "simSteps": [
+ {
+ "simulationKey": "Bulk Monitor Management",
+ "simStepId": "d9db3f40-8add-4551-9323-274348b9800f"
+ }
+ ]
+ },
+ "ab9cb862-0ac7-45a3-9888-f64e87bdda54": {
+ "path": "ab9cb862-0ac7-45a3-9888-f64e87bdda54",
+ "cellName": "Flow 2: CSV Export - Generate CSV - monitorService.js:L245-266",
+ "cellId": "ab9cb862-0ac7-45a3-9888-f64e87bdda54",
+ "visible": true,
+ "parentCellId": "b4e479df-9b21-4430-9383-84831f503c2e"
+ },
+ "server/src/service/v1/business/monitorService.js-simstep-85cd1d5d-7ffb-49c5-aacb-907048003fc5": {
+ "path": "server/src/service/v1/business/monitorService.js-simstep-85cd1d5d-7ffb-49c5-aacb-907048003fc5",
+ "fileName": "monitorService.js",
+ "wiki": "The monitor service fetches all monitors for the given `teamId` from the database. It then maps the data into the required format and uses `papaparse` to convert the JSON array into a CSV-formatted string.",
+ "cellName": "Flow 2: CSV Export - Generate CSV - monitorService.js:L245-266",
+ "cellId": "ab9cb862-0ac7-45a3-9888-f64e87bdda54",
+ "visible": true,
+ "startLine": 245,
+ "endLine": 266,
+ "parentCellId": "b4e479df-9b21-4430-9383-84831f503c2e",
+ "parentPath": "server/src/service/v1/business/monitorService.js",
+ "simSteps": [
+ {
+ "simulationKey": "Bulk Monitor Management",
+ "simStepId": "85cd1d5d-7ffb-49c5-aacb-907048003fc5"
+ }
+ ]
+ },
+ "f151f89d-6399-48f0-aecd-5eb1572b48cc": {
+ "path": "f151f89d-6399-48f0-aecd-5eb1572b48cc",
+ "cellName": "Flow 2: CSV Export - Send File Response - monitorController.js:L434-440",
+ "cellId": "f151f89d-6399-48f0-aecd-5eb1572b48cc",
+ "visible": true,
+ "parentCellId": "341f2784-67ee-4477-9445-a8c240ee6261"
+ },
+ "server/src/controllers/v1/monitorController.js-simstep-256095b8-5e3a-4c20-abd9-be9e6a9d0bad": {
+ "path": "server/src/controllers/v1/monitorController.js-simstep-256095b8-5e3a-4c20-abd9-be9e6a9d0bad",
+ "fileName": "monitorController.js",
+ "wiki": "The controller receives the CSV string and uses the `res.file` method to send it back to the client as a file. It sets the `Content-Type` to `text/csv` and `Content-Disposition` to trigger a download with the filename `monitors.csv`.",
+ "cellName": "Flow 2: CSV Export - Send File Response - monitorController.js:L434-440",
+ "cellId": "f151f89d-6399-48f0-aecd-5eb1572b48cc",
+ "visible": true,
+ "startLine": 434,
+ "endLine": 440,
+ "parentCellId": "341f2784-67ee-4477-9445-a8c240ee6261",
+ "parentPath": "server/src/controllers/v1/monitorController.js",
+ "simSteps": [
+ {
+ "simulationKey": "Bulk Monitor Management",
+ "simStepId": "256095b8-5e3a-4c20-abd9-be9e6a9d0bad"
+ }
+ ]
+ },
+ "d8877474-7203-484f-bd47-fc69e3c7c482": {
+ "path": "d8877474-7203-484f-bd47-fc69e3c7c482",
+ "cellName": "Flow 2: CSV Export - Browser Prompts Download - monitorController.js:L434-440",
+ "cellId": "d8877474-7203-484f-bd47-fc69e3c7c482",
+ "visible": true,
+ "parentCellId": "341f2784-67ee-4477-9445-a8c240ee6261"
+ },
+ "server/src/controllers/v1/monitorController.js-simstep-60abdb27-4078-4678-a010-0f6938d4be48": {
+ "path": "server/src/controllers/v1/monitorController.js-simstep-60abdb27-4078-4678-a010-0f6938d4be48",
+ "fileName": "monitorController.js",
+ "wiki": "The browser receives the server's response. The `Content-Disposition` header instructs the browser to prompt the user to save the received data as a file named `monitors.csv`, completing the export process.",
+ "cellName": "Flow 2: CSV Export - Browser Prompts Download - monitorController.js:L434-440",
+ "cellId": "d8877474-7203-484f-bd47-fc69e3c7c482",
+ "visible": true,
+ "startLine": 434,
+ "endLine": 440,
+ "parentCellId": "341f2784-67ee-4477-9445-a8c240ee6261",
+ "parentPath": "server/src/controllers/v1/monitorController.js",
+ "simSteps": [
+ {
+ "simulationKey": "Bulk Monitor Management",
+ "simStepId": "60abdb27-4078-4678-a010-0f6938d4be48"
+ }
+ ]
+ },
+ "c9a012bc-d53f-494f-82f2-1ee3f40ce646": {
+ "path": "c9a012bc-d53f-494f-82f2-1ee3f40ce646",
+ "cellName": "Flow 1:\nCSV Import\n- Render\nUI",
+ "cellId": "c9a012bc-d53f-494f-82f2-1ee3f40ce646",
+ "visible": true,
+ "parentCellId": "d3264577-10f0-42fe-9411-7e9df0754d1e"
+ },
+ "generated-edge-simstep-ef0bbcff-9535-4c0e-a5ca-96c457f89139-c9a012bc-d53f-494f-82f2-1ee3f40ce646": {
+ "path": "generated-edge-simstep-ef0bbcff-9535-4c0e-a5ca-96c457f89139-c9a012bc-d53f-494f-82f2-1ee3f40ce646",
+ "fileName": "index.jsx",
+ "cellName": "Flow 1: CSV Import - Render UI",
+ "cellId": "c9a012bc-d53f-494f-82f2-1ee3f40ce646",
+ "visible": true,
+ "startLine": 16,
+ "endLine": 50,
+ "parentPath": "client/src/Routes/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Bulk Monitor Management",
+ "simStepId": "ef0bbcff-9535-4c0e-a5ca-96c457f89139"
+ }
+ ]
+ },
+ "e03a8ce2-c2cd-4147-8bab-63d6c2cf0a48": {
+ "path": "e03a8ce2-c2cd-4147-8bab-63d6c2cf0a48",
+ "cellName": "Flow 1:\nCSV Import\n- File\nState Update",
+ "cellId": "e03a8ce2-c2cd-4147-8bab-63d6c2cf0a48",
+ "visible": true,
+ "parentCellId": "56dae13e-5735-4089-a9a4-e18be315aff7"
+ },
+ "generated-edge-simstep-610120d2-0521-4444-b245-feb97239a389-e03a8ce2-c2cd-4147-8bab-63d6c2cf0a48": {
+ "path": "generated-edge-simstep-610120d2-0521-4444-b245-feb97239a389-e03a8ce2-c2cd-4147-8bab-63d6c2cf0a48",
+ "fileName": "Upload.jsx",
+ "cellName": "Flow 1: CSV Import - File State Update",
+ "cellId": "e03a8ce2-c2cd-4147-8bab-63d6c2cf0a48",
+ "visible": true,
+ "startLine": 22,
+ "endLine": 22,
+ "parentPath": "client/src/Pages/v1/Uptime/BulkImport/Upload.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Bulk Monitor Management",
+ "simStepId": "610120d2-0521-4444-b245-feb97239a389"
+ }
+ ]
+ },
+ "b8c0292d-afbb-4648-b484-1c27be96d52f": {
+ "path": "b8c0292d-afbb-4648-b484-1c27be96d52f",
+ "cellName": "Flow 1:\nCSV Import\n- Prepare\nForm Data",
+ "cellId": "b8c0292d-afbb-4648-b484-1c27be96d52f",
+ "visible": true,
+ "parentCellId": "d3264577-10f0-42fe-9411-7e9df0754d1e"
+ },
+ "generated-edge-simstep-a365d018-e80c-419c-80b3-5ffc8f14dc5c-b8c0292d-afbb-4648-b484-1c27be96d52f": {
+ "path": "generated-edge-simstep-a365d018-e80c-419c-80b3-5ffc8f14dc5c-b8c0292d-afbb-4648-b484-1c27be96d52f",
+ "fileName": "index.jsx",
+ "cellName": "Flow 1: CSV Import - Prepare Form Data",
+ "cellId": "b8c0292d-afbb-4648-b484-1c27be96d52f",
+ "visible": true,
+ "startLine": 490,
+ "endLine": 493,
+ "parentPath": "client/src/Pages/v1/Uptime/BulkImport/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "Bulk Monitor Management",
+ "simStepId": "a365d018-e80c-419c-80b3-5ffc8f14dc5c"
+ }
+ ]
+ },
+ "23715cce-c83a-44c0-9853-4c95b85e5ecd": {
+ "path": "23715cce-c83a-44c0-9853-4c95b85e5ecd",
+ "cellName": "Flow 1:\nCSV Import\n- Transmit\nData to\nServer",
+ "cellId": "23715cce-c83a-44c0-9853-4c95b85e5ecd",
+ "visible": true
+ },
+ "generated-edge-simstep-e6dec1dc-33cb-4669-a3b0-7a3c0b233185-23715cce-c83a-44c0-9853-4c95b85e5ecd": {
+ "path": "generated-edge-simstep-e6dec1dc-33cb-4669-a3b0-7a3c0b233185-23715cce-c83a-44c0-9853-4c95b85e5ecd",
+ "fileName": "NetworkService.js",
+ "cellName": "Flow 1: CSV Import - Transmit Data to Server",
+ "cellId": "23715cce-c83a-44c0-9853-4c95b85e5ecd",
+ "visible": true,
+ "startLine": 46,
+ "endLine": 46,
+ "parentPath": "client/src/Utils/NetworkService.js",
+ "simSteps": [
+ {
+ "simulationKey": "Bulk Monitor Management",
+ "simStepId": "e6dec1dc-33cb-4669-a3b0-7a3c0b233185"
+ }
+ ]
+ },
+ "20d0e690-a569-4c6f-8238-916cac920553": {
+ "path": "20d0e690-a569-4c6f-8238-916cac920553",
+ "cellName": "Flow 1:\nCSV Import\n- Route\nto Controller",
+ "cellId": "20d0e690-a569-4c6f-8238-916cac920553",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "generated-edge-simstep-c53d3f62-73fe-4fad-9835-ef2c8cf52894-20d0e690-a569-4c6f-8238-916cac920553": {
+ "path": "generated-edge-simstep-c53d3f62-73fe-4fad-9835-ef2c8cf52894-20d0e690-a569-4c6f-8238-916cac920553",
+ "fileName": "monitorRoute.js",
+ "cellName": "Flow 1: CSV Import - Route to Controller",
+ "cellId": "20d0e690-a569-4c6f-8238-916cac920553",
+ "visible": true,
+ "startLine": 206,
+ "endLine": 212,
+ "parentPath": "server/src/routes/v1/monitorRoute.js",
+ "simSteps": [
+ {
+ "simulationKey": "Bulk Monitor Management",
+ "simStepId": "c53d3f62-73fe-4fad-9835-ef2c8cf52894"
+ }
+ ]
+ },
+ "7292bf6e-431d-4a9d-94ce-2bcfabcaa79d": {
+ "path": "7292bf6e-431d-4a9d-94ce-2bcfabcaa79d",
+ "cellName": "Flow 1:\nCSV Import\n- Controller\nto Service",
+ "cellId": "7292bf6e-431d-4a9d-94ce-2bcfabcaa79d",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "generated-edge-simstep-6038422c-ab83-4fc6-a58b-c75734276118-7292bf6e-431d-4a9d-94ce-2bcfabcaa79d": {
+ "path": "generated-edge-simstep-6038422c-ab83-4fc6-a58b-c75734276118-7292bf6e-431d-4a9d-94ce-2bcfabcaa79d",
+ "fileName": "monitorController.js",
+ "cellName": "Flow 1: CSV Import - Controller to Service",
+ "cellId": "7292bf6e-431d-4a9d-94ce-2bcfabcaa79d",
+ "visible": true,
+ "startLine": 84,
+ "endLine": 84,
+ "parentPath": "server/src/controllers/v1/monitorController.js",
+ "simSteps": [
+ {
+ "simulationKey": "Bulk Monitor Management",
+ "simStepId": "6038422c-ab83-4fc6-a58b-c75734276118"
+ }
+ ]
+ },
+ "b55a0dc6-f596-4e5f-9265-63588b46b519": {
+ "path": "b55a0dc6-f596-4e5f-9265-63588b46b519",
+ "cellName": "Flow 1:\nCSV Import\n- Service\nto Database\nModule",
+ "cellId": "b55a0dc6-f596-4e5f-9265-63588b46b519",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "generated-edge-simstep-0115fb69-4959-4b32-bd90-1dcd3c2231f6-b55a0dc6-f596-4e5f-9265-63588b46b519": {
+ "path": "generated-edge-simstep-0115fb69-4959-4b32-bd90-1dcd3c2231f6-b55a0dc6-f596-4e5f-9265-63588b46b519",
+ "fileName": "monitorService.js",
+ "cellName": "Flow 1: CSV Import - Service to Database Module",
+ "cellId": "b55a0dc6-f596-4e5f-9265-63588b46b519",
+ "visible": true,
+ "startLine": 126,
+ "endLine": 126,
+ "parentPath": "server/src/service/v1/business/monitorService.js",
+ "simSteps": [
+ {
+ "simulationKey": "Bulk Monitor Management",
+ "simStepId": "0115fb69-4959-4b32-bd90-1dcd3c2231f6"
+ }
+ ]
+ },
+ "f27c60ef-fb40-43e2-9750-c2617bdeaf43": {
+ "path": "f27c60ef-fb40-43e2-9750-c2617bdeaf43",
+ "cellName": "Flow 1:\nCSV Import\n- Database\nto Service",
+ "cellId": "f27c60ef-fb40-43e2-9750-c2617bdeaf43",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "generated-edge-simstep-2ee7bd94-d131-4dd8-bdf8-68433d00472d-f27c60ef-fb40-43e2-9750-c2617bdeaf43": {
+ "path": "generated-edge-simstep-2ee7bd94-d131-4dd8-bdf8-68433d00472d-f27c60ef-fb40-43e2-9750-c2617bdeaf43",
+ "fileName": "monitorModule.js",
+ "cellName": "Flow 1: CSV Import - Database to Service",
+ "cellId": "f27c60ef-fb40-43e2-9750-c2617bdeaf43",
+ "visible": true,
+ "startLine": 467,
+ "endLine": 467,
+ "parentPath": "server/src/db/v1/modules/monitorModule.js",
+ "simSteps": [
+ {
+ "simulationKey": "Bulk Monitor Management",
+ "simStepId": "2ee7bd94-d131-4dd8-bdf8-68433d00472d"
+ }
+ ]
+ },
+ "c449f81a-d6af-4f7a-b2d8-050a9a9ee0f9": {
+ "path": "c449f81a-d6af-4f7a-b2d8-050a9a9ee0f9",
+ "cellName": "Flow 1:\nCSV Import\n- Service\nto Controller",
+ "cellId": "c449f81a-d6af-4f7a-b2d8-050a9a9ee0f9",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "generated-edge-simstep-94d5772d-3f46-4d43-b764-3493db5c4971-c449f81a-d6af-4f7a-b2d8-050a9a9ee0f9": {
+ "path": "generated-edge-simstep-94d5772d-3f46-4d43-b764-3493db5c4971-c449f81a-d6af-4f7a-b2d8-050a9a9ee0f9",
+ "fileName": "monitorService.js",
+ "cellName": "Flow 1: CSV Import - Service to Controller",
+ "cellId": "c449f81a-d6af-4f7a-b2d8-050a9a9ee0f9",
+ "visible": true,
+ "startLine": 230,
+ "endLine": 230,
+ "parentPath": "server/src/service/v1/business/monitorService.js",
+ "simSteps": [
+ {
+ "simulationKey": "Bulk Monitor Management",
+ "simStepId": "94d5772d-3f46-4d43-b764-3493db5c4971"
+ }
+ ]
+ },
+ "926a330c-a9c5-4983-a4b5-70359813e49a": {
+ "path": "926a330c-a9c5-4983-a4b5-70359813e49a",
+ "cellName": "Flow 1:\nCSV Import\n- Transmit\nSuccess to\nClient",
+ "cellId": "926a330c-a9c5-4983-a4b5-70359813e49a",
+ "visible": true
+ },
+ "generated-edge-simstep-c60a3adb-dd81-41b0-95e4-a0a6d23f9dd0-926a330c-a9c5-4983-a4b5-70359813e49a": {
+ "path": "generated-edge-simstep-c60a3adb-dd81-41b0-95e4-a0a6d23f9dd0-926a330c-a9c5-4983-a4b5-70359813e49a",
+ "fileName": "monitorController.js",
+ "cellName": "Flow 1: CSV Import - Transmit Success to Client",
+ "cellId": "926a330c-a9c5-4983-a4b5-70359813e49a",
+ "visible": true,
+ "startLine": 494,
+ "endLine": 494,
+ "parentPath": "server/src/controllers/v1/monitorController.js",
+ "simSteps": [
+ {
+ "simulationKey": "Bulk Monitor Management",
+ "simStepId": "c60a3adb-dd81-41b0-95e4-a0a6d23f9dd0"
+ }
+ ]
+ },
+ "febea87e-5033-479c-ba7a-eb35d98db899": {
+ "path": "febea87e-5033-479c-ba7a-eb35d98db899",
+ "cellName": "Flow 2:\nCSV Export\n- Transmit\nRequest to\nServer",
+ "cellId": "febea87e-5033-479c-ba7a-eb35d98db899",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "generated-edge-simstep-53cebb8e-cf45-49eb-a7d3-98fd87af21b4-febea87e-5033-479c-ba7a-eb35d98db899": {
+ "path": "generated-edge-simstep-53cebb8e-cf45-49eb-a7d3-98fd87af21b4-febea87e-5033-479c-ba7a-eb35d98db899",
+ "fileName": "monitorRoute.js",
+ "cellName": "Flow 2: CSV Export - Transmit Request to Server",
+ "cellId": "febea87e-5033-479c-ba7a-eb35d98db899",
+ "visible": true,
+ "startLine": 45,
+ "endLine": 45,
+ "parentPath": "server/src/routes/v1/monitorRoute.js",
+ "simSteps": [
+ {
+ "simulationKey": "Bulk Monitor Management",
+ "simStepId": "53cebb8e-cf45-49eb-a7d3-98fd87af21b4"
+ }
+ ]
+ },
+ "3e0f0966-5c2f-41af-9bcc-e8f615e03a00": {
+ "path": "3e0f0966-5c2f-41af-9bcc-e8f615e03a00",
+ "cellName": "Flow 2:\nCSV Export\n- Controller\nto Service",
+ "cellId": "3e0f0966-5c2f-41af-9bcc-e8f615e03a00",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "generated-edge-simstep-74e95fb5-863c-4137-a41b-d7a32efa04a5-3e0f0966-5c2f-41af-9bcc-e8f615e03a00": {
+ "path": "generated-edge-simstep-74e95fb5-863c-4137-a41b-d7a32efa04a5-3e0f0966-5c2f-41af-9bcc-e8f615e03a00",
+ "fileName": "monitorController.js",
+ "cellName": "Flow 2: CSV Export - Controller to Service",
+ "cellId": "3e0f0966-5c2f-41af-9bcc-e8f615e03a00",
+ "visible": true,
+ "startLine": 245,
+ "endLine": 245,
+ "parentPath": "server/src/controllers/v1/monitorController.js",
+ "simSteps": [
+ {
+ "simulationKey": "Bulk Monitor Management",
+ "simStepId": "74e95fb5-863c-4137-a41b-d7a32efa04a5"
+ }
+ ]
+ },
+ "ad300b1c-6748-40f9-a7c9-5895e0915991": {
+ "path": "ad300b1c-6748-40f9-a7c9-5895e0915991",
+ "cellName": "Flow 2:\nCSV Export\n- Service\nto Controller",
+ "cellId": "ad300b1c-6748-40f9-a7c9-5895e0915991",
+ "visible": true,
+ "parentCellId": "35864a2c-7bd0-4428-84fc-c0aa90c7f1be"
+ },
+ "generated-edge-simstep-4deb5e47-2ac4-4638-9ee6-48295d027bcd-ad300b1c-6748-40f9-a7c9-5895e0915991": {
+ "path": "generated-edge-simstep-4deb5e47-2ac4-4638-9ee6-48295d027bcd-ad300b1c-6748-40f9-a7c9-5895e0915991",
+ "fileName": "monitorService.js",
+ "cellName": "Flow 2: CSV Export - Service to Controller",
+ "cellId": "ad300b1c-6748-40f9-a7c9-5895e0915991",
+ "visible": true,
+ "startLine": 432,
+ "endLine": 432,
+ "parentPath": "server/src/service/v1/business/monitorService.js",
+ "simSteps": [
+ {
+ "simulationKey": "Bulk Monitor Management",
+ "simStepId": "4deb5e47-2ac4-4638-9ee6-48295d027bcd"
+ }
+ ]
+ },
+ "3dfb432a-2ce4-4f92-98ce-5d114b5f7f03": {
+ "path": "3dfb432a-2ce4-4f92-98ce-5d114b5f7f03",
+ "cellName": "Flow 2:\nCSV Export\n- Transmit\nFile to\nClient",
+ "cellId": "3dfb432a-2ce4-4f92-98ce-5d114b5f7f03",
+ "visible": true,
+ "parentCellId": "341f2784-67ee-4477-9445-a8c240ee6261"
+ },
+ "generated-edge-simstep-605f4cbd-1f7f-4e89-b028-f18737ac7fb1-3dfb432a-2ce4-4f92-98ce-5d114b5f7f03": {
+ "path": "generated-edge-simstep-605f4cbd-1f7f-4e89-b028-f18737ac7fb1-3dfb432a-2ce4-4f92-98ce-5d114b5f7f03",
+ "fileName": "monitorController.js",
+ "cellName": "Flow 2: CSV Export - Transmit File to Client",
+ "cellId": "3dfb432a-2ce4-4f92-98ce-5d114b5f7f03",
+ "visible": true,
+ "startLine": 434,
+ "endLine": 440,
+ "parentPath": "server/src/controllers/v1/monitorController.js",
+ "simSteps": [
+ {
+ "simulationKey": "Bulk Monitor Management",
+ "simStepId": "605f4cbd-1f7f-4e89-b028-f18737ac7fb1"
+ }
+ ]
+ },
+ "50f77798-f41b-4f5b-b096-7afc921a0419": {
+ "path": "50f77798-f41b-4f5b-b096-7afc921a0419",
+ "cellName": "Settings",
+ "cellId": "50f77798-f41b-4f5b-b096-7afc921a0419",
+ "visible": true,
+ "parentCellId": "1a083b4a-787c-4eb4-8029-6700de987679"
+ },
+ "1716ec41-8b42-4386-9850-55b8a607638e": {
+ "path": "1716ec41-8b42-4386-9850-55b8a607638e",
+ "cellName": "Logs",
+ "cellId": "1716ec41-8b42-4386-9850-55b8a607638e",
+ "visible": true,
+ "parentCellId": "1a083b4a-787c-4eb4-8029-6700de987679"
+ },
+ "8ee5d524-e61d-4e39-a729-babf54929def": {
+ "path": "8ee5d524-e61d-4e39-a729-babf54929def",
+ "cellName": "settingsHooks.js",
+ "cellId": "8ee5d524-e61d-4e39-a729-babf54929def",
+ "visible": true,
+ "parentCellId": "3bf0c0f9-cbb0-4f0c-8cb3-6e2a4abc0961"
+ },
+ "a1fea5b0-fb7c-4189-9484-9d3312660767": {
+ "path": "a1fea5b0-fb7c-4189-9484-9d3312660767",
+ "cellName": "settingsController.js",
+ "cellId": "a1fea5b0-fb7c-4189-9484-9d3312660767",
+ "visible": true,
+ "parentCellId": "fc591f7a-d4ac-4565-949d-a87a58a79bbc"
+ },
+ "5882419e-8a92-4edd-a71a-1252983f9af7": {
+ "path": "5882419e-8a92-4edd-a71a-1252983f9af7",
+ "cellName": "diagnosticController.js",
+ "cellId": "5882419e-8a92-4edd-a71a-1252983f9af7",
+ "visible": true,
+ "parentCellId": "fc591f7a-d4ac-4565-949d-a87a58a79bbc"
+ },
+ "d4b34195-7e1e-47a0-9303-603987c81f95": {
+ "path": "d4b34195-7e1e-47a0-9303-603987c81f95",
+ "cellName": "index.jsx",
+ "cellId": "d4b34195-7e1e-47a0-9303-603987c81f95",
+ "visible": true,
+ "parentCellId": "50f77798-f41b-4f5b-b096-7afc921a0419"
+ },
+ "d8aa5b9e-f2a7-44c8-8813-d79c3f3238ee": {
+ "path": "d8aa5b9e-f2a7-44c8-8813-d79c3f3238ee",
+ "cellName": "SettingsEmail.jsx",
+ "cellId": "d8aa5b9e-f2a7-44c8-8813-d79c3f3238ee",
+ "visible": true,
+ "parentCellId": "50f77798-f41b-4f5b-b096-7afc921a0419"
+ },
+ "e0cb598c-e300-4fd3-8554-db7ffd6201f1": {
+ "path": "e0cb598c-e300-4fd3-8554-db7ffd6201f1",
+ "cellName": "Diagnostics",
+ "cellId": "e0cb598c-e300-4fd3-8554-db7ffd6201f1",
+ "visible": true,
+ "parentCellId": "1716ec41-8b42-4386-9850-55b8a607638e"
+ },
+ "643b26c1-3bfb-4c41-8179-96dfc237d99e": {
+ "path": "643b26c1-3bfb-4c41-8179-96dfc237d99e",
+ "cellName": "diagnosticService.js",
+ "cellId": "643b26c1-3bfb-4c41-8179-96dfc237d99e",
+ "visible": true,
+ "parentCellId": "87d3b101-9443-4ed4-9c4f-f759d41677eb"
+ },
+ "87e0e2ea-4a6d-460d-95bc-31e2efd9c9cf": {
+ "path": "87e0e2ea-4a6d-460d-95bc-31e2efd9c9cf",
+ "cellName": "index.jsx",
+ "cellId": "87e0e2ea-4a6d-460d-95bc-31e2efd9c9cf",
+ "visible": true,
+ "parentCellId": "e0cb598c-e300-4fd3-8554-db7ffd6201f1"
+ },
+ "97908daf-d071-4df5-8366-1f5f4d8d534a": {
+ "path": "97908daf-d071-4df5-8366-1f5f4d8d534a",
+ "cellName": "Flow 1: Render Settings Page - index.jsx:L25-52",
+ "cellId": "97908daf-d071-4df5-8366-1f5f4d8d534a",
+ "visible": true,
+ "parentCellId": "d4b34195-7e1e-47a0-9303-603987c81f95"
+ },
+ "client/src/Pages/v1/Settings/index.jsx-simstep-64e9f1af-13b7-4361-a2a9-3f45760e316d": {
+ "path": "client/src/Pages/v1/Settings/index.jsx-simstep-64e9f1af-13b7-4361-a2a9-3f45760e316d",
+ "fileName": "index.jsx",
+ "wiki": "The user navigates to the settings page. The main `Settings` component fetches existing settings using the `useFetchSettings` hook and renders various sub-components for different configuration areas like TimeZone, UI, PageSpeed, Email, etc.",
+ "cellName": "Flow 1: Render Settings Page - index.jsx:L25-52",
+ "cellId": "97908daf-d071-4df5-8366-1f5f4d8d534a",
+ "visible": true,
+ "startLine": 25,
+ "endLine": 52,
+ "parentCellId": "d4b34195-7e1e-47a0-9303-603987c81f95",
+ "parentPath": "client/src/Pages/v1/Settings/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "System Administration and Configuration",
+ "simStepId": "64e9f1af-13b7-4361-a2a9-3f45760e316d"
+ }
+ ]
+ },
+ "83596bd1-dfca-4196-b52e-955f6824fafb": {
+ "path": "83596bd1-dfca-4196-b52e-955f6824fafb",
+ "cellName": "Backend: Retrieve Settings from Database - settingsController.js:L40-49",
+ "cellId": "83596bd1-dfca-4196-b52e-955f6824fafb",
+ "visible": true,
+ "parentCellId": "a1fea5b0-fb7c-4189-9484-9d3312660767"
+ },
+ "server/src/controllers/v1/settingsController.js-simstep-441b0c45-5f27-478d-8ca8-58fbd5f460ea": {
+ "path": "server/src/controllers/v1/settingsController.js-simstep-441b0c45-5f27-478d-8ca8-58fbd5f460ea",
+ "fileName": "settingsController.js",
+ "wiki": "The `settingsController` handles the GET request. It calls the `settingsService` to fetch settings from the database. Sensitive data like passwords is sanitized, and flags like `emailPasswordSet` are added before sending the data to the client.",
+ "cellName": "Backend: Retrieve Settings from Database - settingsController.js:L40-49",
+ "cellId": "83596bd1-dfca-4196-b52e-955f6824fafb",
+ "visible": true,
+ "startLine": 40,
+ "endLine": 49,
+ "parentCellId": "a1fea5b0-fb7c-4189-9484-9d3312660767",
+ "parentPath": "server/src/controllers/v1/settingsController.js",
+ "simSteps": [
+ {
+ "simulationKey": "System Administration and Configuration",
+ "simStepId": "441b0c45-5f27-478d-8ca8-58fbd5f460ea"
+ }
+ ]
+ },
+ "09cb194a-0ea4-46e2-b32b-36b62fa87f40": {
+ "path": "09cb194a-0ea4-46e2-b32b-36b62fa87f40",
+ "cellName": "User Interaction: Modify Email Settings - SettingsEmail.jsx:L119-167",
+ "cellId": "09cb194a-0ea4-46e2-b32b-36b62fa87f40",
+ "visible": true,
+ "parentCellId": "d8aa5b9e-f2a7-44c8-8813-d79c3f3238ee"
+ },
+ "client/src/Pages/v1/Settings/SettingsEmail.jsx-simstep-ae5c5146-4dbb-4612-9d23-086e57f72474": {
+ "path": "client/src/Pages/v1/Settings/SettingsEmail.jsx-simstep-ae5c5146-4dbb-4612-9d23-086e57f72474",
+ "fileName": "SettingsEmail.jsx",
+ "wiki": "The user interacts with the `SettingsEmail` component, filling in SMTP server details. The component's state is updated via the `handleChange` function passed down from the parent `Settings` component, which prepares the new configuration data.",
+ "cellName": "User Interaction: Modify Email Settings - SettingsEmail.jsx:L119-167",
+ "cellId": "09cb194a-0ea4-46e2-b32b-36b62fa87f40",
+ "visible": true,
+ "startLine": 119,
+ "endLine": 167,
+ "parentCellId": "d8aa5b9e-f2a7-44c8-8813-d79c3f3238ee",
+ "parentPath": "client/src/Pages/v1/Settings/SettingsEmail.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "System Administration and Configuration",
+ "simStepId": "ae5c5146-4dbb-4612-9d23-086e57f72474"
+ }
+ ]
+ },
+ "1b2cf182-9ee3-4218-a7ec-b74e427ccddf": {
+ "path": "1b2cf182-9ee3-4218-a7ec-b74e427ccddf",
+ "cellName": "API Call: Update Application Settings - settingsHooks.js:L41-67",
+ "cellId": "1b2cf182-9ee3-4218-a7ec-b74e427ccddf",
+ "visible": true,
+ "parentCellId": "8ee5d524-e61d-4e39-a729-babf54929def"
+ },
+ "client/src/Hooks/v1/settingsHooks.js-simstep-d7098072-9a83-4369-8176-9646ba34e186": {
+ "path": "client/src/Hooks/v1/settingsHooks.js-simstep-d7098072-9a83-4369-8176-9646ba34e186",
+ "fileName": "settingsHooks.js",
+ "wiki": "The `handleSave` function calls `saveSettings` from the `useSaveSettings` hook. This triggers a PUT request to `/api/v1/settings` with the new configuration in the request body.",
+ "cellName": "API Call: Update Application Settings - settingsHooks.js:L41-67",
+ "cellId": "1b2cf182-9ee3-4218-a7ec-b74e427ccddf",
+ "visible": true,
+ "startLine": 41,
+ "endLine": 67,
+ "parentCellId": "8ee5d524-e61d-4e39-a729-babf54929def",
+ "parentPath": "client/src/Hooks/v1/settingsHooks.js",
+ "simSteps": [
+ {
+ "simulationKey": "System Administration and Configuration",
+ "simStepId": "d7098072-9a83-4369-8176-9646ba34e186"
+ }
+ ]
+ },
+ "cce3b9c8-f3b7-434b-b3c6-d8d164714f86": {
+ "path": "cce3b9c8-f3b7-434b-b3c6-d8d164714f86",
+ "cellName": "Backend: Persist Updated Settings - settingsController.js:L54-64",
+ "cellId": "cce3b9c8-f3b7-434b-b3c6-d8d164714f86",
+ "visible": true,
+ "parentCellId": "a1fea5b0-fb7c-4189-9484-9d3312660767"
+ },
+ "server/src/controllers/v1/settingsController.js-simstep-7632e8d2-4086-4169-ae3f-bfbf8d657987": {
+ "path": "server/src/controllers/v1/settingsController.js-simstep-7632e8d2-4086-4169-ae3f-bfbf8d657987",
+ "fileName": "settingsController.js",
+ "wiki": "The `settingsController` receives the PUT request, validates the body, and calls the `db.settingsModule.updateAppSettings` method to update the settings document in the database.",
+ "cellName": "Backend: Persist Updated Settings - settingsController.js:L54-64",
+ "cellId": "cce3b9c8-f3b7-434b-b3c6-d8d164714f86",
+ "visible": true,
+ "startLine": 54,
+ "endLine": 64,
+ "parentCellId": "a1fea5b0-fb7c-4189-9484-9d3312660767",
+ "parentPath": "server/src/controllers/v1/settingsController.js",
+ "simSteps": [
+ {
+ "simulationKey": "System Administration and Configuration",
+ "simStepId": "7632e8d2-4086-4169-ae3f-bfbf8d657987"
+ }
+ ]
+ },
+ "fff5dde4-8eb8-4c21-9c6d-0845a9749ff3": {
+ "path": "fff5dde4-8eb8-4c21-9c6d-0845a9749ff3",
+ "cellName": "UI Update: Display Success Confirmation - settingsHooks.js:L58",
+ "cellId": "fff5dde4-8eb8-4c21-9c6d-0845a9749ff3",
+ "visible": true,
+ "parentCellId": "8ee5d524-e61d-4e39-a729-babf54929def"
+ },
+ "client/src/Hooks/v1/settingsHooks.js-simstep-a157bd07-b969-44f8-914c-3d8393c1605a": {
+ "path": "client/src/Hooks/v1/settingsHooks.js-simstep-a157bd07-b969-44f8-914c-3d8393c1605a",
+ "fileName": "settingsHooks.js",
+ "wiki": "The frontend network service receives the successful API response. The `saveSettings` hook then calls `createToast` with a success message, which is displayed to the user.",
+ "cellName": "UI Update: Display Success Confirmation - settingsHooks.js:L58",
+ "cellId": "fff5dde4-8eb8-4c21-9c6d-0845a9749ff3",
+ "visible": true,
+ "startLine": 58,
+ "endLine": 58,
+ "parentCellId": "8ee5d524-e61d-4e39-a729-babf54929def",
+ "parentPath": "client/src/Hooks/v1/settingsHooks.js",
+ "simSteps": [
+ {
+ "simulationKey": "System Administration and Configuration",
+ "simStepId": "a157bd07-b969-44f8-914c-3d8393c1605a"
+ }
+ ]
+ },
+ "704e2ec8-9f2f-47e5-852f-c6ada67a02e0": {
+ "path": "704e2ec8-9f2f-47e5-852f-c6ada67a02e0",
+ "cellName": "Flow 2: Navigate to Diagnostics Page - index.jsx:L14-15",
+ "cellId": "704e2ec8-9f2f-47e5-852f-c6ada67a02e0",
+ "visible": true,
+ "parentCellId": "87e0e2ea-4a6d-460d-95bc-31e2efd9c9cf"
+ },
+ "client/src/Pages/v1/Logs/Diagnostics/index.jsx-simstep-a9b24b5f-534b-4baf-a185-bac7c32a1c42": {
+ "path": "client/src/Pages/v1/Logs/Diagnostics/index.jsx-simstep-a9b24b5f-534b-4baf-a185-bac7c32a1c42",
+ "fileName": "index.jsx",
+ "wiki": "The user navigates to the 'Logs' page and selects the 'Diagnostics' tab. The `Diagnostics` component renders and immediately calls the `useFetchDiagnostics` hook to retrieve system health data.",
+ "cellName": "Flow 2: Navigate to Diagnostics Page - index.jsx:L14-15",
+ "cellId": "704e2ec8-9f2f-47e5-852f-c6ada67a02e0",
+ "visible": true,
+ "startLine": 14,
+ "endLine": 15,
+ "parentCellId": "87e0e2ea-4a6d-460d-95bc-31e2efd9c9cf",
+ "parentPath": "client/src/Pages/v1/Logs/Diagnostics/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "System Administration and Configuration",
+ "simStepId": "a9b24b5f-534b-4baf-a185-bac7c32a1c42"
+ }
+ ]
+ },
+ "09f07e80-185e-4547-aab4-edaf8ae7501e": {
+ "path": "09f07e80-185e-4547-aab4-edaf8ae7501e",
+ "cellName": "Backend: Gather System Statistics - diagnosticService.js:L39-95",
+ "cellId": "09f07e80-185e-4547-aab4-edaf8ae7501e",
+ "visible": true,
+ "parentCellId": "643b26c1-3bfb-4c41-8179-96dfc237d99e"
+ },
+ "server/src/service/v1/business/diagnosticService.js-simstep-8fb704ca-67d9-437c-90d8-01302634d568": {
+ "path": "server/src/service/v1/business/diagnosticService.js-simstep-8fb704ca-67d9-437c-90d8-01302634d568",
+ "fileName": "diagnosticService.js",
+ "wiki": "The `diagnosticController` receives the request and calls `diagnosticService.getSystemStats()`. This service uses native Node.js modules like `os` and `v8` to collect metrics on memory, CPU, heap statistics, and process uptime.",
+ "cellName": "Backend: Gather System Statistics - diagnosticService.js:L39-95",
+ "cellId": "09f07e80-185e-4547-aab4-edaf8ae7501e",
+ "visible": true,
+ "startLine": 39,
+ "endLine": 95,
+ "parentCellId": "643b26c1-3bfb-4c41-8179-96dfc237d99e",
+ "parentPath": "server/src/service/v1/business/diagnosticService.js",
+ "simSteps": [
+ {
+ "simulationKey": "System Administration and Configuration",
+ "simStepId": "8fb704ca-67d9-437c-90d8-01302634d568"
+ }
+ ]
+ },
+ "4b418736-6b72-47e0-917a-d86d4bed9ecd": {
+ "path": "4b418736-6b72-47e0-917a-d86d4bed9ecd",
+ "cellName": "UI Update: Display System Health - index.jsx:L59-63",
+ "cellId": "4b418736-6b72-47e0-917a-d86d4bed9ecd",
+ "visible": true,
+ "parentCellId": "87e0e2ea-4a6d-460d-95bc-31e2efd9c9cf"
+ },
+ "client/src/Pages/v1/Logs/Diagnostics/index.jsx-simstep-e6c94f52-a599-4568-a06f-2db18f963817": {
+ "path": "client/src/Pages/v1/Logs/Diagnostics/index.jsx-simstep-e6c94f52-a599-4568-a06f-2db18f963817",
+ "fileName": "index.jsx",
+ "wiki": "The frontend receives the diagnostic data. The `Diagnostics` component passes this data to its children, `Gauges` and `StatusBoxes`, which then render the information as interactive gauges and statistical boxes.",
+ "cellName": "UI Update: Display System Health - index.jsx:L59-63",
+ "cellId": "4b418736-6b72-47e0-917a-d86d4bed9ecd",
+ "visible": true,
+ "startLine": 59,
+ "endLine": 63,
+ "parentCellId": "87e0e2ea-4a6d-460d-95bc-31e2efd9c9cf",
+ "parentPath": "client/src/Pages/v1/Logs/Diagnostics/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "System Administration and Configuration",
+ "simStepId": "e6c94f52-a599-4568-a06f-2db18f963817"
+ }
+ ]
+ },
+ "f6d80f09-ae83-4c95-aba0-4efffeda38e0": {
+ "path": "f6d80f09-ae83-4c95-aba0-4efffeda38e0",
+ "cellName": "API Call:\nFetch Application\nSettings",
+ "cellId": "f6d80f09-ae83-4c95-aba0-4efffeda38e0",
+ "visible": true
+ },
+ "generated-edge-simstep-dca4a33c-b666-47b8-896b-370ed9d6f9c5-f6d80f09-ae83-4c95-aba0-4efffeda38e0": {
+ "path": "generated-edge-simstep-dca4a33c-b666-47b8-896b-370ed9d6f9c5-f6d80f09-ae83-4c95-aba0-4efffeda38e0",
+ "fileName": "index.jsx",
+ "cellName": "API Call: Fetch Application Settings",
+ "cellId": "f6d80f09-ae83-4c95-aba0-4efffeda38e0",
+ "visible": true,
+ "startLine": 692,
+ "endLine": 698,
+ "parentPath": "client/src/Pages/v1/Settings/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "System Administration and Configuration",
+ "simStepId": "dca4a33c-b666-47b8-896b-370ed9d6f9c5"
+ }
+ ]
+ },
+ "195ad532-9858-493e-876a-6ec457811d1d": {
+ "path": "195ad532-9858-493e-876a-6ec457811d1d",
+ "cellName": "API Response:\nSend Settings\nto Client",
+ "cellId": "195ad532-9858-493e-876a-6ec457811d1d",
+ "visible": true
+ },
+ "generated-edge-simstep-aacc9302-6ca8-45c6-acb8-956d8d0242c2-195ad532-9858-493e-876a-6ec457811d1d": {
+ "path": "generated-edge-simstep-aacc9302-6ca8-45c6-acb8-956d8d0242c2-195ad532-9858-493e-876a-6ec457811d1d",
+ "fileName": "settingsController.js",
+ "cellName": "API Response: Send Settings to Client",
+ "cellId": "195ad532-9858-493e-876a-6ec457811d1d",
+ "visible": true,
+ "startLine": 45,
+ "endLine": 48,
+ "parentPath": "server/src/controllers/v1/settingsController.js",
+ "simSteps": [
+ {
+ "simulationKey": "System Administration and Configuration",
+ "simStepId": "aacc9302-6ca8-45c6-acb8-956d8d0242c2"
+ }
+ ]
+ },
+ "8c107d79-f796-49eb-ab88-f553dd6f9d95": {
+ "path": "8c107d79-f796-49eb-ab88-f553dd6f9d95",
+ "cellName": "User Action:\nSave Settings",
+ "cellId": "8c107d79-f796-49eb-ab88-f553dd6f9d95",
+ "visible": true,
+ "parentCellId": "d3264577-10f0-42fe-9411-7e9df0754d1e"
+ },
+ "generated-edge-simstep-e7e82a97-1409-4bc5-ae27-b4ff5ba746df-8c107d79-f796-49eb-ab88-f553dd6f9d95": {
+ "path": "generated-edge-simstep-e7e82a97-1409-4bc5-ae27-b4ff5ba746df-8c107d79-f796-49eb-ab88-f553dd6f9d95",
+ "fileName": "SettingsEmail.jsx",
+ "cellName": "User Action: Save Settings",
+ "cellId": "8c107d79-f796-49eb-ab88-f553dd6f9d95",
+ "visible": true,
+ "startLine": 258,
+ "endLine": 261,
+ "parentPath": "client/src/Pages/v1/Settings/SettingsEmail.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "System Administration and Configuration",
+ "simStepId": "e7e82a97-1409-4bc5-ae27-b4ff5ba746df"
+ }
+ ]
+ },
+ "ce3ed3ba-a35b-4881-8dfd-f8425b9bf812": {
+ "path": "ce3ed3ba-a35b-4881-8dfd-f8425b9bf812",
+ "cellName": "Data Transmission:\nSend Updated\nSettings to\nServer",
+ "cellId": "ce3ed3ba-a35b-4881-8dfd-f8425b9bf812",
+ "visible": true
+ },
+ "generated-edge-simstep-383387e5-4623-44ab-b2c4-d9ea046f4547-ce3ed3ba-a35b-4881-8dfd-f8425b9bf812": {
+ "path": "generated-edge-simstep-383387e5-4623-44ab-b2c4-d9ea046f4547-ce3ed3ba-a35b-4881-8dfd-f8425b9bf812",
+ "fileName": "settingsHooks.js",
+ "cellName": "Data Transmission: Send Updated Settings to Server",
+ "cellId": "ce3ed3ba-a35b-4881-8dfd-f8425b9bf812",
+ "visible": true,
+ "startLine": 711,
+ "endLine": 717,
+ "parentPath": "client/src/Hooks/v1/settingsHooks.js",
+ "simSteps": [
+ {
+ "simulationKey": "System Administration and Configuration",
+ "simStepId": "383387e5-4623-44ab-b2c4-d9ea046f4547"
+ }
+ ]
+ },
+ "fbe9b160-307e-4f51-8f72-713fda5373c8": {
+ "path": "fbe9b160-307e-4f51-8f72-713fda5373c8",
+ "cellName": "API Response:\nConfirm Settings\nUpdate",
+ "cellId": "fbe9b160-307e-4f51-8f72-713fda5373c8",
+ "visible": true
+ },
+ "generated-edge-simstep-0afcde35-7d51-4939-aae8-bac5ac83c49d-fbe9b160-307e-4f51-8f72-713fda5373c8": {
+ "path": "generated-edge-simstep-0afcde35-7d51-4939-aae8-bac5ac83c49d-fbe9b160-307e-4f51-8f72-713fda5373c8",
+ "fileName": "settingsController.js",
+ "cellName": "API Response: Confirm Settings Update",
+ "cellId": "fbe9b160-307e-4f51-8f72-713fda5373c8",
+ "visible": true,
+ "startLine": 60,
+ "endLine": 63,
+ "parentPath": "server/src/controllers/v1/settingsController.js",
+ "simSteps": [
+ {
+ "simulationKey": "System Administration and Configuration",
+ "simStepId": "0afcde35-7d51-4939-aae8-bac5ac83c49d"
+ }
+ ]
+ },
+ "499dfe1f-77d4-4b26-a12c-45240691109e": {
+ "path": "499dfe1f-77d4-4b26-a12c-45240691109e",
+ "cellName": "API Call:\nFetch System\nDiagnostics",
+ "cellId": "499dfe1f-77d4-4b26-a12c-45240691109e",
+ "visible": true
+ },
+ "generated-edge-simstep-23b8ad7d-8967-4e48-814c-1a7bf5b4dfac-499dfe1f-77d4-4b26-a12c-45240691109e": {
+ "path": "generated-edge-simstep-23b8ad7d-8967-4e48-814c-1a7bf5b4dfac-499dfe1f-77d4-4b26-a12c-45240691109e",
+ "fileName": "index.jsx",
+ "cellName": "API Call: Fetch System Diagnostics",
+ "cellId": "499dfe1f-77d4-4b26-a12c-45240691109e",
+ "visible": true,
+ "startLine": 1113,
+ "endLine": 1119,
+ "parentPath": "client/src/Pages/v1/Logs/Diagnostics/index.jsx",
+ "simSteps": [
+ {
+ "simulationKey": "System Administration and Configuration",
+ "simStepId": "23b8ad7d-8967-4e48-814c-1a7bf5b4dfac"
+ }
+ ]
+ },
+ "d0f92604-a6fa-4d85-9716-ea8f32db78e0": {
+ "path": "d0f92604-a6fa-4d85-9716-ea8f32db78e0",
+ "cellName": "API Response:\nSend Diagnostics\nData to\nClient",
+ "cellId": "d0f92604-a6fa-4d85-9716-ea8f32db78e0",
+ "visible": true
+ },
+ "generated-edge-simstep-44a4372f-a903-4c43-a9a5-c7549c875e11-d0f92604-a6fa-4d85-9716-ea8f32db78e0": {
+ "path": "generated-edge-simstep-44a4372f-a903-4c43-a9a5-c7549c875e11-d0f92604-a6fa-4d85-9716-ea8f32db78e0",
+ "fileName": "diagnosticService.js",
+ "cellName": "API Response: Send Diagnostics Data to Client",
+ "cellId": "d0f92604-a6fa-4d85-9716-ea8f32db78e0",
+ "visible": true,
+ "startLine": 52,
+ "endLine": 55,
+ "parentPath": "server/src/service/v1/business/diagnosticService.js",
+ "simSteps": [
+ {
+ "simulationKey": "System Administration and Configuration",
+ "simStepId": "44a4372f-a903-4c43-a9a5-c7549c875e11"
+ }
+ ]
+ }
+ },
+ "simulations": {
+ "Comprehensive Uptime Monitoring for Various Services": {
+ "name": "Comprehensive Uptime Monitoring for Various Services",
+ "simSteps": [
+ {
+ "simStepId": "ab5de0b9-c70d-492c-98d9-8ddee66284ad",
+ "diagramNodeId": "7a2da928-a65c-49d8-b0a3-c159a6df2bc5",
+ "simStepLabel": "Uptime Monitor Creation: Display Form",
+ "simStepDescription": "The user navigates to the 'Create Uptime Monitor' page. The `UptimeCreate` React component renders the form, allowing the user to select the monitor type (HTTP, Ping, Docker, etc.) and input details like URL, name, and check interval.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Uptime/Create/index.jsx",
+ "startLine": "315",
+ "endLine": "802",
+ "relevantVariables": [
+ "UptimeCreate",
+ "monitor",
+ "setMonitor",
+ "onChange",
+ "onSubmit"
+ ]
+ },
+ "inputDataExample": "{}",
+ "outputDataExample": "{\n \"type\": \"http\",\n \"statusWindowSize\": 5,\n \"statusWindowThreshold\": 60,\n \"matchMethod\": \"equal\",\n \"interval\": 60000,\n \"ignoreTlsErrors\": false,\n \"notifications\": [],\n \"url\": \"\",\n \"name\": \"\"\n}"
+ },
+ {
+ "simStepId": "888df086-41bb-469d-9c0a-daf74bdecb9f",
+ "diagramNodeId": "0e09aa33-d7c7-4c89-9da8-f0937658f4d3",
+ "simStepLabel": "Uptime Monitor Creation: Form Submission",
+ "simStepDescription": "After filling the form, the user clicks the 'Create monitor' button, which triggers the `onSubmit` event handler, passing the current state of the form.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Uptime/Create/index.jsx",
+ "startLine": "435",
+ "endLine": "443",
+ "relevantVariables": [
+ "onSubmit"
+ ]
+ },
+ "inputDataExample": "{\n \"type\": \"http\",\n \"url\": \"https://google.com\",\n \"name\": \"My Google Monitor\",\n \"interval\": 60000,\n \"notifications\": []\n}",
+ "outputDataExample": "{\n \"type\": \"http\",\n \"url\": \"https://google.com\",\n \"name\": \"My Google Monitor\",\n \"interval\": 60000,\n \"notifications\": []\n}"
+ },
+ {
+ "simStepId": "e02ddf57-270e-4f71-b4c4-d5183bb7879a",
+ "diagramNodeId": "a81f4ae8-41fe-4d1b-95ce-86abcc67b53c",
+ "simStepLabel": "Uptime Monitor Creation: Handle Form Submission and Validation",
+ "simStepDescription": "The `onSubmit` function in `UptimeCreate.jsx` is executed. It prevents the default form submission, constructs a `form` object with monitor data, validates it using `monitorValidation`, and then calls the `createMonitor` function from the `useCreateMonitor` hook.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Uptime/Create/index.jsx",
+ "startLine": "170",
+ "endLine": "248",
+ "relevantVariables": [
+ "onSubmit",
+ "monitorValidation",
+ "createMonitor"
+ ]
+ },
+ "inputDataExample": "{\n \"type\": \"http\",\n \"url\": \"google.com\",\n \"name\": \"My Google Monitor\",\n \"interval\": 60000,\n \"notifications\": []\n}",
+ "outputDataExample": "{\n \"monitor\": {\n \"url\": \"http://google.com\",\n \"name\": \"My Google Monitor\",\n \"statusWindowSize\": 5,\n \"statusWindowThreshold\": 60,\n \"type\": \"http\",\n \"interval\": 60000,\n \"description\": \"My Google Monitor\",\n \"notifications\": []\n },\n \"redirect\": \"/uptime\"\n}"
+ },
+ {
+ "simStepId": "3fe0afaa-c673-4b52-a0c4-02a3ffd3a075",
+ "diagramNodeId": "1f6f04a9-0123-4729-b06a-8f3da6d1b58c",
+ "simStepLabel": "Uptime Monitor Creation: Call Network Service",
+ "simStepDescription": "The `createMonitor` function from the `useCreateMonitor` hook is called, which in turn calls `networkService.createMonitor` to send the monitor data to the backend API.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Hooks/v1/monitorHooks.js",
+ "startLine": "306",
+ "endLine": "306",
+ "relevantVariables": [
+ "networkService.createMonitor"
+ ]
+ },
+ "inputDataExample": "{\n \"monitor\": {\n \"url\": \"http://google.com\",\n \"name\": \"My Google Monitor\",\n \"type\": \"http\",\n \"interval\": 60000,\n \"notifications\": []\n }\n}",
+ "outputDataExample": "{\n \"monitor\": {\n \"url\": \"http://google.com\",\n \"name\": \"My Google Monitor\",\n \"type\": \"http\",\n \"interval\": 60000,\n \"notifications\": []\n }\n}"
+ },
+ {
+ "simStepId": "14e9986b-c6ce-475e-97ed-da1fa2b6968d",
+ "diagramNodeId": "a30ac765-c546-4993-988e-8c8cfdb7532f",
+ "simStepLabel": "Uptime Monitor Creation: Send API Request",
+ "simStepDescription": "The `createMonitor` method in `NetworkService.js` makes an HTTP POST request to the `/monitors` endpoint on the server, sending the new monitor's data in the request body.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Utils/NetworkService.js",
+ "startLine": "119",
+ "endLine": "121",
+ "relevantVariables": [
+ "createMonitor",
+ "this.axiosInstance.post"
+ ]
+ },
+ "inputDataExample": "{\n \"monitor\": {\n \"url\": \"http://google.com\",\n \"name\": \"My Google Monitor\",\n \"type\": \"http\",\n \"interval\": 60000,\n \"notifications\": []\n }\n}",
+ "outputDataExample": "{\n \"status\": 200,\n \"data\": {\n \"_id\": \"60d21b4667d0d8992e610c85\",\n \"name\": \"My Google Monitor\",\n \"url\": \"http://google.com\",\n \"type\": \"http\",\n \"interval\": 60000,\n \"isActive\": true\n }\n}"
+ },
+ {
+ "simStepId": "13dd0564-44a5-43ea-a163-ce1148cc48e7",
+ "diagramNodeId": "b6d963d5-c613-407a-9ce9-60a04048dea7",
+ "simStepLabel": "Uptime Monitor Creation: API Route to Controller",
+ "simStepDescription": "The server's router receives the POST request on `/api/v1/monitors` and forwards it to the `createMonitor` method in the `MonitorController`.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/routes/v1/monitorRoute.js",
+ "startLine": "35",
+ "endLine": "35",
+ "relevantVariables": [
+ "this.router.post",
+ "this.monitorController.createMonitor"
+ ]
+ },
+ "inputDataExample": "{\n \"headers\": {\n \"Authorization\": \"Bearer \"\n },\n \"body\": {\n \"name\": \"My Google Monitor\",\n \"url\": \"http://google.com\",\n \"type\": \"http\",\n \"interval\": 60000\n }\n}",
+ "outputDataExample": "{\n \"headers\": {\n \"Authorization\": \"Bearer \"\n },\n \"body\": {\n \"name\": \"My Google Monitor\",\n \"url\": \"http://google.com\",\n \"type\": \"http\",\n \"interval\": 60000\n }\n}"
+ },
+ {
+ "simStepId": "38fd069d-9c44-4078-954b-3a0899e9acf4",
+ "diagramNodeId": "f0409948-75dc-457f-a02d-6b9774258e48",
+ "simStepLabel": "Uptime Monitor Creation: Controller to Service",
+ "simStepDescription": "The `MonitorController` extracts the user and team ID from the request and calls the `createMonitor` method in the `MonitorService`, passing along the monitor data from the request body.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/monitorController.js",
+ "startLine": "195",
+ "endLine": "195",
+ "relevantVariables": [
+ "this.monitorService.createMonitor"
+ ]
+ },
+ "inputDataExample": "{\n \"user\": {\n \"_id\": \"user-123\",\n \"teamId\": \"team-abc\"\n },\n \"body\": {\n \"name\": \"My Google Monitor\",\n \"url\": \"http://google.com\",\n \"type\": \"http\"\n }\n}",
+ "outputDataExample": "{\n \"teamId\": \"team-abc\",\n \"userId\": \"user-123\",\n \"body\": {\n \"name\": \"My Google Monitor\",\n \"url\": \"http://google.com\",\n \"type\": \"http\"\n }\n}"
+ },
+ {
+ "simStepId": "a6f4c967-b1a8-4804-bc2e-67f47a7b68f9",
+ "diagramNodeId": "a6bccd1d-4272-4df7-a7c8-b3979e37b8d7",
+ "simStepLabel": "Uptime Monitor Creation: Service to DB Module",
+ "simStepDescription": "The `MonitorService` calls the `createMonitor` method of the `monitorModule` to handle the database interaction.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/business/monitorService.js",
+ "startLine": "74",
+ "endLine": "78",
+ "relevantVariables": [
+ "this.db.monitorModule.createMonitor"
+ ]
+ },
+ "inputDataExample": "{\n \"teamId\": \"team-abc\",\n \"userId\": \"user-123\",\n \"body\": {\n \"name\": \"My Google Monitor\",\n \"url\": \"http://google.com\",\n \"type\": \"http\"\n }\n}",
+ "outputDataExample": "{\n \"teamId\": \"team-abc\",\n \"userId\": \"user-123\",\n \"body\": {\n \"name\": \"My Google Monitor\",\n \"url\": \"http://google.com\",\n \"type\": \"http\"\n }\n}"
+ },
+ {
+ "simStepId": "ca3ad309-323a-418d-a171-c16f56fae23c",
+ "diagramNodeId": "2d407bfd-e4b0-46b8-99e7-121c507d525f",
+ "simStepLabel": "Uptime Monitor Creation: Save Monitor to Database",
+ "simStepDescription": "The `monitorModule` creates a new `Monitor` model instance with the provided data and saves it to the MongoDB database. The newly created document, including its generated `_id`, is returned.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/db/v1/modules/monitorModule.js",
+ "startLine": "454",
+ "endLine": "455",
+ "relevantVariables": [
+ "this.Monitor",
+ "monitor.save"
+ ]
+ },
+ "inputDataExample": "{\n \"teamId\": \"team-abc\",\n \"userId\": \"user-123\",\n \"body\": {\n \"name\": \"My Google Monitor\",\n \"url\": \"http://google.com\",\n \"type\": \"http\"\n }\n}",
+ "outputDataExample": "{\n \"_id\": \"60d21b4667d0d8992e610c85\",\n \"name\": \"My Google Monitor\",\n \"url\": \"http://google.com\",\n \"type\": \"http\",\n \"interval\": 60000,\n \"userId\": \"user-123\",\n \"teamId\": \"team-abc\",\n \"isActive\": true,\n \"createdAt\": \"2023-01-01T12:00:00Z\",\n \"updatedAt\": \"2023-01-01T12:00:00Z\"\n}"
+ },
+ {
+ "simStepId": "71ec03b6-1166-4c5d-9ada-0875a0866e30",
+ "diagramNodeId": "de320ac1-0949-4297-be7c-b9d7a14e0fec",
+ "simStepLabel": "Uptime Monitor Creation: Service to Job Queue",
+ "simStepDescription": "After successfully saving the monitor to the database, the `MonitorService` returns the new monitor object and then calls `jobQueue.addJob` to schedule the first and subsequent checks for this monitor.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/business/monitorService.js",
+ "startLine": "80",
+ "endLine": "80",
+ "relevantVariables": [
+ "this.jobQueue.addJob"
+ ]
+ },
+ "inputDataExample": "{\n \"_id\": \"60d21b4667d0d8992e610c85\",\n \"name\": \"My Google Monitor\",\n \"url\": \"http://google.com\",\n \"type\": \"http\",\n \"interval\": 60000,\n \"isActive\": true\n}",
+ "outputDataExample": "{\n \"_id\": \"60d21b4667d0d8992e610c85\",\n \"name\": \"My Google Monitor\",\n \"url\": \"http://google.com\",\n \"type\": \"http\",\n \"interval\": 60000,\n \"isActive\": true\n}"
+ },
+ {
+ "simStepId": "2702bfca-7611-4562-8d3e-a05af8257871",
+ "diagramNodeId": "51e8db5f-2fdd-4ddc-9605-43981db9d940",
+ "simStepLabel": "Uptime Monitor Creation: Schedule Job",
+ "simStepDescription": "The `SuperSimpleQueue` receives the new monitor and uses its `scheduler.addJob` method to create a recurring job based on the monitor's interval. The monitor object itself is stored as data for the job.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js",
+ "startLine": "50",
+ "endLine": "57",
+ "relevantVariables": [
+ "addJob",
+ "this.scheduler.addJob"
+ ]
+ },
+ "inputDataExample": "{\n \"_id\": \"60d21b4667d0d8992e610c85\",\n \"name\": \"My Google Monitor\",\n \"url\": \"http://google.com\",\n \"type\": \"http\",\n \"interval\": 60000,\n \"isActive\": true\n}",
+ "outputDataExample": "{\n \"id\": \"60d21b4667d0d8992e610c85\",\n \"template\": \"monitor-job\",\n \"repeat\": 60000,\n \"active\": true,\n \"data\": { ...monitor object ... }\n}"
+ },
+ {
+ "simStepId": "e22a708a-a348-4775-8ee3-f32a35446e24",
+ "diagramNodeId": "18c13391-0cf7-4303-8180-0a40cfd64b58",
+ "simStepLabel": "Service Monitoring: Job Execution",
+ "simStepDescription": "The scheduler triggers a monitor job at the specified interval. The `getMonitorJob` function in `SuperSimpleQueueHelper` is executed, which first checks if the monitor is in a maintenance window.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js",
+ "startLine": "27",
+ "endLine": "73",
+ "relevantVariables": [
+ "getMonitorJob",
+ "isInMaintenanceWindow"
+ ]
+ },
+ "inputDataExample": "{\n \"id\": \"60d21b4667d0d8992e610c85\",\n \"data\": {\n \"_id\": \"60d21b4667d0d8992e610c85\",\n \"name\": \"My Google Monitor\",\n \"url\": \"http://google.com\",\n \"type\": \"http\",\n \"interval\": 60000\n }\n}",
+ "outputDataExample": "{\n \"_id\": \"60d21b4667d0d8992e610c85\",\n \"name\": \"My Google Monitor\",\n \"url\": \"http://google.com\",\n \"type\": \"http\",\n \"interval\": 60000\n}"
+ },
+ {
+ "simStepId": "1d7580f2-f107-4008-a207-b8ec07998721",
+ "diagramNodeId": "38ba9b22-5b26-432d-a856-0eceb792c737",
+ "simStepLabel": "Service Monitoring: Request Service Status",
+ "simStepDescription": "Assuming no active maintenance, the job helper calls `networkService.requestStatus`, passing the monitor object to perform the actual availability check.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js",
+ "startLine": "45",
+ "endLine": "45",
+ "relevantVariables": [
+ "this.networkService.requestStatus"
+ ]
+ },
+ "inputDataExample": "{\n \"_id\": \"60d21b4667d0d8992e610c85\",\n \"name\": \"My Google Monitor\",\n \"url\": \"http://google.com\",\n \"type\": \"http\",\n \"interval\": 60000\n}",
+ "outputDataExample": "{\n \"_id\": \"60d21b4667d0d8992e610c85\",\n \"name\": \"My Google Monitor\",\n \"url\": \"http://google.com\",\n \"type\": \"http\",\n \"interval\": 60000\n}"
+ },
+ {
+ "simStepId": "84f82fea-e306-4e0b-8222-e9705803d51a",
+ "diagramNodeId": "5c900373-e6ba-44d5-b341-676a32d659c8",
+ "simStepLabel": "Service Monitoring: Perform Network Check",
+ "simStepDescription": "The `requestStatus` method in the network service acts as a router. Based on the monitor's `type` (e.g., 'http', 'ping', 'docker'), it calls the corresponding specialized request function (e.g., `requestHttp`) to perform the check and constructs a standardized response object.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/infrastructure/networkService.js",
+ "startLine": "44",
+ "endLine": "59",
+ "relevantVariables": [
+ "requestStatus",
+ "requestHttp",
+ "requestPing",
+ "requestDocker",
+ "requestPort",
+ "requestGame"
+ ]
+ },
+ "inputDataExample": "{\n \"_id\": \"60d21b4667d0d8992e610c85\",\n \"name\": \"My Google Monitor\",\n \"url\": \"http://google.com\",\n \"type\": \"http\"\n}",
+ "outputDataExample": "{\n \"monitorId\": \"60d21b4667d0d8992e610c85\",\n \"type\": \"http\",\n \"status\": true,\n \"code\": 200,\n \"responseTime\": 150,\n \"message\": \"OK\"\n}"
+ },
+ {
+ "simStepId": "9b0ada30-d3e6-4b2c-b3ff-f50615139588",
+ "diagramNodeId": "5e80734c-7b72-41be-9068-30cda3ffb425",
+ "simStepLabel": "Service Monitoring: Propagate Network Response",
+ "simStepDescription": "The network response object, containing the result of the check (status, response time, status code), is returned to the job handler in `SuperSimpleQueueHelper`.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js",
+ "startLine": "45",
+ "endLine": "45",
+ "relevantVariables": [
+ "networkResponse"
+ ]
+ },
+ "inputDataExample": "{\n \"monitorId\": \"60d21b4667d0d8992e610c85\",\n \"type\": \"http\",\n \"status\": true,\n \"code\": 200,\n \"responseTime\": 150,\n \"message\": \"OK\"\n}",
+ "outputDataExample": "{\n \"monitorId\": \"60d21b4667d0d8992e610c85\",\n \"type\": \"http\",\n \"status\": true,\n \"code\": 200,\n \"responseTime\": 150,\n \"message\": \"OK\"\n}"
+ },
+ {
+ "simStepId": "c66a04c6-113e-4391-ab71-93fc612279ef",
+ "diagramNodeId": "45e38f3a-a7c3-4de7-b3dc-589361d793fd",
+ "simStepLabel": "Service Monitoring: Update Monitor Status",
+ "simStepDescription": "The job handler passes the network response to `statusService.updateStatus`. This service creates a new `Check` document in the database, updates the monitor's status based on a sliding window and threshold, and calculates running statistics.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/infrastructure/statusService.js",
+ "startLine": "108",
+ "endLine": "183",
+ "relevantVariables": [
+ "updateStatus",
+ "buildCheck",
+ "insertCheck",
+ "updateRunningStats"
+ ]
+ },
+ "inputDataExample": "{\n \"monitorId\": \"60d21b4667d0d8992e610c85\",\n \"status\": true,\n \"responseTime\": 150,\n \"code\": 200\n}",
+ "outputDataExample": "{\n \"monitor\": { ...updated monitor object ... },\n \"statusChanged\": false,\n \"prevStatus\": true\n}"
+ },
+ {
+ "simStepId": "9896c387-4fca-47ad-82eb-7301286decb7",
+ "diagramNodeId": "0a7e4a72-b644-4bd8-afa1-ae8a1d6c111c",
+ "simStepLabel": "Service Monitoring: Check for Status Change",
+ "simStepDescription": "The result from `statusService`, indicating whether the monitor's overall status changed (e.g., from 'up' to 'down'), is returned to the job handler.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js",
+ "startLine": "51",
+ "endLine": "51",
+ "relevantVariables": [
+ "updatedMonitor",
+ "statusChanged",
+ "prevStatus"
+ ]
+ },
+ "inputDataExample": "{\n \"monitor\": { ...updated monitor object ... },\n \"statusChanged\": false,\n \"prevStatus\": true\n}",
+ "outputDataExample": "{\n \"monitor\": { ...updated monitor object ... },\n \"statusChanged\": false,\n \"prevStatus\": true\n}"
+ },
+ {
+ "simStepId": "1a260a75-cf50-4f7a-90b0-c280b512a5cb",
+ "diagramNodeId": "9f3ea7fc-16d3-4d36-97eb-66f8808425cc",
+ "simStepLabel": "Service Monitoring: Handle Notifications (Conditional)",
+ "simStepDescription": "If `statusChanged` is true, the job handler calls `notificationService.handleNotifications`. The service identifies the notification channels associated with the monitor and sends out alerts (e.g., email, webhook, Discord) with details about the status change.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/infrastructure/notificationService.js",
+ "startLine": "76",
+ "endLine": "92",
+ "relevantVariables": [
+ "handleNotifications",
+ "notifyAll",
+ "buildStatusEmail",
+ "buildWebhookMessage",
+ "buildHardwareAlerts"
+ ]
+ },
+ "inputDataExample": "{\n \"monitorId\": \"60d21b4667d0d8992e610c85\",\n \"status\": false,\n \"monitor\": { ... },\n \"statusChanged\": true,\n \"prevStatus\": true\n}",
+ "outputDataExample": "{}"
+ }
+ ],
+ "description": "The core feature of Checkmate is to monitor the availability and response time of services Users can configure different types of monitors for various needs - Supports multiple protocols including HTTP(s), Ping, Port, and Docker containers - Users can set the check frequency, from one to five minutes, to define how often the service is checked - Advanced HTTP matching is available, allowing checks against JSON responses using JMESPath, regex, or simple inclusion/equality - Game server monitoring is also a specific type of monitor supported by the application ",
+ "simulationNodesAndEdges": {
+ "7a2da928-a65c-49d8-b0a3-c159a6df2bc5": {
+ "simStepIds": [
+ "ab5de0b9-c70d-492c-98d9-8ddee66284ad"
+ ]
+ },
+ "a81f4ae8-41fe-4d1b-95ce-86abcc67b53c": {
+ "simStepIds": [
+ "e02ddf57-270e-4f71-b4c4-d5183bb7879a"
+ ]
+ },
+ "a30ac765-c546-4993-988e-8c8cfdb7532f": {
+ "simStepIds": [
+ "14e9986b-c6ce-475e-97ed-da1fa2b6968d"
+ ]
+ },
+ "f0409948-75dc-457f-a02d-6b9774258e48": {
+ "simStepIds": [
+ "38fd069d-9c44-4078-954b-3a0899e9acf4"
+ ]
+ },
+ "2d407bfd-e4b0-46b8-99e7-121c507d525f": {
+ "simStepIds": [
+ "ca3ad309-323a-418d-a171-c16f56fae23c"
+ ]
+ },
+ "51e8db5f-2fdd-4ddc-9605-43981db9d940": {
+ "simStepIds": [
+ "2702bfca-7611-4562-8d3e-a05af8257871"
+ ]
+ },
+ "18c13391-0cf7-4303-8180-0a40cfd64b58": {
+ "simStepIds": [
+ "e22a708a-a348-4775-8ee3-f32a35446e24"
+ ]
+ },
+ "5c900373-e6ba-44d5-b341-676a32d659c8": {
+ "simStepIds": [
+ "84f82fea-e306-4e0b-8222-e9705803d51a"
+ ]
+ },
+ "45e38f3a-a7c3-4de7-b3dc-589361d793fd": {
+ "simStepIds": [
+ "c66a04c6-113e-4391-ab71-93fc612279ef"
+ ]
+ },
+ "9f3ea7fc-16d3-4d36-97eb-66f8808425cc": {
+ "simStepIds": [
+ "1a260a75-cf50-4f7a-90b0-c280b512a5cb"
+ ]
+ },
+ "0e09aa33-d7c7-4c89-9da8-f0937658f4d3": {
+ "simStepIds": [
+ "888df086-41bb-469d-9c0a-daf74bdecb9f"
+ ]
+ },
+ "1f6f04a9-0123-4729-b06a-8f3da6d1b58c": {
+ "simStepIds": [
+ "3fe0afaa-c673-4b52-a0c4-02a3ffd3a075"
+ ]
+ },
+ "b6d963d5-c613-407a-9ce9-60a04048dea7": {
+ "simStepIds": [
+ "13dd0564-44a5-43ea-a163-ce1148cc48e7"
+ ]
+ },
+ "a6bccd1d-4272-4df7-a7c8-b3979e37b8d7": {
+ "simStepIds": [
+ "a6f4c967-b1a8-4804-bc2e-67f47a7b68f9"
+ ]
+ },
+ "de320ac1-0949-4297-be7c-b9d7a14e0fec": {
+ "simStepIds": [
+ "71ec03b6-1166-4c5d-9ada-0875a0866e30"
+ ]
+ },
+ "38ba9b22-5b26-432d-a856-0eceb792c737": {
+ "simStepIds": [
+ "1d7580f2-f107-4008-a207-b8ec07998721"
+ ]
+ },
+ "5e80734c-7b72-41be-9068-30cda3ffb425": {
+ "simStepIds": [
+ "9b0ada30-d3e6-4b2c-b3ff-f50615139588"
+ ]
+ },
+ "0a7e4a72-b644-4bd8-afa1-ae8a1d6c111c": {
+ "simStepIds": [
+ "9896c387-4fca-47ad-82eb-7301286decb7"
+ ]
+ }
+ },
+ "isAIGenerated": true,
+ "keywords": "Monitor, checkService, UptimeCreate",
+ "generationPrompt": "Comprehensive Uptime Monitoring for Various Services",
+ "generationKeywords": "Monitor, checkService, UptimeCreate"
+ },
+ "Infrastructure and Hardware Health Monitoring": {
+ "name": "Infrastructure and Hardware Health Monitoring",
+ "simSteps": [
+ {
+ "simStepId": "80dd889a-1fc9-4d56-b9cf-5b45bf1110b1",
+ "diagramNodeId": "9651d084-5c16-4417-89c1-f52ae6572a95",
+ "simStepLabel": "Configuration: User Sets Alert Thresholds",
+ "simStepDescription": "A user navigates to the 'Create Infrastructure Monitor' page to set up monitoring for a new server. They enable alerts for CPU, memory, and disk usage, and define specific thresholds for each metric. For example, an alert is configured to trigger if CPU usage exceeds 80%.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx",
+ "startLine": "45",
+ "endLine": "53",
+ "relevantVariables": [
+ "CustomThreshold",
+ "METRICS",
+ "METRIC_PREFIX",
+ "handleCheckboxChange",
+ "infrastructureMonitor"
+ ]
+ },
+ "inputDataExample": "{\n \"infrastructureMonitor\": {\n \"url\": \"my-server.example.com\",\n \"name\": \"Production Web Server\",\n \"cpu\": false,\n \"usage_cpu\": \"\",\n \"memory\": false,\n \"usage_memory\": \"\",\n \"disk\": false,\n \"usage_disk\": \"\"\n }\n}",
+ "outputDataExample": "{\n \"infrastructureMonitor\": {\n \"url\": \"my-server.example.com\",\n \"name\": \"Production Web Server\",\n \"cpu\": true,\n \"usage_cpu\": \"80\",\n \"memory\": true,\n \"usage_memory\": \"75\",\n \"disk\": true,\n \"usage_disk\": \"90\"\n }\n}"
+ },
+ {
+ "simStepId": "815f96af-c28e-4f80-b4ab-18a6fb23eee0",
+ "diagramNodeId": "1c5c9857-bf32-40e4-921b-957c252dac2a",
+ "simStepLabel": "API Call: Submit Monitor Configuration",
+ "simStepDescription": "After configuring the monitor and its alert thresholds, the user saves the configuration. The frontend application constructs a monitor object with `type: 'hardware'` and sends it to the backend via a POST request to create the new monitor.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Hooks/v1/monitorHooks.js",
+ "startLine": "386",
+ "endLine": "389",
+ "relevantVariables": [
+ "updatedFields",
+ "monitor.type",
+ "monitor.thresholds",
+ "monitor.secret"
+ ]
+ },
+ "inputDataExample": "{\n \"monitor\": {\n \"name\": \"Production Web Server\",\n \"url\": \"http://my-server.example.com\",\n \"type\": \"hardware\",\n \"interval\": 15000,\n \"secret\": \"super-secret-key\",\n \"thresholds\": {\n \"usage_cpu\": 0.8,\n \"usage_memory\": 0.75,\n \"usage_disk\": 0.9\n }\n }\n}",
+ "outputDataExample": "{\n \"monitor\": {\n \"name\": \"Production Web Server\",\n \"url\": \"http://my-server.example.com\",\n \"type\": \"hardware\",\n \"interval\": 15000,\n \"secret\": \"super-secret-key\",\n \"thresholds\": {\n \"usage_cpu\": 0.8,\n \"usage_memory\": 0.75,\n \"usage_disk\": 0.9\n }\n }\n}"
+ },
+ {
+ "simStepId": "ed6e7faf-69dd-4d0b-bda8-137739ed288b",
+ "diagramNodeId": "4c47b5f2-5363-4816-9745-30e61d184c7a",
+ "simStepLabel": "Automated Check: Backend Fetches Hardware Data",
+ "simStepDescription": "The Checkmate server's job queue periodically initiates a check for the newly created hardware monitor. It makes an HTTP request to the monitor's URL, which points to the external 'Capture' agent running on the target server.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/infrastructure/networkService.js",
+ "startLine": "7",
+ "endLine": "14",
+ "relevantVariables": [
+ "TYPE_HARDWARE"
+ ]
+ },
+ "inputDataExample": "{\n \"monitor\": {\n \"_id\": \"65ada27a51442afd52857053\",\n \"name\": \"Production Web Server\",\n \"url\": \"http://my-server.example.com\",\n \"type\": \"hardware\",\n \"interval\": 15000\n }\n}",
+ "outputDataExample": "{\n \"networkResponse\": {\n \"ok\": true,\n \"status\": 200,\n \"statusText\": \"OK\",\n \"payload\": {\n \"data\": {\n \"cpu\": {\n \"usage_percent\": 0.85\n },\n \"memory\": {\n \"usage_percent\": 0.65\n },\n \"disk\": [\n {\n \"usage_percent\": 0.5\n }\n ]\n }\n }\n }\n}"
+ },
+ {
+ "simStepId": "4434dca1-2048-4458-a70b-b70d569e378d",
+ "diagramNodeId": "a64547c9-d9e6-4941-8e5e-f49add06516b",
+ "simStepLabel": "Automated Check: Data Received from Capture Agent",
+ "simStepDescription": "The 'Capture' agent responds to the server's request with a JSON payload containing detailed hardware metrics, including CPU usage, memory consumption, disk space, and network statistics.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/infrastructure/statusService.js",
+ "startLine": "266",
+ "endLine": "273",
+ "relevantVariables": [
+ "payload",
+ "cpu",
+ "memory",
+ "disk",
+ "host"
+ ]
+ },
+ "inputDataExample": "{\n \"data\": {\n \"cpu\": {\n \"physical_core\": 4,\n \"logical_core\": 8,\n \"frequency\": 3400,\n \"usage_percent\": 0.85,\n \"temperature\": [65, 66, 65, 67]\n },\n \"memory\": {\n \"total_bytes\": 17179869184,\n \"used_bytes\": 11166914970,\n \"usage_percent\": 0.65\n },\n \"disk\": [\n {\n \"name\": \"/dev/sda1\",\n \"total_bytes\": 250685575168,\n \"used_bytes\": 125342787584,\n \"usage_percent\": 0.5\n }\n ],\n \"host\": {\n \"os\": \"Ubuntu 22.04 LTS\"\n }\n },\n \"errors\": []\n}",
+ "outputDataExample": "{\n \"data\": {\n \"cpu\": {\n \"physical_core\": 4,\n \"logical_core\": 8,\n \"frequency\": 3400,\n \"usage_percent\": 0.85,\n \"temperature\": [65, 66, 65, 67]\n },\n \"memory\": {\n \"total_bytes\": 17179869184,\n \"used_bytes\": 11166914970,\n \"usage_percent\": 0.65\n },\n \"disk\": [\n {\n \"name\": \"/dev/sda1\",\n \"total_bytes\": 250685575168,\n \"used_bytes\": 125342787584,\n \"usage_percent\": 0.5\n }\n ],\n \"host\": {\n \"os\": \"Ubuntu 22.04 LTS\"\n }\n },\n \"errors\": []\n}"
+ },
+ {
+ "simStepId": "e5b90cf2-9369-4741-9803-c835a81c009b",
+ "diagramNodeId": "c1bf10c9-8c1a-44f4-8e23-39708f0301e8",
+ "simStepLabel": "Automated Check: Process and Store Hardware Data",
+ "simStepDescription": "The backend's StatusService receives the payload from the Capture agent. It parses the data and constructs a 'check' document. This document, containing the detailed hardware metrics, is then saved to the database for historical tracking and visualization.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/infrastructure/statusService.js",
+ "startLine": "265",
+ "endLine": "274",
+ "relevantVariables": [
+ "buildCheck",
+ "type",
+ "payload",
+ "check.cpu",
+ "check.memory",
+ "check.disk",
+ "check.host"
+ ]
+ },
+ "inputDataExample": "{\n \"networkResponse\": {\n \"type\": \"hardware\",\n \"payload\": {\n \"data\": {\n \"cpu\": { \"usage_percent\": 0.85 },\n \"memory\": { \"usage_percent\": 0.65 },\n \"disk\": [{ \"usage_percent\": 0.5 }],\n \"host\": { \"os\": \"Ubuntu 22.04 LTS\" }\n }\n }\n }\n}",
+ "outputDataExample": "{\n \"check\": {\n \"monitorId\": \"65ada27a51442afd52857053\",\n \"type\": \"hardware\",\n \"status\": true,\n \"cpu\": { \"usage_percent\": 0.85 },\n \"memory\": { \"usage_percent\": 0.65 },\n \"disk\": [{ \"usage_percent\": 0.5 }],\n \"host\": { \"os\": \"Ubuntu 22.04 LTS\" }\n }\n}"
+ },
+ {
+ "simStepId": "b019a6d0-a23a-4a1d-bd21-644f01996f14",
+ "diagramNodeId": "7a250d09-50c6-4935-b10c-626b0ebe1fba",
+ "simStepLabel": "Automated Check: Pass Data for Alert Evaluation",
+ "simStepDescription": "After storing the check data, the system passes the complete network response, including monitor details and the latest metrics, to the notification service to evaluate if any alerts need to be triggered.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/infrastructure/notificationService.js",
+ "startLine": "60",
+ "endLine": "79",
+ "relevantVariables": [
+ "handleNotifications",
+ "networkResponse",
+ "monitor.type",
+ "thresholds",
+ "metrics"
+ ]
+ },
+ "inputDataExample": "{\n \"networkResponse\": {\n \"monitor\": {\n \"_id\": \"65ada27a51442afd52857053\",\n \"name\": \"Production Web Server\",\n \"thresholds\": {\n \"usage_cpu\": 0.8\n },\n \"notifications\": [\"65a5b0b3e8a2b0e6e8c7b8e3\"]\n },\n \"payload\": {\n \"data\": {\n \"cpu\": { \"usage_percent\": 0.85 }\n }\n }\n }\n}",
+ "outputDataExample": "{\n \"networkResponse\": {\n \"monitor\": {\n \"_id\": \"65ada27a51442afd52857053\",\n \"name\": \"Production Web Server\",\n \"thresholds\": {\n \"usage_cpu\": 0.8\n },\n \"notifications\": [\"65a5b0b3e8a2b0e6e8c7b8e3\"]\n },\n \"payload\": {\n \"data\": {\n \"cpu\": { \"usage_percent\": 0.85 }\n }\n }\n }\n}"
+ },
+ {
+ "simStepId": "07fe8489-1d94-4afb-83f8-7cebb0dff49a",
+ "diagramNodeId": "d4f48f1a-61de-4cde-87d2-4b5215464899",
+ "simStepLabel": "Automated Check: Evaluate Data Against Thresholds",
+ "simStepDescription": "The notification utility function compares the received hardware metrics against the user-defined thresholds stored in the monitor's configuration. In this case, the CPU usage of 85% is greater than the 80% threshold, so an alert condition is met.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/infrastructure/notificationUtils.js",
+ "startLine": "95",
+ "endLine": "109",
+ "relevantVariables": [
+ "buildHardwareAlerts",
+ "thresholds",
+ "metrics",
+ "cpuThreshold",
+ "cpuUsage",
+ "alerts"
+ ]
+ },
+ "inputDataExample": "{\n \"networkResponse\": {\n \"monitor\": {\n \"thresholds\": {\n \"usage_cpu\": 0.8\n }\n },\n \"payload\": {\n \"data\": {\n \"cpu\": {\n \"usage_percent\": 0.85\n }\n }\n }\n }\n}",
+ "outputDataExample": "{\n \"alerts\": {\n \"cpu\": true,\n \"memory\": false,\n \"disk\": false\n }\n}"
+ },
+ {
+ "simStepId": "c691b3b9-88b3-4c88-a399-ff9fd5866194",
+ "diagramNodeId": "3d357de8-9fd2-4d0b-9623-f36fce4cf1de",
+ "simStepLabel": "Automated Check: Generate Alert Notification Content",
+ "simStepDescription": "Since the CPU threshold was breached, the system constructs a user-friendly alert message. For email notifications, it builds an HTML email detailing the alert, including the monitor name, the metric that triggered the alert, the current value, and the threshold.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/infrastructure/notificationUtils.js",
+ "startLine": "177",
+ "endLine": "183",
+ "relevantVariables": [
+ "buildHardwareEmail",
+ "networkResponse",
+ "alerts",
+ "template",
+ "context",
+ "subject",
+ "html"
+ ]
+ },
+ "inputDataExample": "{\n \"networkResponse\": {\n \"monitor\": {\n \"name\": \"Production Web Server\",\n \"url\": \"http://my-server.example.com\"\n }\n },\n \"alerts\": [\n \"Your current CPU usage (85%) is above your threshold (80%)\"\n ]\n}",
+ "outputDataExample": "{\n \"subject\": \"Monitor Production Web Server infrastructure alerts\",\n \"html\": \"...HTML content for the email...\"\n}"
+ },
+ {
+ "simStepId": "c27365db-9db6-4cc9-96f0-4f046fd38562",
+ "diagramNodeId": "801fe70a-1a19-406c-8f0d-770b0cb74409",
+ "simStepLabel": "Visualization: User Views Monitor Details",
+ "simStepDescription": "A user navigates to the details page for their 'Production Web Server' monitor in the Checkmate UI to view its performance. The React component for the details page mounts and prepares to fetch data.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Infrastructure/Details/index.jsx",
+ "startLine": "23",
+ "endLine": "31",
+ "relevantVariables": [
+ "InfrastructureDetails",
+ "useParams",
+ "useState",
+ "dateRange",
+ "monitorId"
+ ]
+ },
+ "inputDataExample": "{\n \"path\": \"/infrastructure/65ada27a51442afd52857053\"\n}",
+ "outputDataExample": "{\n \"monitorId\": \"65ada27a51442afd52857053\",\n \"dateRange\": \"recent\"\n}"
+ },
+ {
+ "simStepId": "a5d20172-1c95-4c9e-af39-f86a498734bb",
+ "diagramNodeId": "00921831-b1d9-4a5a-ae6f-5ddee7cfc1e4",
+ "simStepLabel": "API Call: Request for Historical Hardware Data",
+ "simStepDescription": "The frontend makes a GET request to the backend API to fetch the historical hardware data for the specified monitor and the selected time range (e.g., 'recent').",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Utils/NetworkService.js",
+ "startLine": "198",
+ "endLine": "206",
+ "relevantVariables": [
+ "getHardwareDetailsByMonitorId",
+ "config.monitorId",
+ "config.dateRange",
+ "this.axiosInstance.get"
+ ]
+ },
+ "inputDataExample": "{\n \"monitorId\": \"65ada27a51442afd52857053\",\n \"dateRange\": \"recent\"\n}",
+ "outputDataExample": "{\n \"request\": \"GET /api/v1/monitors/hardware/details/65ada27a51442afd52857053?dateRange=recent\"\n}"
+ },
+ {
+ "simStepId": "8502ae1f-f029-446e-9dde-59c2aba87a8a",
+ "diagramNodeId": "9ef7c423-f4fc-4caf-9bf5-c1aa3002ca03",
+ "simStepLabel": "Backend: Aggregate Historical Data",
+ "simStepDescription": "The backend receives the request and executes a database aggregation pipeline. This query fetches all 'hardware' checks for the monitor within the given date range and calculates metrics like average CPU usage, average memory usage, and average temperatures, grouped by time intervals.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/db/v1/modules/monitorModule.js",
+ "startLine": "312",
+ "endLine": "322",
+ "relevantVariables": [
+ "getHardwareDetailsById",
+ "monitorId",
+ "dateRange",
+ "buildHardwareDetailsPipeline",
+ "hardwareStats"
+ ]
+ },
+ "inputDataExample": "{\n \"monitorId\": \"65ada27a51442afd52857053\",\n \"dateRange\": \"recent\"\n}",
+ "outputDataExample": "{\n \"stats\": {\n \"checks\": [\n {\n \"_id\": \"2023-10-27T10:00:00Z\",\n \"avgCpuUsage\": 0.85,\n \"avgMemoryUsage\": 0.65\n },\n {\n \"_id\": \"2023-10-27T10:01:00Z\",\n \"avgCpuUsage\": 0.82,\n \"avgMemoryUsage\": 0.66\n }\n ],\n \"latestCheck\": {\n \"cpu\": { \"usage_percent\": 0.85 },\n \"memory\": { \"usage_percent\": 0.65 }\n }\n }\n}"
+ },
+ {
+ "simStepId": "627fcb0c-0282-4b4d-8c55-aceb636e4da0",
+ "diagramNodeId": "60308696-6f8a-43c8-b9a1-da199ef500cd",
+ "simStepLabel": "API Response: Send Aggregated Data to Frontend",
+ "simStepDescription": "The backend sends the aggregated hardware statistics back to the frontend in a structured JSON format, ready for visualization.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/db/v1/modules/monitorModuleQueries.js",
+ "startLine": "247",
+ "endLine": "262",
+ "relevantVariables": [
+ "buildHardwareDetailsPipeline",
+ "$group",
+ "avgCpuUsage",
+ "avgMemoryUsage",
+ "avgTemperatures"
+ ]
+ },
+ "inputDataExample": "{\n \"stats\": {\n \"checks\": [\n {\n \"_id\": \"2023-10-27T10:00:00Z\",\n \"avgCpuUsage\": 0.85,\n \"avgMemoryUsage\": 0.65,\n \"avgTemperature\": [65.5]\n },\n {\n \"_id\": \"2023-10-27T10:01:00Z\",\n \"avgCpuUsage\": 0.82,\n \"avgMemoryUsage\": 0.66,\n \"avgTemperature\": [66.0]\n }\n ],\n \"latestCheck\": {\n \"cpu\": { \"usage_percent\": 0.85, \"physical_core\": 4, \"frequency\": 3400 },\n \"memory\": { \"usage_percent\": 0.65, \"total_bytes\": 17179869184, \"used_bytes\": 11166914970 }\n }\n }\n}",
+ "outputDataExample": "{\n \"stats\": {\n \"checks\": [\n {\n \"_id\": \"2023-10-27T10:00:00Z\",\n \"avgCpuUsage\": 0.85,\n \"avgMemoryUsage\": 0.65,\n \"avgTemperature\": [65.5]\n },\n {\n \"_id\": \"2023-10-27T10:01:00Z\",\n \"avgCpuUsage\": 0.82,\n \"avgMemoryUsage\": 0.66,\n \"avgTemperature\": [66.0]\n }\n ],\n \"latestCheck\": {\n \"cpu\": { \"usage_percent\": 0.85, \"physical_core\": 4, \"frequency\": 3400 },\n \"memory\": { \"usage_percent\": 0.65, \"total_bytes\": 17179869184, \"used_bytes\": 11166914970 }\n }\n }\n}"
+ },
+ {
+ "simStepId": "6eedce82-b9c8-4022-95e5-3a6e00628105",
+ "diagramNodeId": "9ea2e32d-1ea8-42f2-9a17-613844723ae6",
+ "simStepLabel": "Visualization: Frontend Renders Charts and Gauges",
+ "simStepDescription": "The frontend components on the Infrastructure Details page receive the aggregated data. The 'GaugeBoxes' component uses the 'latestCheck' data to render real-time gauges for current CPU and memory usage, while the 'AreaChartBoxes' component uses the historical 'checks' data to render time-series charts.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/index.jsx",
+ "startLine": "22",
+ "endLine": "51",
+ "relevantVariables": [
+ "Gauges",
+ "latestCheck",
+ "memoryUsagePercent",
+ "cpuUsagePercent",
+ "gauges"
+ ]
+ },
+ "inputDataExample": "{\n \"monitor\": {\n \"stats\": {\n \"checks\": [\n { \"_id\": \"2023-10-27T10:00:00Z\", \"avgCpuUsage\": 0.85 },\n { \"_id\": \"2023-10-27T10:01:00Z\", \"avgCpuUsage\": 0.82 }\n ],\n \"latestCheck\": {\n \"cpu\": { \"usage_percent\": 0.85, \"physical_core\": 4, \"frequency\": 3400 },\n \"memory\": { \"usage_percent\": 0.65, \"total_bytes\": 17179869184, \"used_bytes\": 11166914970 }\n }\n }\n }\n}",
+ "outputDataExample": "{\n \"rendered_gauges\": [\n {\n \"type\": \"memory\",\n \"value\": \"65%\",\n \"heading\": \"Memory usage\"\n },\n {\n \"type\": \"cpu\",\n \"value\": \"85%\",\n \"heading\": \"CPU usage\"\n }\n ]\n}"
+ }
+ ],
+ "description": "Checkmate provides detailed monitoring of server hardware resources, requiring an external agent called 'Capture' - Tracks key metrics such as CPU usage, temperature, memory consumption, and disk space - Visualizes historical hardware data through detailed charts and real-time gauges - Users can configure alert thresholds for each metric (CPU, memory, disk, temperature) to get notified about potential issues proactively - Provides insights into network statistics, including data sent/received and packet information ",
+ "simulationNodesAndEdges": {
+ "9651d084-5c16-4417-89c1-f52ae6572a95": {
+ "simStepIds": [
+ "80dd889a-1fc9-4d56-b9cf-5b45bf1110b1"
+ ]
+ },
+ "4c47b5f2-5363-4816-9745-30e61d184c7a": {
+ "simStepIds": [
+ "ed6e7faf-69dd-4d0b-bda8-137739ed288b"
+ ]
+ },
+ "c1bf10c9-8c1a-44f4-8e23-39708f0301e8": {
+ "simStepIds": [
+ "e5b90cf2-9369-4741-9803-c835a81c009b"
+ ]
+ },
+ "d4f48f1a-61de-4cde-87d2-4b5215464899": {
+ "simStepIds": [
+ "07fe8489-1d94-4afb-83f8-7cebb0dff49a"
+ ]
+ },
+ "801fe70a-1a19-406c-8f0d-770b0cb74409": {
+ "simStepIds": [
+ "c27365db-9db6-4cc9-96f0-4f046fd38562"
+ ]
+ },
+ "9ef7c423-f4fc-4caf-9bf5-c1aa3002ca03": {
+ "simStepIds": [
+ "8502ae1f-f029-446e-9dde-59c2aba87a8a"
+ ]
+ },
+ "9ea2e32d-1ea8-42f2-9a17-613844723ae6": {
+ "simStepIds": [
+ "6eedce82-b9c8-4022-95e5-3a6e00628105"
+ ]
+ },
+ "1c5c9857-bf32-40e4-921b-957c252dac2a": {
+ "simStepIds": [
+ "815f96af-c28e-4f80-b4ab-18a6fb23eee0"
+ ]
+ },
+ "a64547c9-d9e6-4941-8e5e-f49add06516b": {
+ "simStepIds": [
+ "4434dca1-2048-4458-a70b-b70d569e378d"
+ ]
+ },
+ "7a250d09-50c6-4935-b10c-626b0ebe1fba": {
+ "simStepIds": [
+ "b019a6d0-a23a-4a1d-bd21-644f01996f14"
+ ]
+ },
+ "3d357de8-9fd2-4d0b-9623-f36fce4cf1de": {
+ "simStepIds": [
+ "c691b3b9-88b3-4c88-a399-ff9fd5866194"
+ ]
+ },
+ "00921831-b1d9-4a5a-ae6f-5ddee7cfc1e4": {
+ "simStepIds": [
+ "a5d20172-1c95-4c9e-af39-f86a498734bb"
+ ]
+ },
+ "60308696-6f8a-43c8-b9a1-da199ef500cd": {
+ "simStepIds": [
+ "627fcb0c-0282-4b4d-8c55-aceb636e4da0"
+ ]
+ }
+ },
+ "isAIGenerated": true,
+ "keywords": "hardware, InfrastructureDetails, cpu",
+ "generationPrompt": "Infrastructure and Hardware Health Monitoring",
+ "generationKeywords": "hardware, InfrastructureDetails, cpu"
+ },
+ "Multi-Channel Alerting and Notifications": {
+ "name": "Multi-Channel Alerting and Notifications",
+ "simSteps": [
+ {
+ "simStepId": "e924ec15-1bea-4e3b-befb-a8bf30cbf740",
+ "diagramNodeId": "15486629-84b3-463a-962b-06083f69e4bf",
+ "simStepLabel": "Flow 1: Render Notification Creation Form",
+ "simStepDescription": "User accesses the `/notifications/create` route, which renders the `CreateNotifications` component. This component displays a form for setting up a new notification channel, including fields for name, type, and provider-specific details.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Notifications/create/index.jsx",
+ "startLine": "157",
+ "endLine": "321",
+ "relevantVariables": [
+ "CreateNotifications",
+ "notification",
+ "onChange",
+ "onSubmit"
+ ]
+ },
+ "inputDataExample": "{\n \"route\": \"/notifications/create\"\n}",
+ "outputDataExample": "{\n \"renderedComponent\": \"CreateNotifications\",\n \"formFields\": [\"notificationName\", \"type\", \"address\", \"slackWebhookUrl\", etc...]\n}"
+ },
+ {
+ "simStepId": "71dbcd41-c6d4-4514-b5a4-6c966ca7ba6f",
+ "diagramNodeId": "54497b9a-4910-4df4-b016-480b9ac250a7",
+ "simStepLabel": "Flow 1: Submit Form Data for Creation",
+ "simStepDescription": "After filling the form, the user clicks 'Save'. The `onSubmit` function is triggered, which calls the `createNotification` function from the `useCreateNotification` hook with the form data.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Notifications/create/index.jsx",
+ "startLine": "96",
+ "endLine": "101",
+ "relevantVariables": [
+ "onSubmit",
+ "createNotification"
+ ]
+ },
+ "inputDataExample": "{\n \"notificationName\": \"Production Slack Alerts\",\n \"type\": \"slack\",\n \"slackWebhookUrl\": \"https://hooks.slack.com/services/T01ABCD/B01EFGH/xXxXxXxXxX\",\n \"isDefault\": false\n}",
+ "outputDataExample": "{\n \"notificationName\": \"Production Slack Alerts\",\n \"type\": \"slack\",\n \"slackWebhookUrl\": \"https://hooks.slack.com/services/T01ABCD/B01EFGH/xXxXxXxXxX\",\n \"isDefault\": false\n}"
+ },
+ {
+ "simStepId": "383c6be8-aa51-4398-9ae3-21035413f73a",
+ "diagramNodeId": "a07b805e-e55f-46cb-b54b-765b7813b0ca",
+ "simStepLabel": "Flow 1: Frontend API Call",
+ "simStepDescription": "The `createNotification` hook executes an asynchronous function that calls `networkService.createNotification` to send the new notification data to the backend API.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Hooks/v1/useNotifications.js",
+ "startLine": "14",
+ "endLine": "17",
+ "relevantVariables": [
+ "createNotification",
+ "networkService"
+ ]
+ },
+ "inputDataExample": "{\n \"notificationName\": \"Production Slack Alerts\",\n \"type\": \"slack\",\n \"slackWebhookUrl\": \"https://hooks.slack.com/services/T01ABCD/B01EFGH/xXxXxXxXxX\",\n \"isDefault\": false\n}",
+ "outputDataExample": "{\n \"status\": \"pending\",\n \"promise\": {}\n}"
+ },
+ {
+ "simStepId": "162d8d2f-6cd1-45fa-8711-1ee85c5f86ef",
+ "diagramNodeId": "dc135d8d-0515-403d-ae58-f3c04647b2ae",
+ "simStepLabel": "Flow 1: HTTP POST Request",
+ "simStepDescription": "The `NetworkService` constructs and sends an HTTP POST request to the `/notifications` endpoint with the notification data in the request body.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Utils/NetworkService.js",
+ "startLine": "1064",
+ "endLine": "1066",
+ "relevantVariables": [
+ "createNotification",
+ "this.axiosInstance"
+ ]
+ },
+ "inputDataExample": "{\n \"url\": \"/notifications\",\n \"method\": \"POST\",\n \"data\": {\n \"notificationName\": \"Production Slack Alerts\",\n \"type\": \"slack\",\n \"slackWebhookUrl\": \"https://hooks.slack.com/services/T01ABCD/B01EFGH/xXxXxXxXxX\",\n \"isDefault\": false\n }\n}",
+ "outputDataExample": "{\n \"url\": \"/notifications\",\n \"method\": \"POST\",\n \"data\": {\n \"notificationName\": \"Production Slack Alerts\",\n \"type\": \"slack\",\n \"slackWebhookUrl\": \"https://hooks.slack.com/services/T01ABCD/B01EFGH/xXxXxXxXxX\",\n \"isDefault\": false\n }\n}"
+ },
+ {
+ "simStepId": "2f9266c9-4cbc-4b50-a280-cf8b5800d540",
+ "diagramNodeId": "d0a4737f-715f-4f50-b6d5-5e4b31f86f93",
+ "simStepLabel": "Flow 1: Backend Route Handling",
+ "simStepDescription": "The backend server receives the POST request. The Express router matches the `/notifications` path and forwards the request to the `createNotification` method of the `NotificationController`.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/routes/v1/notificationRoute.js",
+ "startLine": "10",
+ "endLine": "10",
+ "relevantVariables": [
+ "this.router",
+ "this.notificationController.createNotification"
+ ]
+ },
+ "inputDataExample": "{\n \"request\": {\n \"path\": \"/notifications\",\n \"method\": \"POST\",\n \"body\": {\n \"notificationName\": \"Production Slack Alerts\",\n \"type\": \"slack\",\n \"slackWebhookUrl\": \"https://hooks.slack.com/services/T01ABCD/B01EFGH/xXxXxXxXxX\"\n }\n }\n}",
+ "outputDataExample": "{\n \"handler\": \"notificationController.createNotification\"\n}"
+ },
+ {
+ "simStepId": "73e63d2f-2b43-488d-9e86-170f511673f5",
+ "diagramNodeId": "7b9ebb0e-a6b2-438c-bfed-9c851a1bccbc",
+ "simStepLabel": "Flow 1: Controller to Database Module",
+ "simStepDescription": "The `createNotification` method in the controller validates the request body, appends user/team context, and then calls the `createNotification` method in the `notificationModule` to persist the data.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/notificationController.js",
+ "startLine": "55",
+ "endLine": "55",
+ "relevantVariables": [
+ "body",
+ "this.db.notificationModule.createNotification"
+ ]
+ },
+ "inputDataExample": "{\n \"notificationName\": \"Production Slack Alerts\",\n \"type\": \"slack\",\n \"slackWebhookUrl\": \"https://hooks.slack.com/services/T01ABCD/B01EFGH/xXxXxXxXxX\",\n \"userId\": \"user-123\",\n \"teamId\": \"team-456\"\n}",
+ "outputDataExample": "{\n \"notificationName\": \"Production Slack Alerts\",\n \"type\": \"slack\",\n \"slackWebhookUrl\": \"https://hooks.slack.com/services/T01ABCD/B01EFGH/xXxXxXxXxX\",\n \"userId\": \"user-123\",\n \"teamId\": \"team-456\"\n}"
+ },
+ {
+ "simStepId": "d29ab4ab-be50-4f17-af6e-2c61cec9d0ab",
+ "diagramNodeId": "4a796ccf-40a9-4446-a109-6a6eed52e898",
+ "simStepLabel": "Flow 1: Database Insertion",
+ "simStepDescription": "The `notificationModule` creates a new instance of the `Notification` model with the provided data and saves it to the MongoDB database.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/db/v1/modules/notificationModule.js",
+ "startLine": "10",
+ "endLine": "13",
+ "relevantVariables": [
+ "notificationData",
+ "this.Notification"
+ ]
+ },
+ "inputDataExample": "{\n \"notificationName\": \"Production Slack Alerts\",\n \"type\": \"slack\",\n \"slackWebhookUrl\": \"https://hooks.slack.com/services/T01ABCD/B01EFGH/xXxXxXxXxX\",\n \"userId\": \"user-123\",\n \"teamId\": \"team-456\"\n}",
+ "outputDataExample": "{\n \"_id\": \"60d5ecf31c9d440000a1b2c3\",\n \"notificationName\": \"Production Slack Alerts\",\n \"type\": \"slack\",\n \"slackWebhookUrl\": \"https://hooks.slack.com/services/T01ABCD/B01EFGH/xXxXxXxXxX\",\n \"userId\": \"user-123\",\n \"teamId\": \"team-456\",\n \"createdAt\": \"2023-10-27T10:00:00Z\",\n \"updatedAt\": \"2023-10-27T10:00:00Z\"\n}"
+ },
+ {
+ "simStepId": "d390ef45-f2fe-4806-a45a-cc9c84b48a5a",
+ "diagramNodeId": "279155cd-ff31-41a2-8f5f-a538f01bfa66",
+ "simStepLabel": "Flow 1: User Clicks Test Notification Button",
+ "simStepDescription": "The user clicks the 'Test notification' button, which invokes the `onTestNotification` handler. This handler calls the `testNotification` function from the `useTestNotification` hook.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Notifications/create/index.jsx",
+ "startLine": "281",
+ "endLine": "285",
+ "relevantVariables": [
+ "onTestNotification",
+ "testNotification"
+ ]
+ },
+ "inputDataExample": "{\n \"notificationName\": \"Production Slack Alerts\",\n \"type\": \"slack\",\n \"slackWebhookUrl\": \"https://hooks.slack.com/services/T01ABCD/B01EFGH/xXxXxXxXxX\",\n \"isDefault\": false\n}",
+ "outputDataExample": "{\n \"notificationName\": \"Production Slack Alerts\",\n \"type\": \"slack\",\n \"slackWebhookUrl\": \"https://hooks.slack.com/services/T01ABCD/B01EFGH/xXxXxXxXxX\",\n \"isDefault\": false\n}"
+ },
+ {
+ "simStepId": "c1b17b64-90e9-48bc-8927-25d7ebfc3036",
+ "diagramNodeId": "29c2c56e-9dc0-4304-bfbf-e0c0f8915a39",
+ "simStepLabel": "Flow 1: Backend Receives Test Request",
+ "simStepDescription": "The client sends a POST request to `/notifications/test`. The router directs this request to the `testNotification` method in the `NotificationController`.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/routes/v1/notificationRoute.js",
+ "startLine": "13",
+ "endLine": "13",
+ "relevantVariables": [
+ "this.router",
+ "this.notificationController.testNotification"
+ ]
+ },
+ "inputDataExample": "{\n \"request\": {\n \"path\": \"/notifications/test\",\n \"method\": \"POST\",\n \"body\": {\n \"type\": \"slack\",\n \"slackWebhookUrl\": \"https://hooks.slack.com/services/T01ABCD/B01EFGH/xXxXxXxXxX\"\n }\n }\n}",
+ "outputDataExample": "{\n \"handler\": \"notificationController.testNotification\"\n}"
+ },
+ {
+ "simStepId": "6f2bf027-50d9-472e-9569-808a64fce7cf",
+ "diagramNodeId": "57018b30-e2f4-4eb2-a1da-fe75afbb8c2f",
+ "simStepLabel": "Flow 1: Controller to Notification Service",
+ "simStepDescription": "The `testNotification` controller method passes the notification configuration from the request body to the `sendTestNotification` method of the `NotificationService`.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/notificationController.js",
+ "startLine": "22",
+ "endLine": "22",
+ "relevantVariables": [
+ "notification",
+ "this.notificationService.sendTestNotification"
+ ]
+ },
+ "inputDataExample": "{\n \"type\": \"slack\",\n \"slackWebhookUrl\": \"https://hooks.slack.com/services/T01ABCD/B01EFGH/xXxXxXxXxX\"\n}",
+ "outputDataExample": "{\n \"type\": \"slack\",\n \"slackWebhookUrl\": \"https://hooks.slack.com/services/T01ABCD/B01EFGH/xXxXxXxXxX\"\n}"
+ },
+ {
+ "simStepId": "f29f9be1-a0cd-43a3-b259-e5105a25ce41",
+ "diagramNodeId": "1cc01e08-4a6c-4526-b8a7-5ee8a06c1709",
+ "simStepLabel": "Flow 1: Send Test Notification",
+ "simStepDescription": "The `NotificationService` prepares a standard test message and calls its internal `sendNotification` method. This method identifies the notification type (e.g., email, Slack) and uses the corresponding service (e.g., EmailService, NetworkService for webhooks) to dispatch the test message.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/infrastructure/notificationService.js",
+ "startLine": "120",
+ "endLine": "123",
+ "relevantVariables": [
+ "sendTestNotification",
+ "sendNotification"
+ ]
+ },
+ "inputDataExample": "{\n \"type\": \"slack\",\n \"slackWebhookUrl\": \"https://hooks.slack.com/services/T01ABCD/B01EFGH/xXxXxXxXxX\"\n}",
+ "outputDataExample": "{\n \"success\": true\n}"
+ },
+ {
+ "simStepId": "157cec87-9b76-4a1c-9d85-0a6d3decbaa3",
+ "diagramNodeId": "28f1eabb-4f24-4944-8a0a-04f41ceb8589",
+ "simStepLabel": "Flow 2: Job Execution and Status Update",
+ "simStepDescription": "A background job is generated for a monitor. When executed, it performs a network check, saves the result, and updates the monitor's status via `statusService.updateMonitorStatus`.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v2/infrastructure/JobGenerator.ts",
+ "startLine": "43",
+ "endLine": "66",
+ "relevantVariables": [
+ "generateJob",
+ "networkService.check",
+ "statusService.updateMonitorStatus"
+ ]
+ },
+ "inputDataExample": "{\n \"monitor\": {\n \"_id\": \"60d5f1b31c9d440000a1b2c4\",\n \"name\": \"Main API\",\n \"type\": \"http\",\n \"url\": \"https://api.example.com/health\",\n \"status\": \"up\"\n }\n}",
+ "outputDataExample": "{\n \"updatedMonitor\": {\n \"_id\": \"60d5f1b31c9d440000a1b2c4\",\n \"name\": \"Main API\",\n \"url\": \"https://api.example.com/health\",\n \"status\": \"down\"\n },\n \"statusChanged\": true\n}"
+ },
+ {
+ "simStepId": "b7ec7415-fcae-4077-8c19-302a015b00b5",
+ "diagramNodeId": "50797935-1447-47a4-891b-9e886e5601fa",
+ "simStepLabel": "Flow 2: Status Change Detected",
+ "simStepDescription": "The `updateMonitorStatus` function returns a boolean indicating if the monitor's status has changed (e.g., from 'up' to 'down'). This boolean is used as a condition to trigger notifications.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v2/infrastructure/JobGenerator.ts",
+ "startLine": "60",
+ "endLine": "60",
+ "relevantVariables": [
+ "statusChanged"
+ ]
+ },
+ "inputDataExample": "{\n \"statusChanged\": true\n}",
+ "outputDataExample": "{\n \"statusChanged\": true\n}"
+ },
+ {
+ "simStepId": "55ad645b-118a-478e-a00c-09ef82357008",
+ "diagramNodeId": "31fe5a28-0640-4b52-841b-dd02e2ae646e",
+ "simStepLabel": "Flow 2: Initiate Notification Handling",
+ "simStepDescription": "If the monitor's status has changed, the `handleNotifications` method of the `NotificationService` is invoked with the updated monitor's data.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v2/infrastructure/JobGenerator.ts",
+ "startLine": "61",
+ "endLine": "61",
+ "relevantVariables": [
+ "this.notificationService.handleNotifications",
+ "updatedMonitor"
+ ]
+ },
+ "inputDataExample": "{\n \"updatedMonitor\": {\n \"_id\": \"60d5f1b31c9d440000a1b2c4\",\n \"name\": \"Main API\",\n \"url\": \"https://api.example.com/health\",\n \"status\": \"down\",\n \"notifications\": [\"60d5ecf31c9d440000a1b2c3\"]\n }\n}",
+ "outputDataExample": "{\n \"status\": \"pending\",\n \"promise\": {}\n}"
+ },
+ {
+ "simStepId": "b7259d77-c8df-4060-ac03-58728ae3eb54",
+ "diagramNodeId": "c778c6b9-c12e-45c0-8682-2c9e892867b0",
+ "simStepLabel": "Flow 2: Monitor Data Passed to Notification Service",
+ "simStepDescription": "The monitor object, which includes its current status ('down') and a list of associated notification channels, is passed to the `NotificationService`.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v2/infrastructure/NotificationService.ts",
+ "startLine": "7",
+ "endLine": "7",
+ "relevantVariables": [
+ "handleNotifications",
+ "monitor"
+ ]
+ },
+ "inputDataExample": "{\n \"monitor\": {\n \"_id\": \"60d5f1b31c9d440000a1b2c4\",\n \"name\": \"Main API\",\n \"status\": \"down\",\n \"notifications\": [\"60d5ecf31c9d440000a1b2c3\"]\n }\n}",
+ "outputDataExample": "{\n \"monitor\": {\n \"_id\": \"60d5f1b31c9d440000a1b2c4\",\n \"name\": \"Main API\",\n \"status\": \"down\",\n \"notifications\": [\"60d5ecf31c9d440000a1b2c3\"]\n }\n}"
+ },
+ {
+ "simStepId": "8d1176f3-c755-401d-9ca8-234689b4b837",
+ "diagramNodeId": "78e5cb9f-6a97-4d31-a487-fe7dbce5d7d1",
+ "simStepLabel": "Flow 2: Dispatch Alerts",
+ "simStepDescription": "The `handleNotifications` method retrieves the full details for each associated notification channel. It then iterates through them, using specific provider services (e.g., `SlackService`, `EmailService`) to format and send a tailored alert message for each channel, notifying users that the monitor is down.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v2/infrastructure/NotificationService.ts",
+ "startLine": "28",
+ "endLine": "57",
+ "relevantVariables": [
+ "handleNotifications",
+ "channels",
+ "emailService.send",
+ "slackService.send",
+ "discordService.send"
+ ]
+ },
+ "inputDataExample": "{\n \"monitor\": {\n \"_id\": \"60d5f1b31c9d440000a1b2c4\",\n \"name\": \"Main API\",\n \"status\": \"down\",\n \"notifications\": [\"60d5ecf31c9d440000a1b2c3\"]\n }\n}",
+ "outputDataExample": "{\n \"dispatchResults\": [\n {\n \"channelId\": \"60d5ecf31c9d440000a1b2c3\",\n \"status\": \"success\"\n }\n ]\n}"
+ }
+ ],
+ "description": "A critical feature that makes the monitoring data actionable by alerting users when an issue is detected - Users can create and manage notification channels for different services - Supports a wide range of notification providers including Email, Slack, Discord, Webhooks, PagerDuty, and Matrix - Notifications are triggered based on monitor status changes (e g , from 'up' to 'down') or when hardware metrics cross pre-defined thresholds - Users can test notification configurations to ensure they are working correctly before attaching them to monitors ",
+ "simulationNodesAndEdges": {
+ "15486629-84b3-463a-962b-06083f69e4bf": {
+ "simStepIds": [
+ "e924ec15-1bea-4e3b-befb-a8bf30cbf740"
+ ]
+ },
+ "a07b805e-e55f-46cb-b54b-765b7813b0ca": {
+ "simStepIds": [
+ "383c6be8-aa51-4398-9ae3-21035413f73a"
+ ]
+ },
+ "d0a4737f-715f-4f50-b6d5-5e4b31f86f93": {
+ "simStepIds": [
+ "2f9266c9-4cbc-4b50-a280-cf8b5800d540"
+ ]
+ },
+ "4a796ccf-40a9-4446-a109-6a6eed52e898": {
+ "simStepIds": [
+ "d29ab4ab-be50-4f17-af6e-2c61cec9d0ab"
+ ]
+ },
+ "29c2c56e-9dc0-4304-bfbf-e0c0f8915a39": {
+ "simStepIds": [
+ "c1b17b64-90e9-48bc-8927-25d7ebfc3036"
+ ]
+ },
+ "1cc01e08-4a6c-4526-b8a7-5ee8a06c1709": {
+ "simStepIds": [
+ "f29f9be1-a0cd-43a3-b259-e5105a25ce41"
+ ]
+ },
+ "28f1eabb-4f24-4944-8a0a-04f41ceb8589": {
+ "simStepIds": [
+ "157cec87-9b76-4a1c-9d85-0a6d3decbaa3"
+ ]
+ },
+ "31fe5a28-0640-4b52-841b-dd02e2ae646e": {
+ "simStepIds": [
+ "55ad645b-118a-478e-a00c-09ef82357008"
+ ]
+ },
+ "78e5cb9f-6a97-4d31-a487-fe7dbce5d7d1": {
+ "simStepIds": [
+ "8d1176f3-c755-401d-9ca8-234689b4b837"
+ ]
+ },
+ "54497b9a-4910-4df4-b016-480b9ac250a7": {
+ "simStepIds": [
+ "71dbcd41-c6d4-4514-b5a4-6c966ca7ba6f"
+ ]
+ },
+ "dc135d8d-0515-403d-ae58-f3c04647b2ae": {
+ "simStepIds": [
+ "162d8d2f-6cd1-45fa-8711-1ee85c5f86ef"
+ ]
+ },
+ "7b9ebb0e-a6b2-438c-bfed-9c851a1bccbc": {
+ "simStepIds": [
+ "73e63d2f-2b43-488d-9e86-170f511673f5"
+ ]
+ },
+ "279155cd-ff31-41a2-8f5f-a538f01bfa66": {
+ "simStepIds": [
+ "d390ef45-f2fe-4806-a45a-cc9c84b48a5a"
+ ]
+ },
+ "57018b30-e2f4-4eb2-a1da-fe75afbb8c2f": {
+ "simStepIds": [
+ "6f2bf027-50d9-472e-9569-808a64fce7cf"
+ ]
+ },
+ "50797935-1447-47a4-891b-9e886e5601fa": {
+ "simStepIds": [
+ "b7ec7415-fcae-4077-8c19-302a015b00b5"
+ ]
+ },
+ "c778c6b9-c12e-45c0-8682-2c9e892867b0": {
+ "simStepIds": [
+ "b7259d77-c8df-4060-ac03-58728ae3eb54"
+ ]
+ }
+ },
+ "isAIGenerated": true,
+ "keywords": "NotificationService, sendNotification, createNotification",
+ "generationPrompt": "Multi-Channel Alerting and Notifications",
+ "generationKeywords": "NotificationService, sendNotification, createNotification"
+ },
+ "Customizable Public and Private Status Pages": {
+ "name": "Customizable Public and Private Status Pages",
+ "simSteps": [
+ {
+ "simStepId": "de7b5d1f-b370-42a4-a410-4178f1e2d3a6",
+ "diagramNodeId": "00289d11-0015-4b2a-9c5b-dc9fd0f32b80",
+ "simStepLabel": "Create Status Page: User Initiates Creation",
+ "simStepDescription": "The user navigates to the 'Create Status Page' screen. The `CreateStatusPage` component renders the UI, including tabs for configuration, and initializes the form state.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/StatusPage/Create/index.jsx",
+ "startLine": "26",
+ "endLine": "313",
+ "relevantVariables": [
+ "CreateStatusPage",
+ "form",
+ "setForm",
+ "errors",
+ "setErrors",
+ "handleFormChange",
+ "handleSubmit"
+ ]
+ },
+ "inputDataExample": "{}",
+ "outputDataExample": "{\"form\":{\"companyName\":\"\",\"url\":\"\",\"timezone\":\"UTC\",\"color\":\"#FF5733\",\"isPublished\":false,\"logo\":null,\"monitors\":[],\"showUptimePercentage\":true,\"showCharts\":true,\"showAdminLoginLink\":false},\"errors\":{}}"
+ },
+ {
+ "simStepId": "d419f7b6-7b9b-4494-9fad-83e3ce208ca4",
+ "diagramNodeId": "de23eee2-b60a-4236-a890-3c5b78388bb3",
+ "simStepLabel": "Create Status Page: Form Submission",
+ "simStepDescription": "The user fills out the form and clicks the 'Save' button, which triggers the `handleSubmit` function. This function validates the form data and calls the `createStatusPage` function from the `useCreateStatusPage` hook.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/StatusPage/Create/index.jsx",
+ "startLine": "143",
+ "endLine": "159",
+ "relevantVariables": [
+ "handleSubmit",
+ "createStatusPage"
+ ]
+ },
+ "inputDataExample": "{\"form\":{\"companyName\":\"My Awesome Service\",\"url\":\"my-awesome-service\",\"timezone\":\"America/New_York\",\"color\":\"#3B82F6\",\"isPublished\":true,\"logo\":{\"src\":\"data:image/png;base64,...\",\"name\":\"logo.png\",\"type\":\"image/png\",\"size\":12345},\"monitors\":[\"65aee7cc1c363d5032a03972\"],\"showUptimePercentage\":true,\"showCharts\":true,\"showAdminLoginLink\":true}}",
+ "outputDataExample": "{\"form\":{\"companyName\":\"My Awesome Service\",\"url\":\"my-awesome-service\",\"timezone\":\"America/New_York\",\"color\":\"#3B82F6\",\"isPublished\":true,\"logo\":{\"src\":\"data:image/png;base64,...\",\"name\":\"logo.png\",\"type\":\"image/png\",\"size\":12345},\"monitors\":[\"65aee7cc1c363d5032a03972\"],\"showUptimePercentage\":true,\"showCharts\":true,\"showAdminLoginLink\":true}}"
+ },
+ {
+ "simStepId": "3c99ae6a-b671-4f47-bed0-ddccd8ff5d35",
+ "diagramNodeId": "aa5d6d07-05f0-45f3-b19a-199af50e35f1",
+ "simStepLabel": "Create Status Page: Client Hook Sends API Request",
+ "simStepDescription": "The `useCreateStatusPage` hook receives the form data and uses the `networkService` to send a request to the backend API to create or update the status page.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/StatusPage/Create/Hooks/useCreateStatusPage.jsx",
+ "startLine": "8",
+ "endLine": "11",
+ "relevantVariables": [
+ "createStatusPage",
+ "networkService.createStatusPage"
+ ]
+ },
+ "inputDataExample": "{\"form\":{\"companyName\":\"My Awesome Service\",\"url\":\"my-awesome-service\",\"timezone\":\"America/New_York\",\"color\":\"#3B82F6\",\"isPublished\":true,\"logo\":{\"src\":\"data:image/png;base64,...\"},\"monitors\":[\"65aee7cc1c363d5032a03972\"]}}",
+ "outputDataExample": "{}"
+ },
+ {
+ "simStepId": "8a2ab372-73db-4b9f-bc0e-6a20f15a4d79",
+ "diagramNodeId": "d7faa713-4204-4a97-a172-d621da36c287",
+ "simStepLabel": "Create Status Page: API Call to Backend",
+ "simStepDescription": "The `NetworkService` class constructs a `FormData` object from the form data and sends a POST request to the `/api/v1/status-page` endpoint. The logo is sent as a file upload.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Utils/NetworkService.js",
+ "startLine": "912",
+ "endLine": "944",
+ "relevantVariables": [
+ "createStatusPage",
+ "axiosInstance.post"
+ ]
+ },
+ "inputDataExample": "{\"form\":{\"companyName\":\"My Awesome Service\",\"url\":\"my-awesome-service\"...}, \"isCreate\": true}",
+ "outputDataExample": "{\"form\":{\"companyName\":\"My Awesome Service\",\"url\":\"my-awesome-service\"...}, \"isCreate\": true}"
+ },
+ {
+ "simStepId": "84f11567-b3c4-42a2-aa7f-d78c1412099a",
+ "diagramNodeId": "3efddcad-588f-4492-9808-b0c81c823c96",
+ "simStepLabel": "Create Status Page: Backend Route Handling",
+ "simStepDescription": "The backend router receives the POST request on `/api/v1/status-page`. It uses `multer` to handle the file upload and forwards the request to the `createStatusPage` method in the `StatusPageController`.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/routes/v1/statusPageRoute.js",
+ "startLine": "14",
+ "endLine": "14",
+ "relevantVariables": [
+ "router.post",
+ "upload.single",
+ "verifyJWT",
+ "this.statusPageController.createStatusPage"
+ ]
+ },
+ "inputDataExample": "{\"method\":\"POST\",\"url\":\"/api/v1/status-page\",\"body\":{\"companyName\":\"My Awesome Service\",...},\"file\":{...}}",
+ "outputDataExample": "{\"req\":{...},\"res\":{...}}"
+ },
+ {
+ "simStepId": "0923a533-c572-4da3-ad87-cdfff111f3bb",
+ "diagramNodeId": "a3f28fa6-8268-4c13-95a0-83e3b1abdcb0",
+ "simStepLabel": "Create Status Page: Data Passed to Controller",
+ "simStepDescription": "The router passes the request object, containing form data in `req.body`, the uploaded logo in `req.file`, and user details in `req.user`, to the controller method.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/routes/v1/statusPageRoute.js",
+ "startLine": "14",
+ "endLine": "14",
+ "relevantVariables": [
+ "statusPageController.createStatusPage"
+ ]
+ },
+ "inputDataExample": "{\"req\":{\"body\":{\"companyName\":\"My Awesome Service\",...},\"file\":{\"fieldname\":\"logo\",...},\"user\":{\"_id\":\"65aee7cc1c363d5032a03970\",\"teamId\":\"65aee7cc1c363d5032a03971\"}}}",
+ "outputDataExample": "{\"req\":{\"body\":{\"companyName\":\"My Awesome Service\",...},\"file\":{\"fieldname\":\"logo\",...},\"user\":{\"_id\":\"65aee7cc1c363d5032a03970\",\"teamId\":\"65aee7cc1c363d5032a03971\"}}}"
+ },
+ {
+ "simStepId": "01694730-8a75-4949-8815-bb4d6e3e2b42",
+ "diagramNodeId": "f15ef77e-380f-4768-98cb-7f0dd4113841",
+ "simStepLabel": "Create Status Page: Controller Logic",
+ "simStepDescription": "The `StatusPageController` validates the incoming request body and file. It then calls the `statusPageModule` to persist the new status page in the database.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/statusPageController.js",
+ "startLine": "18",
+ "endLine": "24",
+ "relevantVariables": [
+ "createStatusPageBodyValidation",
+ "imageValidation",
+ "this.db.statusPageModule.createStatusPage"
+ ]
+ },
+ "inputDataExample": "{\"req\":{\"body\":{...},\"file\":{...},\"user\":{...}}}",
+ "outputDataExample": "{\"statusPageData\":{...},\"image\":{...},\"userId\":\"65aee7cc1c363d5032a03970\",\"teamId\":\"65aee7cc1c363d5032a03971\"}"
+ },
+ {
+ "simStepId": "67603f03-c088-4ed0-9f18-072435134cdd",
+ "diagramNodeId": "9735611c-924b-49aa-92e8-10d8a86ac288",
+ "simStepLabel": "Create Status Page: Data Sent to DB Module",
+ "simStepDescription": "The controller passes the processed status page data, image buffer, user ID, and team ID to the `createStatusPage` function in the database module.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/statusPageController.js",
+ "startLine": "21",
+ "endLine": "21",
+ "relevantVariables": [
+ "this.db.statusPageModule.createStatusPage"
+ ]
+ },
+ "inputDataExample": "{\"statusPageData\":{\"companyName\":\"My Awesome Service\",...},\"image\":{\"buffer\":,\"mimetype\":\"image/png\"},\"userId\":\"65aee7cc1c363d5032a03970\",\"teamId\":\"65aee7cc1c363d5032a03971\"}",
+ "outputDataExample": "{\"statusPageData\":{\"companyName\":\"My Awesome Service\",...},\"image\":{\"buffer\":,\"mimetype\":\"image/png\"},\"userId\":\"65aee7cc1c363d5032a03970\",\"teamId\":\"65aee7cc1c363d5032a03971\"}"
+ },
+ {
+ "simStepId": "b6fa93cd-6fca-4d9e-969a-5400e2437ee8",
+ "diagramNodeId": "e234917e-a950-48f5-b864-cd957b2216e1",
+ "simStepLabel": "Create Status Page: Database Record Creation",
+ "simStepDescription": "The `StatusPageModule` creates a new instance of the `StatusPage` Mongoose model, populates it with the provided data including the image buffer, and saves it to the MongoDB database. It also handles duplicate URL errors.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/db/v1/modules/statusPageModule.js",
+ "startLine": "14",
+ "endLine": "34",
+ "relevantVariables": [
+ "createStatusPage",
+ "new this.StatusPage",
+ "statusPage.save"
+ ]
+ },
+ "inputDataExample": "{\"statusPageData\":{...},\"image\":{...},\"userId\":\"...\",\"teamId\":\"...\"}",
+ "outputDataExample": "{\"_id\":\"65aee7dd1c363d5032a03975\",\"companyName\":\"My Awesome Service\",\"url\":\"my-awesome-service\",\"userId\":\"65aee7cc1c363d5032a03970\",...}"
+ },
+ {
+ "simStepId": "ea4b3532-baae-4890-a73e-2ca385d0319e",
+ "diagramNodeId": "40cb7d7b-2888-44de-bb7d-7735af70ba34",
+ "simStepLabel": "Create Status Page: Database Result Returned",
+ "simStepDescription": "The newly created status page document is returned from the database module to the controller.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/db/v1/modules/statusPageModule.js",
+ "startLine": "27",
+ "endLine": "27",
+ "relevantVariables": [
+ "statusPage"
+ ]
+ },
+ "inputDataExample": "{\"_id\":\"65aee7dd1c363d5032a03975\",\"companyName\":\"My Awesome Service\",\"url\":\"my-awesome-service\",...}",
+ "outputDataExample": "{\"_id\":\"65aee7dd1c363d5032a03975\",\"companyName\":\"My Awesome Service\",\"url\":\"my-awesome-service\",...}"
+ },
+ {
+ "simStepId": "2a115421-5175-4c8f-8cdd-62f3fe148af5",
+ "diagramNodeId": "058ded1f-83ce-40a7-a99d-d178c81fe557",
+ "simStepLabel": "Create Status Page: Controller Sends Success Response",
+ "simStepDescription": "The controller receives the newly created status page object from the database module and sends a successful JSON response back to the client, including a success message and the new data.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/statusPageController.js",
+ "startLine": "25",
+ "endLine": "28",
+ "relevantVariables": [
+ "res.success"
+ ]
+ },
+ "inputDataExample": "{\"_id\":\"65aee7dd1c363d5032a03975\",\"companyName\":\"My Awesome Service\",...}",
+ "outputDataExample": "{\"msg\":\"Status page created successfully\",\"data\":{\"_id\":\"65aee7dd1c363d5032a03975\",...}}"
+ },
+ {
+ "simStepId": "210acc07-81c3-4394-b566-41f6031e8eeb",
+ "diagramNodeId": "c3f83811-8674-4dbb-9f84-79499f97fb1f",
+ "simStepLabel": "Create Status Page: UI Receives Response and Redirects",
+ "simStepDescription": "The client-side `handleSubmit` function receives the successful response. It displays a success toast message and navigates the user to the newly created status page.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/StatusPage/Create/index.jsx",
+ "startLine": "151",
+ "endLine": "156",
+ "relevantVariables": [
+ "createToast",
+ "navigate"
+ ]
+ },
+ "inputDataExample": "{\"success\":true}",
+ "outputDataExample": "{\"success\":true}"
+ },
+ {
+ "simStepId": "132354d7-df7a-4e03-9d00-16d54f836a1b",
+ "diagramNodeId": "30aaee38-f509-4e19-a917-e6d2f4bba61c",
+ "simStepLabel": "View Status Page: User Navigates to URL",
+ "simStepDescription": "A user accesses a status page URL (e.g., /status/uptime/my-awesome-service). The React router matches the path and renders the `Status` component, which is responsible for fetching and displaying the page details.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Routes/index.jsx",
+ "startLine": "148",
+ "endLine": "152",
+ "relevantVariables": [
+ "Route",
+ "Status"
+ ]
+ },
+ "inputDataExample": "{\"url\":\"/status/uptime/my-awesome-service\"}",
+ "outputDataExample": "{}"
+ },
+ {
+ "simStepId": "2ba49e28-1cac-47f5-9118-ab930788736a",
+ "diagramNodeId": "0575bf87-5362-4988-b895-7e514148d420",
+ "simStepLabel": "View Status Page: Component Triggers Data Fetch",
+ "simStepDescription": "The `Status` component mounts and calls the `useStatusPageFetch` hook, which is responsible for initiating the API call to fetch the status page data from the backend.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/StatusPage/Status/index.jsx",
+ "startLine": "29",
+ "endLine": "29",
+ "relevantVariables": [
+ "useStatusPageFetch"
+ ]
+ },
+ "inputDataExample": "{\"isCreate\":false, \"url\":\"my-awesome-service\"}",
+ "outputDataExample": "{\"isCreate\":false, \"url\":\"my-awesome-service\"}"
+ },
+ {
+ "simStepId": "b57bb249-9066-454e-aa8c-89108fdfcc2e",
+ "diagramNodeId": "ecfb2d04-2681-4f16-af75-632f69e1a868",
+ "simStepLabel": "View Status Page: Client Hook Sends API Request",
+ "simStepDescription": "The `useStatusPageFetch` hook uses the `networkService` to send a GET request to the backend API to retrieve the status page data based on its URL.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageFetch.jsx",
+ "startLine": "15",
+ "endLine": "18",
+ "relevantVariables": [
+ "fetchStatusPage",
+ "networkService.getStatusPageByUrl"
+ ]
+ },
+ "inputDataExample": "{\"url\":\"my-awesome-service\", \"type\":\"uptime\"}",
+ "outputDataExample": "{}"
+ },
+ {
+ "simStepId": "de0714d2-bf1e-4a1a-a389-60793de8b772",
+ "diagramNodeId": "ae1c9341-0e39-4d91-a9ac-5cc8a627c247",
+ "simStepLabel": "View Status Page: API Call to Backend",
+ "simStepDescription": "The `NetworkService` class makes a GET request to the `/api/v1/status-page/:url` endpoint to fetch the status page details.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Utils/NetworkService.js",
+ "startLine": "896",
+ "endLine": "902",
+ "relevantVariables": [
+ "getStatusPageByUrl",
+ "axiosInstance.get"
+ ]
+ },
+ "inputDataExample": "{\"url\":\"my-awesome-service\",\"type\":\"uptime\"}",
+ "outputDataExample": "{\"url\":\"my-awesome-service\",\"type\":\"uptime\"}"
+ },
+ {
+ "simStepId": "477c6821-b29b-4b38-8930-ced21b8a75e5",
+ "diagramNodeId": "2e26cd2f-1413-4c16-8e63-ea7808ab6c52",
+ "simStepLabel": "View Status Page: Backend Route Handling",
+ "simStepDescription": "The backend router receives the GET request on `/api/v1/status-page/:url` and forwards it to the `getStatusPageByUrl` method in the `StatusPageController`.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/routes/v1/statusPageRoute.js",
+ "startLine": "16",
+ "endLine": "16",
+ "relevantVariables": [
+ "router.get",
+ "this.statusPageController.getStatusPageByUrl"
+ ]
+ },
+ "inputDataExample": "{\"method\":\"GET\",\"url\":\"/api/v1/status-page/my-awesome-service\"}",
+ "outputDataExample": "{\"req\":{...},\"res\":{...}}"
+ },
+ {
+ "simStepId": "b79b8922-4de6-477d-8648-37b4bd9a6767",
+ "diagramNodeId": "5735f30c-b55f-444a-9236-f3d75f68ed27",
+ "simStepLabel": "View Status Page: Data Passed to Controller",
+ "simStepDescription": "The router passes the request object, containing the URL in `req.params`, to the controller method.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/routes/v1/statusPageRoute.js",
+ "startLine": "16",
+ "endLine": "16",
+ "relevantVariables": [
+ "statusPageController.getStatusPageByUrl"
+ ]
+ },
+ "inputDataExample": "{\"req\":{\"params\":{\"url\":\"my-awesome-service\"}}}",
+ "outputDataExample": "{\"req\":{\"params\":{\"url\":\"my-awesome-service\"}}}"
+ },
+ {
+ "simStepId": "5bdbd179-ce8f-4171-a073-af72a99b4468",
+ "diagramNodeId": "9f217674-4cd7-4ef8-b088-d5620042a7ba",
+ "simStepLabel": "View Status Page: Controller Logic",
+ "simStepDescription": "The `StatusPageController` validates the request parameters and calls the `statusPageModule` to retrieve the status page data from the database.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/statusPageController.js",
+ "startLine": "75",
+ "endLine": "78",
+ "relevantVariables": [
+ "getStatusPageByUrl",
+ "this.db.statusPageModule.getStatusPageByUrl"
+ ]
+ },
+ "inputDataExample": "{\"req\":{\"params\":{\"url\":\"my-awesome-service\"}}}",
+ "outputDataExample": "{\"url\":\"my-awesome-service\"}"
+ },
+ {
+ "simStepId": "788307ef-4cfa-4f5a-b2e9-a0bfc7a378d8",
+ "diagramNodeId": "dace45d6-fb25-4dae-b940-49ef7d77bbed",
+ "simStepLabel": "View Status Page: URL Sent to DB Module",
+ "simStepDescription": "The controller passes the URL string to the `getStatusPageByUrl` function in the database module.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/statusPageController.js",
+ "startLine": "78",
+ "endLine": "78",
+ "relevantVariables": [
+ "this.db.statusPageModule.getStatusPageByUrl"
+ ]
+ },
+ "inputDataExample": "{\"url\":\"my-awesome-service\"}",
+ "outputDataExample": "{\"url\":\"my-awesome-service\"}"
+ },
+ {
+ "simStepId": "ceb044e9-6545-4d04-bf79-ec8146eb8ec9",
+ "diagramNodeId": "26da60f9-dde4-44ec-be89-91858cc14f90",
+ "simStepLabel": "View Status Page: Database Record Retrieval",
+ "simStepDescription": "The `StatusPageModule` executes a complex MongoDB aggregation pipeline to find the status page by its URL, and then look up and join data for all associated monitors and their recent checks.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/db/v1/modules/statusPageModule.js",
+ "startLine": "89",
+ "endLine": "299",
+ "relevantVariables": [
+ "getStatusPage",
+ "this.StatusPage.aggregate"
+ ]
+ },
+ "inputDataExample": "{\"url\":\"my-awesome-service\"}",
+ "outputDataExample": "{\"statusPage\":{\"_id\":\"...\",\"companyName\":\"My Awesome Service\",...},\"monitors\":[{\"_id\":\"...\",\"name\":\"Main Website\",...}]}"
+ },
+ {
+ "simStepId": "4d3fad92-8fa9-4a8f-be3b-f09eda2f8dd4",
+ "diagramNodeId": "137a5ab7-1e49-42ec-869d-58f7f2380cb3",
+ "simStepLabel": "View Status Page: Database Result Returned",
+ "simStepDescription": "The aggregated status page and monitor data is returned from the database module to the controller.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/db/v1/modules/statusPageModule.js",
+ "startLine": "291",
+ "endLine": "291",
+ "relevantVariables": [
+ "statusPage",
+ "normalizedMonitors"
+ ]
+ },
+ "inputDataExample": "{\"statusPage\":{...},\"monitors\":[...]}",
+ "outputDataExample": "{\"statusPage\":{...},\"monitors\":[...]}"
+ },
+ {
+ "simStepId": "83224406-2f1d-4030-9beb-d65c2baf544d",
+ "diagramNodeId": "d76e70e0-41f9-46cc-b775-b2027c5a0d5b",
+ "simStepLabel": "View Status Page: Controller Sends Success Response",
+ "simStepDescription": "The controller receives the status page data and sends a successful JSON response back to the client.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/statusPageController.js",
+ "startLine": "79",
+ "endLine": "82",
+ "relevantVariables": [
+ "res.success"
+ ]
+ },
+ "inputDataExample": "{\"statusPage\":{...},\"monitors\":[...]}",
+ "outputDataExample": "{\"msg\":\"Got status page by url successfully\",\"data\":{\"statusPage\":{...},\"monitors\":[...]}}"
+ },
+ {
+ "simStepId": "d5f3c300-9777-4e09-bb05-f1281c2ff29a",
+ "diagramNodeId": "04a33e4f-1dfe-4c5a-8b30-01ed0591c380",
+ "simStepLabel": "View Status Page: UI Receives Data",
+ "simStepDescription": "The `useStatusPageFetch` hook receives the API response, processes the data, and updates the component's state, triggering a re-render.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageFetch.jsx",
+ "startLine": "20",
+ "endLine": "26",
+ "relevantVariables": [
+ "setStatusPage",
+ "setMonitors",
+ "getMonitorWithPercentage"
+ ]
+ },
+ "inputDataExample": "{\"data\":{\"data\":{\"statusPage\":{...},\"monitors\":[...]}}}",
+ "outputDataExample": "{\"statusPage\":{...},\"monitors\":[...]}"
+ },
+ {
+ "simStepId": "3d51e811-a659-442e-97e0-00798cd15647",
+ "diagramNodeId": "f59b91de-5196-4a43-9d57-3f28402c0672",
+ "simStepLabel": "View Status Page: Component Renders UI",
+ "simStepDescription": "The `Status` component re-renders with the fetched data. It displays the company name, logo, overall status bar, and a list of monitors with their individual status and historical uptime charts.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/StatusPage/Status/index.jsx",
+ "startLine": "145",
+ "endLine": "163",
+ "relevantVariables": [
+ "ControlsHeader",
+ "StatusBar",
+ "MonitorsList"
+ ]
+ },
+ "inputDataExample": "{\"statusPage\":{...},\"monitors\":[...]}",
+ "outputDataExample": "{}"
+ }
+ ],
+ "description": "Allows users to create dedicated pages to communicate the status of their services to internal teams or external customers - Users can create a status page with a custom URL, company name, and logo - Select which monitors to display on the status page, providing granular control over what information is shared - Options to show or hide historical charts and uptime percentages for transparency - Includes settings for custom CSS and an optional link back to the admin login page ",
+ "simulationNodesAndEdges": {
+ "00289d11-0015-4b2a-9c5b-dc9fd0f32b80": {
+ "simStepIds": [
+ "de7b5d1f-b370-42a4-a410-4178f1e2d3a6"
+ ]
+ },
+ "aa5d6d07-05f0-45f3-b19a-199af50e35f1": {
+ "simStepIds": [
+ "3c99ae6a-b671-4f47-bed0-ddccd8ff5d35"
+ ]
+ },
+ "3efddcad-588f-4492-9808-b0c81c823c96": {
+ "simStepIds": [
+ "84f11567-b3c4-42a2-aa7f-d78c1412099a"
+ ]
+ },
+ "f15ef77e-380f-4768-98cb-7f0dd4113841": {
+ "simStepIds": [
+ "01694730-8a75-4949-8815-bb4d6e3e2b42"
+ ]
+ },
+ "e234917e-a950-48f5-b864-cd957b2216e1": {
+ "simStepIds": [
+ "b6fa93cd-6fca-4d9e-969a-5400e2437ee8"
+ ]
+ },
+ "058ded1f-83ce-40a7-a99d-d178c81fe557": {
+ "simStepIds": [
+ "2a115421-5175-4c8f-8cdd-62f3fe148af5"
+ ]
+ },
+ "30aaee38-f509-4e19-a917-e6d2f4bba61c": {
+ "simStepIds": [
+ "132354d7-df7a-4e03-9d00-16d54f836a1b"
+ ]
+ },
+ "ecfb2d04-2681-4f16-af75-632f69e1a868": {
+ "simStepIds": [
+ "b57bb249-9066-454e-aa8c-89108fdfcc2e"
+ ]
+ },
+ "2e26cd2f-1413-4c16-8e63-ea7808ab6c52": {
+ "simStepIds": [
+ "477c6821-b29b-4b38-8930-ced21b8a75e5"
+ ]
+ },
+ "9f217674-4cd7-4ef8-b088-d5620042a7ba": {
+ "simStepIds": [
+ "5bdbd179-ce8f-4171-a073-af72a99b4468"
+ ]
+ },
+ "26da60f9-dde4-44ec-be89-91858cc14f90": {
+ "simStepIds": [
+ "ceb044e9-6545-4d04-bf79-ec8146eb8ec9"
+ ]
+ },
+ "d76e70e0-41f9-46cc-b775-b2027c5a0d5b": {
+ "simStepIds": [
+ "83224406-2f1d-4030-9beb-d65c2baf544d"
+ ]
+ },
+ "f59b91de-5196-4a43-9d57-3f28402c0672": {
+ "simStepIds": [
+ "3d51e811-a659-442e-97e0-00798cd15647"
+ ]
+ },
+ "de23eee2-b60a-4236-a890-3c5b78388bb3": {
+ "simStepIds": [
+ "d419f7b6-7b9b-4494-9fad-83e3ce208ca4"
+ ]
+ },
+ "d7faa713-4204-4a97-a172-d621da36c287": {
+ "simStepIds": [
+ "8a2ab372-73db-4b9f-bc0e-6a20f15a4d79"
+ ]
+ },
+ "a3f28fa6-8268-4c13-95a0-83e3b1abdcb0": {
+ "simStepIds": [
+ "0923a533-c572-4da3-ad87-cdfff111f3bb"
+ ]
+ },
+ "9735611c-924b-49aa-92e8-10d8a86ac288": {
+ "simStepIds": [
+ "67603f03-c088-4ed0-9f18-072435134cdd"
+ ]
+ },
+ "40cb7d7b-2888-44de-bb7d-7735af70ba34": {
+ "simStepIds": [
+ "ea4b3532-baae-4890-a73e-2ca385d0319e"
+ ]
+ },
+ "c3f83811-8674-4dbb-9f84-79499f97fb1f": {
+ "simStepIds": [
+ "210acc07-81c3-4394-b566-41f6031e8eeb"
+ ]
+ },
+ "0575bf87-5362-4988-b895-7e514148d420": {
+ "simStepIds": [
+ "2ba49e28-1cac-47f5-9118-ab930788736a"
+ ]
+ },
+ "ae1c9341-0e39-4d91-a9ac-5cc8a627c247": {
+ "simStepIds": [
+ "de0714d2-bf1e-4a1a-a389-60793de8b772"
+ ]
+ },
+ "5735f30c-b55f-444a-9236-f3d75f68ed27": {
+ "simStepIds": [
+ "b79b8922-4de6-477d-8648-37b4bd9a6767"
+ ]
+ },
+ "dace45d6-fb25-4dae-b940-49ef7d77bbed": {
+ "simStepIds": [
+ "788307ef-4cfa-4f5a-b2e9-a0bfc7a378d8"
+ ]
+ },
+ "137a5ab7-1e49-42ec-869d-58f7f2380cb3": {
+ "simStepIds": [
+ "4d3fad92-8fa9-4a8f-be3b-f09eda2f8dd4"
+ ]
+ },
+ "04a33e4f-1dfe-4c5a-8b30-01ed0591c380": {
+ "simStepIds": [
+ "d5f3c300-9777-4e09-bb05-f1281c2ff29a"
+ ]
+ }
+ },
+ "isAIGenerated": true,
+ "keywords": "StatusPage, createStatusPage, getStatusPageByUrl",
+ "generationPrompt": "Customizable Public and Private Status Pages",
+ "generationKeywords": "StatusPage, createStatusPage, getStatusPageByUrl"
+ },
+ "Incident History, Analysis, and Management": {
+ "name": "Incident History, Analysis, and Management",
+ "simSteps": [
+ {
+ "simStepId": "35195328-9847-4678-b014-9331553f0d19",
+ "diagramNodeId": "78a8a7c5-41d3-4453-a7f7-ac632bbef288",
+ "simStepLabel": "Navigate to Monitor Details",
+ "simStepDescription": "The user navigates to the details page for a specific uptime monitor. The `UptimeDetails` component mounts and initializes its state and hooks.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Uptime/Details/index.jsx",
+ "startLine": "27",
+ "endLine": "188",
+ "relevantVariables": [
+ "UptimeDetails",
+ "useParams",
+ "useFetchChecksByMonitor"
+ ]
+ },
+ "inputDataExample": "{\"url\": \"/uptime/65a12f60e9a5d109f7596a77\"}",
+ "outputDataExample": "{\"monitorId\": \"65a12f60e9a5d109f7596a77\"}"
+ },
+ {
+ "simStepId": "49979250-1a32-4da5-95e0-58652fe218f9",
+ "diagramNodeId": "e90e4943-6108-46e3-8a2c-a9a1c7361279",
+ "simStepLabel": "Trigger Check History Fetch",
+ "simStepDescription": "The `UptimeDetails` component calls the `useFetchChecksByMonitor` hook to retrieve the historical check data for the specified monitor.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Uptime/Details/index.jsx",
+ "startLine": "69",
+ "endLine": "77",
+ "relevantVariables": [
+ "useFetchChecksByMonitor"
+ ]
+ },
+ "inputDataExample": "{\"monitorId\": \"65a12f60e9a5d109f7596a77\", \"type\": \"http\", \"sortOrder\": \"desc\", \"page\": 0, \"rowsPerPage\": 10}",
+ "outputDataExample": "{\"monitorId\": \"65a12f60e9a5d109f7596a77\", \"type\": \"http\", \"sortOrder\": \"desc\", \"page\": 0, \"rowsPerPage\": 10}"
+ },
+ {
+ "simStepId": "8dc4347c-3e9e-40d7-ae6f-54684f2e6956",
+ "diagramNodeId": "4a5847d0-91a0-4b4c-8517-2f4ff9a7e8d4",
+ "simStepLabel": "Fetch Monitor Checks Hook",
+ "simStepDescription": "The `useFetchChecksByMonitor` custom hook executes. It sets the loading state and prepares to call the network service to fetch the data from the backend API.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Hooks/v1/checkHooks.js",
+ "startLine": "89",
+ "endLine": "121",
+ "relevantVariables": [
+ "useFetchChecksByMonitor",
+ "useEffect",
+ "networkService.getChecksByMonitor"
+ ]
+ },
+ "inputDataExample": "{\"monitorId\": \"65a12f60e9a5d109f7596a77\", \"page\": 0, \"rowsPerPage\": 10, \"sortOrder\": \"desc\"}",
+ "outputDataExample": "{\"config\": {\"monitorId\": \"65a12f60e9a5d109f7596a77\", \"sortOrder\": \"desc\", \"page\": \"0\", \"rowsPerPage\": \"10\", \"status\": undefined}}"
+ },
+ {
+ "simStepId": "336b534d-8d6a-4639-af8e-889b38f51ab1",
+ "diagramNodeId": "2b8ae59e-ed8d-4837-9a9b-64eb480f982c",
+ "simStepLabel": "API Request for Check History",
+ "simStepDescription": "The `NetworkService` constructs and sends an HTTP GET request to the backend API endpoint to retrieve the paginated list of checks for the specified monitor.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Utils/NetworkService.js",
+ "startLine": "551",
+ "endLine": "562",
+ "relevantVariables": [
+ "getChecksByMonitor",
+ "this.axiosInstance.get"
+ ]
+ },
+ "inputDataExample": "{\"config\": {\"monitorId\": \"65a12f60e9a5d109f7596a77\", \"sortOrder\": \"desc\", \"page\": \"0\", \"rowsPerPage\": \"10\"}}",
+ "outputDataExample": "{\"config\": {\"monitorId\": \"65a12f60e9a5d109f7596a77\", \"sortOrder\": \"desc\", \"page\": \"0\", \"rowsPerPage\": \"10\"}}"
+ },
+ {
+ "simStepId": "c5fd192f-ea28-48a0-b3e7-5bc04f521192",
+ "diagramNodeId": "cdd090be-0b62-45b4-baa0-fec01fc3ffd1",
+ "simStepLabel": "Route Request to Controller",
+ "simStepDescription": "The backend Express router receives the incoming GET request and matches it to the appropriate route, forwarding the request to the `getChecksByMonitor` method in the `CheckController`.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/routes/v1/checkRoute.js",
+ "startLine": "19",
+ "endLine": "19",
+ "relevantVariables": [
+ "this.router.get"
+ ]
+ },
+ "inputDataExample": "{\"request\": \"GET /api/v1/checks/65a12f60e9a5d109f7596a77?sortOrder=desc&page=0&rowsPerPage=10\"}",
+ "outputDataExample": "{\"controllerMethod\": \"getChecksByMonitor\"}"
+ },
+ {
+ "simStepId": "4f73f0fe-b390-4fe1-a354-eb0857835126",
+ "diagramNodeId": "839ba9f6-8a34-4919-a04a-13fba1795c61",
+ "simStepLabel": "Forward Request to Service Layer",
+ "simStepDescription": "The `CheckController` validates the request parameters and calls the `CheckService` to handle the business logic of fetching the monitor checks.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/checkController.js",
+ "startLine": "81",
+ "endLine": "85",
+ "relevantVariables": [
+ "getChecksByMonitor",
+ "this.checkService.getChecksByMonitor"
+ ]
+ },
+ "inputDataExample": "{\"monitorId\": \"65a12f60e9a5d109f7596a77\", \"query\": {\"sortOrder\": \"desc\", \"page\": \"0\", \"rowsPerPage\": \"10\"}, \"teamId\": \"65a12f50e9a5d109f7596a66\"}",
+ "outputDataExample": "{\"monitorId\": \"65a12f60e9a5d109f7596a77\", \"query\": {\"sortOrder\": \"desc\", \"page\": \"0\", \"rowsPerPage\": \"10\"}, \"teamId\": \"65a12f50e9a5d109f7596a66\"}"
+ },
+ {
+ "simStepId": "83fea05d-8306-4a73-a43f-882cdd14f50f",
+ "diagramNodeId": "499b46a2-f2a2-4ccd-aecd-012eade2bd23",
+ "simStepLabel": "Fetch Checks from Database",
+ "simStepDescription": "The `CheckService` calls the `checkModule` to interact with the database. It passes the necessary parameters to query for the check history.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/business/checkService.js",
+ "startLine": "36",
+ "endLine": "45",
+ "relevantVariables": [
+ "getChecksByMonitor",
+ "this.db.checkModule.getChecksByMonitor"
+ ]
+ },
+ "inputDataExample": "{\"monitorId\": \"65a12f60e9a5d109f7596a77\", \"query\": {\"sortOrder\": \"desc\", \"page\": \"0\", \"rowsPerPage\": \"10\"}, \"teamId\": \"65a12f50e9a5d109f7596a66\"}",
+ "outputDataExample": "{\"monitorId\": \"65a12f60e9a5d109f7596a77\", \"sortOrder\": \"desc\", \"dateRange\": undefined, \"filter\": undefined, \"ack\": undefined, \"page\": \"0\", \"rowsPerPage\": \"10\", \"status\": undefined}"
+ },
+ {
+ "simStepId": "65da1a9e-041c-4656-9c5b-0a92b761a9be",
+ "diagramNodeId": "42d3bc70-701a-4be0-96b4-d6c2f0519b49",
+ "simStepLabel": "Execute Database Query",
+ "simStepDescription": "The `checkModule` constructs and executes a MongoDB aggregation pipeline on the `Check` collection to find, sort, and paginate the historical checks for the given monitor.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/db/v1/modules/checkModule.js",
+ "startLine": "28",
+ "endLine": "108",
+ "relevantVariables": [
+ "getChecksByMonitor",
+ "this.Check.aggregate"
+ ]
+ },
+ "inputDataExample": "{\"monitorId\": \"65a12f60e9a5d109f7596a77\", \"sortOrder\": -1, \"page\": 0, \"rowsPerPage\": 10}",
+ "outputDataExample": "{\"monitorId\": \"65a12f60e9a5d109f7596a77\", \"sortOrder\": -1, \"page\": 0, \"rowsPerPage\": 10}"
+ },
+ {
+ "simStepId": "5768fdc5-045c-48ef-b0c9-79769403648f",
+ "diagramNodeId": "14df9f01-1358-442d-96f5-eab9a4c4abbf",
+ "simStepLabel": "Return Check Data",
+ "simStepDescription": "The database returns the query results, which flow back up through the `checkModule` and `checkService`. The `checkController` then formats a successful JSON response.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/checkController.js",
+ "startLine": "87",
+ "endLine": "90",
+ "relevantVariables": [
+ "res.success"
+ ]
+ },
+ "inputDataExample": "{\"checksCount\": 152, \"checks\": [{\"_id\": \"...\", \"status\": false}, {\"...\"}]}",
+ "outputDataExample": "{\"msg\": \"Got checks successfully\", \"data\": {\"checksCount\": 152, \"checks\": [{\"_id\": \"...\"}]}}"
+ },
+ {
+ "simStepId": "2c1bdec8-d0e6-4163-a99c-f27899b987c5",
+ "diagramNodeId": "6c5b028c-356c-43c8-a35a-b47448cd8695",
+ "simStepLabel": "API Response with Check History",
+ "simStepDescription": "The backend sends the HTTP response containing the historical check data back to the client.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Utils/NetworkService.js",
+ "startLine": "560",
+ "endLine": "560",
+ "relevantVariables": [
+ "this.axiosInstance.get"
+ ]
+ },
+ "inputDataExample": "{\"success\": true, \"msg\": \"Got checks successfully\", \"data\": {\"checksCount\": 152, \"checks\": [{\"_id\": \"65a13f60e9a5d109f7596b88\", \"status\": false, \"httpStatusCode\": 503, \"responseTime\": 1200}, {\"_id\": \"65a13f50e9a5d109f7596b77\", \"status\": true, \"httpStatusCode\": 200, \"responseTime\": 250}]}}",
+ "outputDataExample": "{\"success\": true, \"msg\": \"Got checks successfully\", \"data\": {\"checksCount\": 152, \"checks\": [{\"_id\": \"65a13f60e9a5d109f7596b88\", \"status\": false, \"httpStatusCode\": 503, \"responseTime\": 1200}, {\"_id\": \"65a13f50e9a5d109f7596b77\", \"status\": true, \"httpStatusCode\": 200, \"responseTime\": 250}]}}"
+ },
+ {
+ "simStepId": "faba07f3-7460-47ae-924d-f92f5b2e821d",
+ "diagramNodeId": "ceb9cda3-968d-44aa-b223-49902adb93b4",
+ "simStepLabel": "Update Frontend State",
+ "simStepDescription": "The `useFetchChecksByMonitor` hook receives the data from the API call and updates the component's state with the new list of checks and the total count.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Hooks/v1/checkHooks.js",
+ "startLine": "109",
+ "endLine": "110",
+ "relevantVariables": [
+ "setChecks",
+ "setChecksCount"
+ ]
+ },
+ "inputDataExample": "{\"data\": {\"checksCount\": 152, \"checks\": [{\"_id\": \"...\"}]}}",
+ "outputDataExample": "{\"checks\": [{\"_id\": \"...\"}], \"checksCount\": 152}"
+ },
+ {
+ "simStepId": "9405dce1-0565-4dfa-9539-a048472acda9",
+ "diagramNodeId": "74b0dd89-9fea-4219-af1f-9571316a9d5a",
+ "simStepLabel": "Pass Data to UI Components",
+ "simStepDescription": "The state update triggers a re-render of the `UptimeDetails` page, passing the `checks` and `checksCount` as props to child components like `ResponseTable`.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Uptime/Details/index.jsx",
+ "startLine": "175",
+ "endLine": "186",
+ "relevantVariables": [
+ "ResponseTable",
+ "checks",
+ "checksCount"
+ ]
+ },
+ "inputDataExample": "{\"checks\": [{\"_id\": \"65a13f60e9a5d109f7596b88\", \"status\": false, \"httpStatusCode\": 503}, {\"_id\": \"65a13f50e9a5d109f7596b77\", \"status\": true, \"httpStatusCode\": 200}], \"checksCount\": 152}",
+ "outputDataExample": "{\"checks\": [{\"_id\": \"65a13f60e9a5d109f7596b88\", \"status\": false, \"httpStatusCode\": 503}, {\"_id\": \"65a13f50e9a5d109f7596b77\", \"status\": true, \"httpStatusCode\": 200}], \"checksCount\": 152}"
+ },
+ {
+ "simStepId": "2b1651cd-2e70-4cc6-b2bb-23f94fa73d66",
+ "diagramNodeId": "c996f7ca-9384-4252-b343-7bfc049d14bb",
+ "simStepLabel": "Render Historical Data Table",
+ "simStepDescription": "The `ResponseTable` component receives the list of checks and renders them in a data table, allowing the user to view the incident history, including status, response time, and when the check occurred.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx",
+ "startLine": "11",
+ "endLine": "92",
+ "relevantVariables": [
+ "ResponseTable",
+ "Table",
+ "TablePagination"
+ ]
+ },
+ "inputDataExample": "{\"checks\": [{\"_id\": \"65a13f60e9a5d109f7596b88\", \"status\": false, \"httpStatusCode\": 503}, {\"_id\": \"65a13f50e9a5d109f7596b77\", \"status\": true, \"httpStatusCode\": 200}]}",
+ "outputDataExample": "{\"tableRows\": [{\"id\": \"status\", \"content\": \"Status\", \"render\": \"...\"}, {\"id\": \"httpStatus\", \"content\": \"HTTP Status\", \"render\": \"...\"}]}"
+ }
+ ],
+ "description": "Provides detailed historical data and tools to manage monitoring events, which are treated as incidents - Every check result is stored, creating a comprehensive log of uptime, downtime, and response time history for each monitor - The UI includes detailed views with charts and tables to analyze performance trends and past incidents - Users can acknowledge incidents (failed checks), which helps in tracking the response to an outage - All historical check data can be purged on a per-monitor or per-team basis ",
+ "simulationNodesAndEdges": {
+ "78a8a7c5-41d3-4453-a7f7-ac632bbef288": {
+ "simStepIds": [
+ "35195328-9847-4678-b014-9331553f0d19"
+ ]
+ },
+ "4a5847d0-91a0-4b4c-8517-2f4ff9a7e8d4": {
+ "simStepIds": [
+ "8dc4347c-3e9e-40d7-ae6f-54684f2e6956"
+ ]
+ },
+ "cdd090be-0b62-45b4-baa0-fec01fc3ffd1": {
+ "simStepIds": [
+ "c5fd192f-ea28-48a0-b3e7-5bc04f521192"
+ ]
+ },
+ "499b46a2-f2a2-4ccd-aecd-012eade2bd23": {
+ "simStepIds": [
+ "83fea05d-8306-4a73-a43f-882cdd14f50f"
+ ]
+ },
+ "14df9f01-1358-442d-96f5-eab9a4c4abbf": {
+ "simStepIds": [
+ "5768fdc5-045c-48ef-b0c9-79769403648f"
+ ]
+ },
+ "ceb9cda3-968d-44aa-b223-49902adb93b4": {
+ "simStepIds": [
+ "faba07f3-7460-47ae-924d-f92f5b2e821d"
+ ]
+ },
+ "c996f7ca-9384-4252-b343-7bfc049d14bb": {
+ "simStepIds": [
+ "2b1651cd-2e70-4cc6-b2bb-23f94fa73d66"
+ ]
+ },
+ "e90e4943-6108-46e3-8a2c-a9a1c7361279": {
+ "simStepIds": [
+ "49979250-1a32-4da5-95e0-58652fe218f9"
+ ]
+ },
+ "2b8ae59e-ed8d-4837-9a9b-64eb480f982c": {
+ "simStepIds": [
+ "336b534d-8d6a-4639-af8e-889b38f51ab1"
+ ]
+ },
+ "839ba9f6-8a34-4919-a04a-13fba1795c61": {
+ "simStepIds": [
+ "4f73f0fe-b390-4fe1-a354-eb0857835126"
+ ]
+ },
+ "42d3bc70-701a-4be0-96b4-d6c2f0519b49": {
+ "simStepIds": [
+ "65da1a9e-041c-4656-9c5b-0a92b761a9be"
+ ]
+ },
+ "6c5b028c-356c-43c8-a35a-b47448cd8695": {
+ "simStepIds": [
+ "2c1bdec8-d0e6-4163-a99c-f27899b987c5"
+ ]
+ },
+ "74b0dd89-9fea-4219-af1f-9571316a9d5a": {
+ "simStepIds": [
+ "9405dce1-0565-4dfa-9539-a048472acda9"
+ ]
+ }
+ },
+ "isAIGenerated": true,
+ "keywords": "Check, getChecksByMonitor, ResponseTable",
+ "generationPrompt": "Incident History, Analysis, and Management",
+ "generationKeywords": "Check, getChecksByMonitor, ResponseTable"
+ },
+ "Website Performance and Page Speed Monitoring": {
+ "name": "Website Performance and Page Speed Monitoring",
+ "simSteps": [
+ {
+ "simStepId": "b0cc6e6a-961e-4ea9-aa8a-6bfa44d6ba12",
+ "diagramNodeId": "0622b212-3a4a-484d-9b7a-125864d785cd",
+ "simStepLabel": "Create PageSpeed Monitor",
+ "simStepDescription": "The user navigates to the PageSpeed creation page, fills in the details for a new monitor (URL, name, interval), and submits the form. This triggers the `handleSubmit` function, which calls the `createMonitor` hook to send the data to the backend API.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/PageSpeed/Create/index.jsx",
+ "startLine": "111",
+ "endLine": "143",
+ "relevantVariables": [
+ "handleSubmit",
+ "createMonitor",
+ "updateMonitor",
+ "monitor",
+ "form"
+ ]
+ },
+ "inputDataExample": "{\"formState\": {\"url\": \"https://example.com\",\"name\": \"Example Homepage\",\"type\": \"pagespeed\",\"interval\": 180000}}",
+ "outputDataExample": "{\"monitor\": {\"url\": \"https://example.com\",\"name\": \"Example Homepage\",\"type\": \"pagespeed\",\"interval\": 180000}, \"redirect\": \"/pagespeed\"}"
+ },
+ {
+ "simStepId": "f8df1cad-6051-466e-a13e-47d710ba1c14",
+ "diagramNodeId": "b8e83b0a-69ff-4c34-a7fe-cb8c9ba85438",
+ "simStepLabel": "API Call: Create Monitor",
+ "simStepDescription": "The frontend sends a POST request to the backend API to create a new monitor record in the database. This request includes the monitor's configuration details.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/PageSpeed/Create/index.jsx",
+ "startLine": "119",
+ "endLine": "119",
+ "relevantVariables": [
+ "createMonitor"
+ ]
+ },
+ "inputDataExample": "{\"monitor\": {\"url\": \"https://example.com\",\"name\": \"Example Homepage\",\"type\": \"pagespeed\",\"notifications\": [],\"interval\": 180000}, \"redirect\": \"/pagespeed\"}",
+ "outputDataExample": "{\"monitor\": {\"url\": \"https://example.com\",\"name\": \"Example Homepage\",\"type\": \"pagespeed\",\"notifications\": [],\"interval\": 180000}, \"redirect\": \"/pagespeed\"}"
+ },
+ {
+ "simStepId": "53968267-0c6e-42f4-88b3-cd90f44532ae",
+ "diagramNodeId": "6527fb47-157e-4bba-9aed-adde061c5689",
+ "simStepLabel": "Backend Requests PageSpeed Analysis",
+ "simStepDescription": "A background job for the newly created monitor triggers the `NetworkService` to request a performance analysis from the Google PageSpeed Insights API. It constructs the API URL using the monitor's target URL and the configured PageSpeed API key.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v2/infrastructure/NetworkService.ts",
+ "startLine": "129",
+ "endLine": "162",
+ "relevantVariables": [
+ "requestPagespeed",
+ "apiKey",
+ "pagespeedUrl",
+ "pagespeedResponse"
+ ]
+ },
+ "inputDataExample": "{\"monitor\": {\"_id\": \"64b9a6b3e1b6f0b3e8a7d1c0\",\"url\": \"https://example.com\",\"type\": \"pagespeed\"}}",
+ "outputDataExample": "{\"statusResponse\": {\"monitorId\": \"64b9a6b3e1b6f0b3e8a7d1c0\",\"status\": \"up\",\"responseTime\": 450, \"payload\": {\"lighthouseResult\": {...}}}}"
+ },
+ {
+ "simStepId": "0b380571-3896-4365-b5dc-187494f02e0d",
+ "diagramNodeId": "0d3c8091-3d91-4e42-9b46-7e35ed726a7a",
+ "simStepLabel": "Google PageSpeed API Response",
+ "simStepDescription": "The Google PageSpeed Insights API responds with a detailed JSON payload containing the Lighthouse analysis results, including scores for performance, accessibility, SEO, and best practices.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v2/infrastructure/NetworkService.ts",
+ "startLine": "156",
+ "endLine": "156",
+ "relevantVariables": [
+ "payload"
+ ]
+ },
+ "inputDataExample": "{\"lighthouseResult\": {\"categories\": {\"performance\": {\"score\": 0.92},\"accessibility\": {\"score\": 0.98},\"best-practices\": {\"score\": 1},\"seo\": {\"score\": 0.95}},\"audits\": {\"first-contentful-paint\": {\"score\": 0.99,\"numericValue\": 850},\"largest-contentful-paint\": {\"score\": 0.91,\"numericValue\": 1450}}}}",
+ "outputDataExample": "{\"lighthouseResult\": {\"categories\": {\"performance\": {\"score\": 0.92},\"accessibility\": {\"score\": 0.98},\"best-practices\": {\"score\": 1},\"seo\": {\"score\": 0.95}},\"audits\": {\"first-contentful-paint\": {\"score\": 0.99,\"numericValue\": 850},\"largest-contentful-paint\": {\"score\": 0.91,\"numericValue\": 1450}}}}"
+ },
+ {
+ "simStepId": "01206383-2db8-4b18-84d0-be2a0289c196",
+ "diagramNodeId": "f89cbd87-c961-4f8b-92b6-714da57718d5",
+ "simStepLabel": "Backend Processes Lighthouse Data",
+ "simStepDescription": "The `CheckService` receives the raw data from the Google API. The `buildPagespeedCheck` function extracts and transforms the relevant scores and audit details into a structured `Check` object, preparing it for database storage.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v2/business/CheckService.ts",
+ "startLine": "82",
+ "endLine": "102",
+ "relevantVariables": [
+ "buildPagespeedCheck",
+ "isPagespeedPayload",
+ "lighthouseResult",
+ "check.lighthouse"
+ ]
+ },
+ "inputDataExample": "{\"statusResponse\": {\"payload\": {\"lighthouseResult\": {\"categories\": {\"performance\": {\"score\": 0.92},\"accessibility\": {\"score\": 0.98},\"best-practices\": {\"score\": 1},\"seo\": {\"score\": 0.95}},\"audits\": {\"cumulative-layout-shift\": {},\"speed-index\": {},\"first-contentful-paint\": {},\"largest-contentful-paint\": {},\"total-blocking-time\": {}}}}}}",
+ "outputDataExample": "{\"check\": {\"lighthouse\": {\"accessibility\": 0.98,\"bestPractices\": 1,\"seo\": 0.95,\"performance\": 0.92,\"audits\": {\"cls\": {},\"si\": {},\"fcp\": {},\"lcp\": {},\"tbt\": {}}}}}"
+ },
+ {
+ "simStepId": "a629a102-0df1-4798-a7a2-45c8f6cdad29",
+ "diagramNodeId": "27c7191b-80a2-475b-9371-e106108aff1c",
+ "simStepLabel": "Store Processed Check Data",
+ "simStepDescription": "The processed and structured check data, containing the key performance metrics, is saved to the 'checks' collection in the database, associated with the corresponding monitor ID.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/db/v2/models/checks/Check.ts",
+ "startLine": "237",
+ "endLine": "268",
+ "relevantVariables": [
+ "lighthouse"
+ ]
+ },
+ "inputDataExample": "{\"check\": {\"monitorId\": \"64b9a6b3e1b6f0b3e8a7d1c0\", \"status\": \"up\", \"responseTime\": 450, \"lighthouse\": {\"performance\": 0.92, \"accessibility\": 0.98, \"bestPractices\": 1, \"seo\": 0.95, \"audits\": {...}}}}",
+ "outputDataExample": "{\"check\": {\"monitorId\": \"64b9a6b3e1b6f0b3e8a7d1c0\", \"status\": \"up\", \"responseTime\": 450, \"lighthouse\": {\"performance\": 0.92, \"accessibility\": 0.98, \"bestPractices\": 1, \"seo\": 0.95, \"audits\": {...}}}}"
+ },
+ {
+ "simStepId": "5616ac6d-c281-4879-82f1-6590c721c8f7",
+ "diagramNodeId": "ca6ee27b-1fbf-49de-978a-d6de259cc3d5",
+ "simStepLabel": "Backend Aggregates Performance Data",
+ "simStepDescription": "When a user views the details of a PageSpeed monitor, the frontend requests historical data. The `MonitorService` on the backend uses a MongoDB aggregation pipeline to group checks by time intervals and calculate average scores for metrics like performance, SEO, etc. This is defined by helper functions like `getPageSpeedGroup`.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v2/business/MonitorService.ts",
+ "startLine": "106",
+ "endLine": "120",
+ "relevantVariables": [
+ "getPageSpeedGroup",
+ "accessibility",
+ "bestPractices",
+ "seo",
+ "performance"
+ ]
+ },
+ "inputDataExample": "{\"monitorId\": \"64b9a6b3e1b6f0b3e8a7d1c0\", \"range\": \"7d\"}",
+ "outputDataExample": "[{\"_id\": \"2023-07-20T10:00:00Z\", \"avgResponseTime\": 450, \"performance\": 0.92, \"seo\": 0.95, \"accessibility\": 0.98, \"bestPractices\": 1.0}, {\"_id\": \"2023-07-20T11:00:00Z\", \"avgResponseTime\": 465, \"performance\": 0.91, \"seo\": 0.95, \"accessibility\": 0.98, \"bestPractices\": 1.0}]"
+ },
+ {
+ "simStepId": "4ed43eaf-fa8f-4fce-af87-8a5f74dd5c08",
+ "diagramNodeId": "7be432cd-2587-4eab-abdc-f7b9c1923597",
+ "simStepLabel": "Aggregated Data Transmitted to Frontend",
+ "simStepDescription": "The backend sends the aggregated and time-series formatted performance data to the frontend in a JSON response.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v2/business/MonitorService.ts",
+ "startLine": "368",
+ "endLine": "372",
+ "relevantVariables": [
+ "Check.aggregate"
+ ]
+ },
+ "inputDataExample": "[{\"_id\": \"2023-07-20T10:00:00Z\", \"performance\": 0.92, \"seo\": 0.95, \"accessibility\": 0.98}, {\"_id\": \"2023-07-20T11:00:00Z\", \"performance\": 0.91, \"seo\": 0.95, \"accessibility\": 0.98}]",
+ "outputDataExample": "[{\"_id\": \"2023-07-20T10:00:00Z\", \"performance\": 0.92, \"seo\": 0.95, \"accessibility\": 0.98}, {\"_id\": \"2023-07-20T11:00:00Z\", \"performance\": 0.91, \"seo\": 0.95, \"accessibility\": 0.98}]"
+ },
+ {
+ "simStepId": "0ec0eb06-05eb-4204-b077-9e47eb2cc316",
+ "diagramNodeId": "9ef9e218-4742-487a-9a88-5c70e788ccdc",
+ "simStepLabel": "Frontend Renders Performance Charts",
+ "simStepDescription": "The `PageSpeedDetails` component receives the historical data from the backend. It then passes this data to specialized child components like `PageSpeedAreaChart` and `PerformanceReport` to render visualizations of the website's performance over time.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/PageSpeed/Details/index.jsx",
+ "startLine": "91",
+ "endLine": "126",
+ "relevantVariables": [
+ "PageSpeedStatusBoxes",
+ "MonitorTimeFrameHeader",
+ "PageSpeedAreaChart",
+ "PerformanceReport",
+ "monitor",
+ "audits"
+ ]
+ },
+ "inputDataExample": "{\"monitor\": {\"checks\": [{\"createdAt\": \"2023-07-20T10:00:00Z\", \"performance\": 0.92, \"seo\": 0.95}, {\"createdAt\": \"2023-07-20T11:00:00Z\", \"performance\": 0.91, \"seo\": 0.95}]}}",
+ "outputDataExample": "{\"renderedUI\": \"A page with charts and graphs showing performance trends.\"}"
+ }
+ ],
+ "description": "Integrates with Google's PageSpeed Insights to monitor and analyze web page performance - Tracks key web vitals and performance metrics such as SEO, Accessibility, Best Practices, and Performance scores - Provides a grid view of all page speed monitors for a quick overview of website health - Detailed reports show historical performance data, allowing users to track improvements or regressions over time - This feature requires a Google PageSpeed API key to be configured in the settings ",
+ "simulationNodesAndEdges": {
+ "0622b212-3a4a-484d-9b7a-125864d785cd": {
+ "simStepIds": [
+ "b0cc6e6a-961e-4ea9-aa8a-6bfa44d6ba12"
+ ]
+ },
+ "6527fb47-157e-4bba-9aed-adde061c5689": {
+ "simStepIds": [
+ "53968267-0c6e-42f4-88b3-cd90f44532ae"
+ ]
+ },
+ "f89cbd87-c961-4f8b-92b6-714da57718d5": {
+ "simStepIds": [
+ "01206383-2db8-4b18-84d0-be2a0289c196"
+ ]
+ },
+ "ca6ee27b-1fbf-49de-978a-d6de259cc3d5": {
+ "simStepIds": [
+ "5616ac6d-c281-4879-82f1-6590c721c8f7"
+ ]
+ },
+ "9ef9e218-4742-487a-9a88-5c70e788ccdc": {
+ "simStepIds": [
+ "0ec0eb06-05eb-4204-b077-9e47eb2cc316"
+ ]
+ },
+ "b8e83b0a-69ff-4c34-a7fe-cb8c9ba85438": {
+ "simStepIds": [
+ "f8df1cad-6051-466e-a13e-47d710ba1c14"
+ ]
+ },
+ "0d3c8091-3d91-4e42-9b46-7e35ed726a7a": {
+ "simStepIds": [
+ "0b380571-3896-4365-b5dc-187494f02e0d"
+ ]
+ },
+ "27c7191b-80a2-475b-9371-e106108aff1c": {
+ "simStepIds": [
+ "a629a102-0df1-4798-a7a2-45c8f6cdad29"
+ ]
+ },
+ "7be432cd-2587-4eab-abdc-f7b9c1923597": {
+ "simStepIds": [
+ "4ed43eaf-fa8f-4fce-af87-8a5f74dd5c08"
+ ]
+ }
+ },
+ "isAIGenerated": true,
+ "keywords": "pagespeed, lighthouse, performance",
+ "generationPrompt": "Website Performance and Page Speed Monitoring",
+ "generationKeywords": "pagespeed, lighthouse, performance"
+ },
+ "Scheduled Maintenance Windows": {
+ "name": "Scheduled Maintenance Windows",
+ "simSteps": [
+ {
+ "simStepId": "2401ef7a-8d8e-4056-868a-547ab213daf1",
+ "diagramNodeId": "20335f29-a2ac-4e0c-aba6-28707748ecd4",
+ "simStepLabel": "Flow: Create Maintenance Window - User Interaction",
+ "simStepDescription": "A user navigates to the 'Create Maintenance Window' page. They fill out a form with details such as a name, start date/time, duration, recurrence, and select the monitors to which this window will apply.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx",
+ "startLine": "46",
+ "endLine": "131",
+ "relevantVariables": [
+ "CreateMaintenance",
+ "form",
+ "setForm",
+ "handleFormChange",
+ "handleSubmit"
+ ]
+ },
+ "inputDataExample": "{}",
+ "outputDataExample": "{\"name\":\"Server Patching Window\",\"startDate\":\"2023-10-27T00:00:00.000Z\",\"startTime\":\"2023-10-27T02:00:00.000Z\",\"duration\":\"60\",\"durationUnit\":\"minutes\",\"repeat\":\"none\",\"monitors\":[{\"_id\":\"652ff379963e792a5a3a0e4e\",\"name\":\"Main Production Server\"}]}"
+ },
+ {
+ "simStepId": "f881435b-3bc2-453d-b6bf-56ad3a12f120",
+ "diagramNodeId": "d3bdc6ff-875e-472e-88c8-eb10b20c526e",
+ "simStepLabel": "Flow: Create Maintenance Window - Form Submission",
+ "simStepDescription": "Upon clicking the 'Create Maintenance' button, the form state is passed to the `handleSubmit` function which then calls the `handleSubmitForm` action from a custom hook.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx",
+ "startLine": "108",
+ "endLine": "109",
+ "relevantVariables": [
+ "handleSubmit",
+ "handleSubmitForm"
+ ]
+ },
+ "inputDataExample": "{\"name\":\"Server Patching Window\",\"startDate\":\"2023-10-27T00:00:00.000Z\",\"startTime\":\"2023-10-27T02:00:00.000Z\",\"duration\":\"60\",\"durationUnit\":\"minutes\",\"repeat\":\"none\",\"monitors\":[{\"_id\":\"652ff379963e792a5a3a0e4e\",\"name\":\"Main Production Server\"}]}",
+ "outputDataExample": "{\"name\":\"Server Patching Window\",\"startDate\":\"2023-10-27T00:00:00.000Z\",\"startTime\":\"2023-10-27T02:00:00.000Z\",\"duration\":\"60\",\"durationUnit\":\"minutes\",\"repeat\":\"none\",\"monitors\":[{\"_id\":\"652ff379963e792a5a3a0e4e\",\"name\":\"Main Production Server\"}]}"
+ },
+ {
+ "simStepId": "5e0d7085-099b-478a-afaa-47ad4dd07043",
+ "diagramNodeId": "5bdfdfa4-5d93-49fa-b05c-905813e6cf9d",
+ "simStepLabel": "Flow: Create Maintenance Window - Prepare API Request",
+ "simStepDescription": "The `handleSubmitForm` hook processes the form data, calculates start and end timestamps, and constructs a request object. It then determines whether to call `createMaintenanceWindow` or `editMaintenanceWindow` on the network service.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx",
+ "startLine": "25",
+ "endLine": "58",
+ "relevantVariables": [
+ "handleSubmitForm",
+ "networkService.createMaintenanceWindow",
+ "networkService.editMaintenanceWindow"
+ ]
+ },
+ "inputDataExample": "{\"form\":{\"name\":\"Server Patching Window\",\"startDate\":\"2023-10-27T00:00:00.000Z\",\"startTime\":\"2023-10-27T02:00:00.000Z\",\"duration\":\"60\",\"durationUnit\":\"minutes\",\"repeat\":\"none\",\"monitors\":[{\"_id\":\"652ff379963e792a5a3a0e4e\"}]}}",
+ "outputDataExample": "{\"maintenanceWindow\":{\"name\":\"Server Patching Window\",\"start\":\"2023-10-27T02:00:00.000Z\",\"end\":\"2023-10-27T03:00:00.000Z\",\"repeat\":0,\"oneTime\":true,\"monitors\":[\"652ff379963e792a5a3a0e4e\"],\"expiry\":\"2023-10-27T03:00:00.000Z\"}}"
+ },
+ {
+ "simStepId": "0660b48b-139f-4ecf-b7d9-f12f544e0ab5",
+ "diagramNodeId": "fd5e4447-123b-4b2d-8bce-1d1ae7cb00af",
+ "simStepLabel": "Flow: Create Maintenance Window - API Call",
+ "simStepDescription": "The `NetworkService` sends the prepared maintenance window data as a POST request to the `/api/v1/maintenance-window` endpoint.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Utils/NetworkService.js",
+ "startLine": "762",
+ "endLine": "766",
+ "relevantVariables": [
+ "createMaintenanceWindow",
+ "this.axiosInstance.post"
+ ]
+ },
+ "inputDataExample": "{\"maintenanceWindow\":{\"name\":\"Server Patching Window\",\"start\":\"2023-10-27T02:00:00.000Z\",\"end\":\"2023-10-27T03:00:00.000Z\",\"repeat\":0,\"oneTime\":true,\"monitors\":[\"652ff379963e792a5a3a0e4e\"],\"expiry\":\"2023-10-27T03:00:00.000Z\"}}",
+ "outputDataExample": "{\"maintenanceWindow\":{\"name\":\"Server Patching Window\",\"start\":\"2023-10-27T02:00:00.000Z\",\"end\":\"2023-10-27T03:00:00.000Z\",\"repeat\":0,\"oneTime\":true,\"monitors\":[\"652ff379963e792a5a3a0e4e\"],\"expiry\":\"2023-10-27T03:00:00.000Z\"}}"
+ },
+ {
+ "simStepId": "3e93ef65-8e2c-405b-8dc1-56d8e93937b3",
+ "diagramNodeId": "a391e8ea-9dbe-4fe4-90df-bf475d8b7ef8",
+ "simStepLabel": "Flow: Create Maintenance Window - API Routing",
+ "simStepDescription": "The backend Express server receives the request. The routing configuration maps the `/api/v1/maintenance-window` path to the `maintenanceWindowRoutes`, which in turn directs the POST request to the `createMaintenanceWindows` method in the controller.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/config/routes.js",
+ "startLine": "43",
+ "endLine": "43",
+ "relevantVariables": [
+ "app",
+ "verifyJWT",
+ "maintenanceWindowRoutes"
+ ]
+ },
+ "inputDataExample": "{\"method\":\"POST\",\"url\":\"/api/v1/maintenance-window\",\"body\":{\"name\":\"Server Patching Window\",\"start\":\"2023-10-27T02:00:00.000Z\",\"end\":\"2023-10-27T03:00:00.000Z\",\"repeat\":0,\"oneTime\":true,\"monitors\":[\"652ff379963e792a5a3a0e4e\"]}}",
+ "outputDataExample": "{\"controllerMethod\":\"createMaintenanceWindows\",\"params\":{}}"
+ },
+ {
+ "simStepId": "e4cc5ce8-6da9-4281-9204-29c061ab13ce",
+ "diagramNodeId": "e2588541-3ffc-47c6-8f8a-41d30ea7fc72",
+ "simStepLabel": "Flow: Create Maintenance Window - Controller Invocation",
+ "simStepDescription": "The request and response objects are passed from the router to the `createMaintenanceWindows` controller method.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/routes/v1/maintenanceWindowRoute.js",
+ "startLine": "10",
+ "endLine": "10",
+ "relevantVariables": [
+ "this.router.post",
+ "this.mwController.createMaintenanceWindows"
+ ]
+ },
+ "inputDataExample": "{\"req\": {\"body\": {\"name\":\"Server Patching Window\", \"monitors\":[\"652ff379963e792a5a3a0e4e\"]}, \"user\": {\"teamId\":\"652ff379963e792a5a3a0e4d\"}}}",
+ "outputDataExample": "{\"req\": {\"body\": {\"name\":\"Server Patching Window\", \"monitors\":[\"652ff379963e792a5a3a0e4e\"]}, \"user\": {\"teamId\":\"652ff379963e792a5a3a0e4d\"}}}"
+ },
+ {
+ "simStepId": "95fc1919-339d-4659-98cb-3dc50f662075",
+ "diagramNodeId": "0fae6e75-38ea-444d-a1bf-0f820ba443b9",
+ "simStepLabel": "Flow: Create Maintenance Window - Controller Logic",
+ "simStepDescription": "The `maintenanceWindowController` validates the request body using Joi, ensures a `teamId` exists on the user object, and then calls the `maintenanceWindowService` to handle the business logic of creating the window.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/maintenanceWindowController.js",
+ "startLine": "27",
+ "endLine": "41",
+ "relevantVariables": [
+ "createMaintenanceWindows",
+ "createMaintenanceWindowBodyValidation",
+ "this.maintenanceWindowService.createMaintenanceWindow"
+ ]
+ },
+ "inputDataExample": "{\"req\": {\"body\": {\"name\":\"Server Patching Window\", \"monitors\":[\"652ff379963e792a5a3a0e4e\"]}, \"user\": {\"teamId\":\"652ff379963e792a5a3a0e4d\"}}}",
+ "outputDataExample": "{\"teamId\":\"652ff379963e792a5a3a0e4d\",\"body\":{\"name\":\"Server Patching Window\",\"monitors\":[\"652ff379963e792a5a3a0e4e\"]}}"
+ },
+ {
+ "simStepId": "c3d1a95c-e93d-499a-8830-a6d870902414",
+ "diagramNodeId": "e1d59cf6-0d96-4e0a-ad17-2a7984a9157f",
+ "simStepLabel": "Flow: Create Maintenance Window - Service Layer Call",
+ "simStepDescription": "The controller passes the teamId and request body to the `createMaintenanceWindow` method in the `maintenanceWindowService`.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/maintenanceWindowController.js",
+ "startLine": "35",
+ "endLine": "35",
+ "relevantVariables": [
+ "this.maintenanceWindowService.createMaintenanceWindow"
+ ]
+ },
+ "inputDataExample": "{\"teamId\":\"652ff379963e792a5a3a0e4d\",\"body\":{\"name\":\"Server Patching Window\",\"monitors\":[\"652ff379963e792a5a3a0e4e\"]}}",
+ "outputDataExample": "{\"teamId\":\"652ff379963e792a5a3a0e4d\",\"body\":{\"name\":\"Server Patching Window\",\"monitors\":[\"652ff379963e792a5a3a0e4e\"]}}"
+ },
+ {
+ "simStepId": "6e2cf739-1514-4c37-9874-10b68a47010e",
+ "diagramNodeId": "560d268b-ef0f-4cd2-bcfe-9dc0a0e2ce6f",
+ "simStepLabel": "Flow: Create Maintenance Window - Business Logic Execution",
+ "simStepDescription": "The `maintenanceWindowService` verifies that all monitors specified in the request belong to the user's team. It then iterates through each monitor ID and prepares a separate database transaction to create a maintenance window record for each one.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/business/maintenanceWindowService.js",
+ "startLine": "18",
+ "endLine": "39",
+ "relevantVariables": [
+ "createMaintenanceWindow",
+ "this.db.monitorModule.getMonitorsByIds",
+ "this.db.maintenanceWindowModule.createMaintenanceWindow",
+ "Promise.all"
+ ]
+ },
+ "inputDataExample": "{\"teamId\":\"652ff379963e792a5a3a0e4d\",\"body\":{\"name\":\"Server Patching Window\",\"start\":\"2023-10-27T02:00:00.000Z\",\"end\":\"2023-10-27T03:00:00.000Z\",\"monitors\":[\"652ff379963e792a5a3a0e4e\"]}}",
+ "outputDataExample": "[{\"teamId\":\"652ff379963e792a5a3a0e4d\",\"monitorId\":\"652ff379963e792a5a3a0e4e\",\"name\":\"Server Patching Window\",\"start\":\"2023-10-27T02:00:00.000Z\",\"end\":\"2023-10-27T03:00:00.000Z\"}]"
+ },
+ {
+ "simStepId": "80aed28e-68b6-47bf-8bc9-ededb4824066",
+ "diagramNodeId": "12d0ab8a-392f-48fa-9efa-06ffe1f3a252",
+ "simStepLabel": "Flow: Create Maintenance Window - Database Module Call",
+ "simStepDescription": "For each monitor, the service layer calls the `createMaintenanceWindow` method in the `maintenanceWindowModule`, passing the structured data for the new record.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/business/maintenanceWindowService.js",
+ "startLine": "28",
+ "endLine": "37",
+ "relevantVariables": [
+ "dbTransactions",
+ "this.db.maintenanceWindowModule.createMaintenanceWindow"
+ ]
+ },
+ "inputDataExample": "{\"teamId\":\"652ff379963e792a5a3a0e4d\",\"monitorId\":\"652ff379963e792a5a3a0e4e\",\"name\":\"Server Patching Window\",\"start\":\"2023-10-27T02:00:00.000Z\",\"end\":\"2023-10-27T03:00:00.000Z\",\"oneTime\":true}",
+ "outputDataExample": "{\"teamId\":\"652ff379963e792a5a3a0e4d\",\"monitorId\":\"652ff379963e792a5a3a0e4e\",\"name\":\"Server Patching Window\",\"start\":\"2023-10-27T02:00:00.000Z\",\"end\":\"2023-10-27T03:00:00.000Z\",\"oneTime\":true}"
+ },
+ {
+ "simStepId": "a3c47fd4-c701-46c2-9aff-94a667b3e368",
+ "diagramNodeId": "3890b98d-a133-4279-a961-5d2facc6a17a",
+ "simStepLabel": "Flow: Create Maintenance Window - Database Record Creation",
+ "simStepDescription": "The `maintenanceWindowModule` instantiates a new `MaintenanceWindow` Mongoose model. If the window is a one-time event, it sets an `expiry` field to the end time for automatic cleanup by MongoDB's TTL index. Finally, it saves the new document to the database.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/db/v1/modules/maintenanceWindowModule.js",
+ "startLine": "7",
+ "endLine": "22",
+ "relevantVariables": [
+ "createMaintenanceWindow",
+ "this.MaintenanceWindow",
+ "maintenanceWindow.save"
+ ]
+ },
+ "inputDataExample": "{\"teamId\":\"652ff379963e792a5a3a0e4d\",\"monitorId\":\"652ff379963e792a5a3a0e4e\",\"name\":\"Server Patching Window\",\"start\":\"2023-10-27T02:00:00.000Z\",\"end\":\"2023-10-27T03:00:00.000Z\",\"oneTime\":true}",
+ "outputDataExample": "{\"_id\":\"653ba9a6331a31004887396a\",\"monitorId\":\"652ff379963e792a5a3a0e4e\",\"teamId\":\"652ff379963e792a5a3a0e4d\",\"name\":\"Server Patching Window\",\"active\":true,\"start\":\"2023-10-27T02:00:00.000Z\",\"end\":\"2023-10-27T03:00:00.000Z\",\"expiry\":\"2023-10-27T03:00:00.000Z\"}"
+ },
+ {
+ "simStepId": "69fdc2a9-837f-466c-9562-a12319019580",
+ "diagramNodeId": "fcf4eef4-394c-4872-9124-f7a0b32cdc3b",
+ "simStepLabel": "Flow: Create Maintenance Window - API Response",
+ "simStepDescription": "After the database operations are complete, a success response is sent back through the layers to the frontend.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/maintenanceWindowController.js",
+ "startLine": "37",
+ "endLine": "39",
+ "relevantVariables": [
+ "res.success",
+ "this.stringService.maintenanceWindowCreate"
+ ]
+ },
+ "inputDataExample": "{\"msg\":\"Maintenance Window created successfully.\"}",
+ "outputDataExample": "{\"msg\":\"Maintenance Window created successfully.\"}"
+ },
+ {
+ "simStepId": "ebe8fd52-f2a1-4a98-a71b-b04d6f4cdadc",
+ "diagramNodeId": "a5a84b08-8c70-4023-8d08-b74e23aab4d7",
+ "simStepLabel": "Flow: Create Maintenance Window - UI Update",
+ "simStepDescription": "The frontend receives the success response. A success toast message is displayed, and the user is navigated back to the main maintenance windows table.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx",
+ "startLine": "111",
+ "endLine": "119",
+ "relevantVariables": [
+ "request",
+ "createToast",
+ "navigate"
+ ]
+ },
+ "inputDataExample": "{\"data\":{\"msg\":\"Maintenance Window created successfully.\"}}",
+ "outputDataExample": "{}"
+ },
+ {
+ "simStepId": "82b5ea14-bb00-4511-aba4-76196029fd81",
+ "diagramNodeId": "d145bb4c-e1e0-473a-b6bc-decc932b738b",
+ "simStepLabel": "Flow: View Maintenance Windows - Page Load",
+ "simStepDescription": "User navigates to the '/maintenance' page. The `Maintenance` component mounts and triggers a `useEffect` hook to fetch the list of maintenance windows.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Maintenance/index.jsx",
+ "startLine": "25",
+ "endLine": "47",
+ "relevantVariables": [
+ "useEffect",
+ "networkService.getMaintenanceWindowsByTeamId"
+ ]
+ },
+ "inputDataExample": "{}",
+ "outputDataExample": "{\"page\":0,\"rowsPerPage\":5}"
+ },
+ {
+ "simStepId": "7e20cd6c-d24e-4540-86d6-d6a9fa087314",
+ "diagramNodeId": "7f921125-500f-4902-abc3-936ecac033d8",
+ "simStepLabel": "Flow: View Maintenance Windows - API Call",
+ "simStepDescription": "The `networkService` makes a GET request to the backend API to fetch the maintenance windows for the user's team, including pagination parameters.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Utils/NetworkService.js",
+ "startLine": "824",
+ "endLine": "824",
+ "relevantVariables": [
+ "getMaintenanceWindowsByTeamId",
+ "this.axiosInstance.get"
+ ]
+ },
+ "inputDataExample": "{\"page\":0,\"rowsPerPage\":5}",
+ "outputDataExample": "{\"page\":0,\"rowsPerPage\":5}"
+ },
+ {
+ "simStepId": "1251da83-5949-4e24-a1d2-fc74ff785aa9",
+ "diagramNodeId": "c35a065c-2808-402c-877e-9055d956ce0d",
+ "simStepLabel": "Flow: View Maintenance Windows - Controller and Service",
+ "simStepDescription": "The backend routes the request to `maintenanceWindowController.getMaintenanceWindowsByTeamId`. This controller calls the corresponding service, which in turn calls the database module to query for the data.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/maintenanceWindowController.js",
+ "startLine": "75",
+ "endLine": "75",
+ "relevantVariables": [
+ "this.maintenanceWindowService.getMaintenanceWindowsByTeamId"
+ ]
+ },
+ "inputDataExample": "{\"teamId\":\"652ff379963e792a5a3a0e4d\",\"query\":{\"page\":\"0\",\"rowsPerPage\":\"5\"}}",
+ "outputDataExample": "{\"teamId\":\"652ff379963e792a5a3a0e4d\",\"query\":{\"page\":\"0\",\"rowsPerPage\":\"5\"}}"
+ },
+ {
+ "simStepId": "feb8bd4f-339f-4132-a709-89c63c4efa9c",
+ "diagramNodeId": "c3127c74-43f1-42d7-8da6-b0a494c3658f",
+ "simStepLabel": "Flow: View Maintenance Windows - Database Query",
+ "simStepDescription": "The service layer invokes the database module to fetch the maintenance windows from the database.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/business/maintenanceWindowService.js",
+ "startLine": "48",
+ "endLine": "48",
+ "relevantVariables": [
+ "this.db.maintenanceWindowModule.getMaintenanceWindowsByTeamId"
+ ]
+ },
+ "inputDataExample": "{\"teamId\":\"652ff379963e792a5a3a0e4d\",\"query\":{\"page\":\"0\",\"rowsPerPage\":\"5\"}}",
+ "outputDataExample": "{\"teamId\":\"652ff379963e792a5a3a0e4d\",\"query\":{\"page\":\"0\",\"rowsPerPage\":\"5\"}}"
+ },
+ {
+ "simStepId": "9bfbae5a-f487-40ef-86f5-04a1cc3c7662",
+ "diagramNodeId": "27d9160a-4f8d-4dff-a09b-1678bc28cf2c",
+ "simStepLabel": "Flow: View Maintenance Windows - Data Retrieval",
+ "simStepDescription": "The `maintenanceWindowModule` constructs a MongoDB query to find all maintenance windows for the given `teamId`, applying pagination (skip, limit) and sorting. It executes the query and returns the results along with the total count.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/db/v1/modules/maintenanceWindowModule.js",
+ "startLine": "37",
+ "endLine": "66",
+ "relevantVariables": [
+ "getMaintenanceWindowsByTeamId",
+ "this.MaintenanceWindow.countDocuments",
+ "this.MaintenanceWindow.find"
+ ]
+ },
+ "inputDataExample": "{\"teamId\":\"652ff379963e792a5a3a0e4d\",\"query\":{\"page\":\"0\",\"rowsPerPage\":\"5\"}}",
+ "outputDataExample": "{\"maintenanceWindows\":[{\"_id\":\"653ba9a6331a31004887396a\",\"name\":\"Server Patching Window\"}],\"maintenanceWindowCount\":1}"
+ },
+ {
+ "simStepId": "eda86dca-dea6-4671-a079-7d212f4f18c4",
+ "diagramNodeId": "6c9979cd-1cc1-4227-b535-2ec3a10ca27e",
+ "simStepLabel": "Flow: View Maintenance Windows - Data Return to Frontend",
+ "simStepDescription": "The fetched data is sent back in the API response to the client.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/maintenanceWindowController.js",
+ "startLine": "77",
+ "endLine": "80",
+ "relevantVariables": [
+ "res.success"
+ ]
+ },
+ "inputDataExample": "{\"data\":{\"maintenanceWindows\":[{\"_id\":\"653ba9a6331a31004887396a\",\"name\":\"Server Patching Window\"}],\"maintenanceWindowCount\":1}}",
+ "outputDataExample": "{\"data\":{\"maintenanceWindows\":[{\"_id\":\"653ba9a6331a31004887396a\",\"name\":\"Server Patching Window\"}],\"maintenanceWindowCount\":1}}"
+ },
+ {
+ "simStepId": "acf97639-e291-4ca6-81a6-d0487397f42a",
+ "diagramNodeId": "970e2002-7c0e-40ef-80d8-e5e6d0a60735",
+ "simStepLabel": "Flow: View Maintenance Windows - UI Rendering",
+ "simStepDescription": "The client receives the data and updates its state. The `MaintenanceTable` component then renders the list of maintenance windows in a table, displaying their names, schedules, and providing actions like edit or delete.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx",
+ "startLine": "195",
+ "endLine": "201",
+ "relevantVariables": [
+ "Table",
+ "headers",
+ "maintenanceWindows"
+ ]
+ },
+ "inputDataExample": "{\"maintenanceWindows\":[{\"_id\":\"653ba9a6331a31004887396a\",\"name\":\"Server Patching Window\"}],\"maintenanceWindowCount\":1}",
+ "outputDataExample": "{}"
+ },
+ {
+ "simStepId": "a47affaf-b952-46d6-9709-6120a77690e1",
+ "diagramNodeId": "23630a1f-7452-4373-a864-7ec7821137f2",
+ "simStepLabel": "Flow: Monitor Suppression - Job Execution Start",
+ "simStepDescription": "The `SuperSimpleQueue` processing system picks up a job to check a specific monitor. The job handler is retrieved from `SuperSimpleQueueHelper`.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js",
+ "startLine": "30",
+ "endLine": "45",
+ "relevantVariables": [
+ "getMonitorJob",
+ "this.isInMaintenanceWindow"
+ ]
+ },
+ "inputDataExample": "{\"monitor\":{\"_id\":\"652ff379963e792a5a3a0e4e\",\"teamId\":\"652ff379963e792a5a3a0e4d\",\"name\":\"Main Production Server\"}}",
+ "outputDataExample": "{\"monitorId\":\"652ff379963e792a5a3a0e4e\",\"teamId\":\"652ff379963e792a5a3a0e4d\"}"
+ },
+ {
+ "simStepId": "87583096-f1ec-4160-ae52-5238f32ed0ab",
+ "diagramNodeId": "c99291bb-7316-440e-9a7f-323ee39e26b8",
+ "simStepLabel": "Flow: Monitor Suppression - Maintenance Check Call",
+ "simStepDescription": "Before performing the monitor check, the `getMonitorJob` function calls `isInMaintenanceWindow`, passing the monitor's ID and team ID to see if it should be suppressed.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js",
+ "startLine": "36",
+ "endLine": "36",
+ "relevantVariables": [
+ "this.isInMaintenanceWindow"
+ ]
+ },
+ "inputDataExample": "{\"monitorId\":\"652ff379963e792a5a3a0e4e\",\"teamId\":\"652ff379963e792a5a3a0e4d\"}",
+ "outputDataExample": "{\"monitorId\":\"652ff379963e792a5a3a0e4e\",\"teamId\":\"652ff379963e792a5a3a0e4d\"}"
+ },
+ {
+ "simStepId": "2127e749-80b4-499a-ae14-8dc2dcc9db9b",
+ "diagramNodeId": "fc78b5b6-7fad-4f8f-a5a3-c7c38931daba",
+ "simStepLabel": "Flow: Monitor Suppression - Fetching Maintenance Windows",
+ "simStepDescription": "The `isInMaintenanceWindow` function calls the database module to fetch all maintenance windows associated with the given monitor ID and team ID.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js",
+ "startLine": "80",
+ "endLine": "83",
+ "relevantVariables": [
+ "this.db.maintenanceWindowModule.getMaintenanceWindowsByMonitorId"
+ ]
+ },
+ "inputDataExample": "{\"monitorId\":\"652ff379963e792a5a3a0e4e\",\"teamId\":\"652ff379963e792a5a3a0e4d\"}",
+ "outputDataExample": "{\"monitorId\":\"652ff379963e792a5a3a0e4e\",\"teamId\":\"652ff379963e792a5a3a0e4d\"}"
+ },
+ {
+ "simStepId": "e6542e14-556d-482a-8c35-f7b03997be49",
+ "diagramNodeId": "8a13e585-2310-420b-9e92-211b6d65f201",
+ "simStepLabel": "Flow: Monitor Suppression - Database Result Return",
+ "simStepDescription": "The database module returns a list of maintenance window documents that match the monitor ID.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/db/v1/modules/maintenanceWindowModule.js",
+ "startLine": "69",
+ "endLine": "76",
+ "relevantVariables": [
+ "getMaintenanceWindowsByMonitorId",
+ "this.MaintenanceWindow.find"
+ ]
+ },
+ "inputDataExample": "[{\"_id\":\"653ba9a6331a31004887396a\",\"monitorId\":\"652ff379963e792a5a3a0e4e\",\"active\":true,\"start\":\"2023-10-27T02:00:00.000Z\",\"end\":\"2023-10-27T03:00:00.000Z\"}]",
+ "outputDataExample": "[{\"_id\":\"653ba9a6331a31004887396a\",\"monitorId\":\"652ff379963e792a5a3a0e4e\",\"active\":true,\"start\":\"2023-10-27T02:00:00.000Z\",\"end\":\"2023-10-27T03:00:00.000Z\"}]"
+ },
+ {
+ "simStepId": "6ba6b9d1-5fc3-454e-8744-2022b5192ed9",
+ "diagramNodeId": "0fe32416-db43-49b9-9438-3796ba1a0add",
+ "simStepLabel": "Flow: Monitor Suppression - Time Evaluation",
+ "simStepDescription": "The `isInMaintenanceWindow` function iterates through the returned windows. For each active window, it checks if the current time (`new Date()`) is between the `start` and `end` times.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js",
+ "startLine": "85",
+ "endLine": "113",
+ "relevantVariables": [
+ "maintenanceWindows.reduce",
+ "start",
+ "end",
+ "now"
+ ]
+ },
+ "inputDataExample": "[{\"_id\":\"653ba9a6331a31004887396a\",\"monitorId\":\"652ff379963e792a5a3a0e4e\",\"active\":true,\"start\":\"2023-10-27T02:00:00.000Z\",\"end\":\"2023-10-27T03:00:00.000Z\"}]",
+ "outputDataExample": "true"
+ },
+ {
+ "simStepId": "a715d98f-60bf-4f32-aae6-21ee4e09f4da",
+ "diagramNodeId": "40f4d9df-4c0f-4384-901c-8d7117e3b538",
+ "simStepLabel": "Flow: Monitor Suppression - Return Suppression Flag",
+ "simStepDescription": "The boolean result (true if in maintenance, false otherwise) is returned to the `getMonitorJob` function.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js",
+ "startLine": "114",
+ "endLine": "114",
+ "relevantVariables": [
+ "maintenanceWindowIsActive"
+ ]
+ },
+ "inputDataExample": "true",
+ "outputDataExample": "true"
+ },
+ {
+ "simStepId": "e59fe64e-f06b-4855-b631-923bb5a68540",
+ "diagramNodeId": "01e042c3-40b4-416a-8d9e-6d6fd323d60b",
+ "simStepLabel": "Flow: Monitor Suppression - Conditional Job Termination",
+ "simStepDescription": "If the `isInMaintenanceWindow` function returns true, the `getMonitorJob` logs that the monitor is in a maintenance window and terminates its execution for this cycle, effectively suppressing the check.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js",
+ "startLine": "37",
+ "endLine": "44",
+ "relevantVariables": [
+ "maintenanceWindowActive",
+ "this.logger.info",
+ "return"
+ ]
+ },
+ "inputDataExample": "{\"maintenanceWindowActive\": true}",
+ "outputDataExample": "{\"message\":\"Monitor 652ff379963e792a5a3a0e4e is in maintenance window\"}"
+ }
+ ],
+ "description": "Allows users to schedule maintenance periods to prevent false alarms and avoid skewing uptime statistics during planned downtime - Users can define a start and end time for a maintenance window - Specific monitors or all monitors can be associated with a maintenance window - During a scheduled maintenance, notifications for associated monitors are suppressed - Provides a central table to view all active, upcoming, and past maintenance windows ",
+ "simulationNodesAndEdges": {
+ "20335f29-a2ac-4e0c-aba6-28707748ecd4": {
+ "simStepIds": [
+ "2401ef7a-8d8e-4056-868a-547ab213daf1"
+ ]
+ },
+ "5bdfdfa4-5d93-49fa-b05c-905813e6cf9d": {
+ "simStepIds": [
+ "5e0d7085-099b-478a-afaa-47ad4dd07043"
+ ]
+ },
+ "a391e8ea-9dbe-4fe4-90df-bf475d8b7ef8": {
+ "simStepIds": [
+ "3e93ef65-8e2c-405b-8dc1-56d8e93937b3"
+ ]
+ },
+ "0fae6e75-38ea-444d-a1bf-0f820ba443b9": {
+ "simStepIds": [
+ "95fc1919-339d-4659-98cb-3dc50f662075"
+ ]
+ },
+ "560d268b-ef0f-4cd2-bcfe-9dc0a0e2ce6f": {
+ "simStepIds": [
+ "6e2cf739-1514-4c37-9874-10b68a47010e"
+ ]
+ },
+ "3890b98d-a133-4279-a961-5d2facc6a17a": {
+ "simStepIds": [
+ "a3c47fd4-c701-46c2-9aff-94a667b3e368"
+ ]
+ },
+ "a5a84b08-8c70-4023-8d08-b74e23aab4d7": {
+ "simStepIds": [
+ "ebe8fd52-f2a1-4a98-a71b-b04d6f4cdadc"
+ ]
+ },
+ "d145bb4c-e1e0-473a-b6bc-decc932b738b": {
+ "simStepIds": [
+ "82b5ea14-bb00-4511-aba4-76196029fd81"
+ ]
+ },
+ "c35a065c-2808-402c-877e-9055d956ce0d": {
+ "simStepIds": [
+ "1251da83-5949-4e24-a1d2-fc74ff785aa9"
+ ]
+ },
+ "27d9160a-4f8d-4dff-a09b-1678bc28cf2c": {
+ "simStepIds": [
+ "9bfbae5a-f487-40ef-86f5-04a1cc3c7662"
+ ]
+ },
+ "970e2002-7c0e-40ef-80d8-e5e6d0a60735": {
+ "simStepIds": [
+ "acf97639-e291-4ca6-81a6-d0487397f42a"
+ ]
+ },
+ "23630a1f-7452-4373-a864-7ec7821137f2": {
+ "simStepIds": [
+ "a47affaf-b952-46d6-9709-6120a77690e1"
+ ]
+ },
+ "fc78b5b6-7fad-4f8f-a5a3-c7c38931daba": {
+ "simStepIds": [
+ "2127e749-80b4-499a-ae14-8dc2dcc9db9b"
+ ]
+ },
+ "0fe32416-db43-49b9-9438-3796ba1a0add": {
+ "simStepIds": [
+ "6ba6b9d1-5fc3-454e-8744-2022b5192ed9"
+ ]
+ },
+ "01e042c3-40b4-416a-8d9e-6d6fd323d60b": {
+ "simStepIds": [
+ "e59fe64e-f06b-4855-b631-923bb5a68540"
+ ]
+ },
+ "d3bdc6ff-875e-472e-88c8-eb10b20c526e": {
+ "simStepIds": [
+ "f881435b-3bc2-453d-b6bf-56ad3a12f120"
+ ]
+ },
+ "fd5e4447-123b-4b2d-8bce-1d1ae7cb00af": {
+ "simStepIds": [
+ "0660b48b-139f-4ecf-b7d9-f12f544e0ab5"
+ ]
+ },
+ "e2588541-3ffc-47c6-8f8a-41d30ea7fc72": {
+ "simStepIds": [
+ "e4cc5ce8-6da9-4281-9204-29c061ab13ce"
+ ]
+ },
+ "e1d59cf6-0d96-4e0a-ad17-2a7984a9157f": {
+ "simStepIds": [
+ "c3d1a95c-e93d-499a-8830-a6d870902414"
+ ]
+ },
+ "12d0ab8a-392f-48fa-9efa-06ffe1f3a252": {
+ "simStepIds": [
+ "80aed28e-68b6-47bf-8bc9-ededb4824066"
+ ]
+ },
+ "fcf4eef4-394c-4872-9124-f7a0b32cdc3b": {
+ "simStepIds": [
+ "69fdc2a9-837f-466c-9562-a12319019580"
+ ]
+ },
+ "7f921125-500f-4902-abc3-936ecac033d8": {
+ "simStepIds": [
+ "7e20cd6c-d24e-4540-86d6-d6a9fa087314"
+ ]
+ },
+ "c3127c74-43f1-42d7-8da6-b0a494c3658f": {
+ "simStepIds": [
+ "feb8bd4f-339f-4132-a709-89c63c4efa9c"
+ ]
+ },
+ "6c9979cd-1cc1-4227-b535-2ec3a10ca27e": {
+ "simStepIds": [
+ "eda86dca-dea6-4671-a079-7d212f4f18c4"
+ ]
+ },
+ "c99291bb-7316-440e-9a7f-323ee39e26b8": {
+ "simStepIds": [
+ "87583096-f1ec-4160-ae52-5238f32ed0ab"
+ ]
+ },
+ "8a13e585-2310-420b-9e92-211b6d65f201": {
+ "simStepIds": [
+ "e6542e14-556d-482a-8c35-f7b03997be49"
+ ]
+ },
+ "40f4d9df-4c0f-4384-901c-8d7117e3b538": {
+ "simStepIds": [
+ "a715d98f-60bf-4f32-aae6-21ee4e09f4da"
+ ]
+ }
+ },
+ "isAIGenerated": true,
+ "keywords": "MaintenanceWindow, createMaintenanceWindow, maintenanceWindowController",
+ "generationPrompt": "Scheduled Maintenance Windows",
+ "generationKeywords": "MaintenanceWindow, createMaintenanceWindow, maintenanceWindowController"
+ },
+ "User and Team Management with Role-Based Access": {
+ "name": "User and Team Management with Role-Based Access",
+ "simSteps": [
+ {
+ "simStepId": "2d5a952a-ce62-4948-9309-96b28ad74e59",
+ "diagramNodeId": "3de2d413-5e9b-49f4-9409-74be32c52e0f",
+ "simStepLabel": "Client: Conditional UI Rendering Based on Role",
+ "simStepDescription": "A user with 'superadmin' or 'admin' role navigates to the Account page. The application checks the user's role stored in the Redux state to conditionally render the 'Team' management tab. Non-admin users will not see this option.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Account/index.jsx",
+ "startLine": "37",
+ "endLine": "41",
+ "relevantVariables": [
+ "user.role",
+ "requiredRoles",
+ "hideTeams"
+ ]
+ },
+ "inputDataExample": "{\n \"user\": {\n \"_id\": \"60f8f7b3a7b3c2a3e4f5a6b7\",\n \"firstName\": \"Jane\",\n \"lastName\": \"Doe\",\n \"email\": \"jane.doe@example.com\",\n \"role\": [\"superadmin\"],\n \"teamId\": \"60f8f7b3a7b3c2a3e4f5a6b8\"\n }\n}",
+ "outputDataExample": "{\n \"hideTeams\": false,\n \"tabList\": [\n {\n \"name\": \"Profile\",\n \"value\": \"profile\"\n },\n {\n \"name\": \"Password\",\n \"value\": \"password\"\n },\n {\n \"name\": \"Team\",\n \"value\": \"team\"\n }\n ]\n}"
+ },
+ {
+ "simStepId": "10f80ada-2d0a-4e98-bd5d-057e7d4fe6de",
+ "diagramNodeId": "c441312f-f3c6-48c6-b1b9-7c12749d91bb",
+ "simStepLabel": "Client: User Navigates to Team Panel",
+ "simStepDescription": "The user clicks on the 'Team' tab, which navigates them to the team management view. The user's role data is implicitly passed through the application state to render the appropriate components.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Account/index.jsx",
+ "startLine": "98",
+ "endLine": "98",
+ "relevantVariables": [
+ "TeamPanel"
+ ]
+ },
+ "inputDataExample": "{\n \"navigationPath\": \"/account/team\",\n \"userState\": {\n \"role\": [\"superadmin\"]\n }\n}",
+ "outputDataExample": "{\n \"navigationPath\": \"/account/team\",\n \"userState\": {\n \"role\": [\"superadmin\"]\n }\n}"
+ },
+ {
+ "simStepId": "eebaca25-d588-49c4-a35c-9abc82fde580",
+ "diagramNodeId": "b27f1bba-c655-40c6-a1dc-71e78c590c92",
+ "simStepLabel": "Client: Initiate User Invitation",
+ "simStepDescription": "In the `TeamPanel`, the admin clicks the 'Add Team Member' button, then selects 'Invite a team member'. A dialog opens, prompting for the new user's email and role. After filling the form and clicking 'E-mail token', the `handleInviteMember` function is triggered to start the invitation process.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Account/components/TeamPanel.jsx",
+ "startLine": "348",
+ "endLine": "354",
+ "relevantVariables": [
+ "handleInviteMember",
+ "Button"
+ ]
+ },
+ "inputDataExample": "{\n \"email\": \"new.developer@example.com\",\n \"role\": [\"user\"]\n}",
+ "outputDataExample": "{\n \"apiCall\": \"networkService.sendInviteEmail\",\n \"payload\": {\n \"email\": \"new.developer@example.com\",\n \"role\": [\"user\"]\n }\n}"
+ },
+ {
+ "simStepId": "16d87608-4458-430c-b459-cd9b1d413fa6",
+ "diagramNodeId": "3530244f-2689-444e-bcbf-611db2e3c000",
+ "simStepLabel": "API Call: Send Invitation Request",
+ "simStepDescription": "The client sends a POST request to the `/api/v1/invite/send` endpoint. The request includes the new user's email and selected role in the body, and the admin's authentication token in the headers.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/routes/v1/inviteRoute.js",
+ "startLine": "14",
+ "endLine": "14",
+ "relevantVariables": [
+ "this.router.post"
+ ]
+ },
+ "inputDataExample": "{\n \"endpoint\": \"/api/v1/invite/send\",\n \"method\": \"POST\",\n \"headers\": {\n \"Authorization\": \"Bearer \"\n },\n \"body\": {\n \"email\": \"new.developer@example.com\",\n \"role\": [\"user\"]\n }\n}",
+ "outputDataExample": "{\n \"endpoint\": \"/api/v1/invite/send\",\n \"method\": \"POST\",\n \"headers\": {\n \"Authorization\": \"Bearer \"\n },\n \"body\": {\n \"email\": \"new.developer@example.com\",\n \"role\": [\"user\"]\n }\n}"
+ },
+ {
+ "simStepId": "50dd7316-7d01-43f7-9036-d97d51a9c178",
+ "diagramNodeId": "8b3cb6c2-ed57-4510-be15-decb8e3c89e1",
+ "simStepLabel": "Server: Role-Based Access Control Middleware",
+ "simStepDescription": "The server receives the request. Before reaching the main controller, the `isAllowed` middleware intercepts the request. It verifies the JWT from the request header and checks if the user's role (e.g., 'superadmin') is present in the list of allowed roles for this route (`['admin', 'superadmin']`).",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/middleware/v1/isAllowed.js",
+ "startLine": "39",
+ "endLine": "44",
+ "relevantVariables": [
+ "decoded.role",
+ "allowedRoles",
+ "hasRole"
+ ]
+ },
+ "inputDataExample": "{\n \"req\": {\n \"user\": {\n \"_id\": \"60f8f7b3a7b3c2a3e4f5a6b7\",\n \"role\": [\"superadmin\"],\n \"teamId\": \"60f8f7b3a7b3c2a3e4f5a6b8\"\n },\n \"headers\": {\n \"authorization\": \"Bearer \"\n }\n },\n \"allowedRoles\": [\"admin\", \"superadmin\"]\n}",
+ "outputDataExample": "{\n \"result\": \"next() called\",\n \"req\": {\n \"user\": {\n \"_id\": \"60f8f7b3a7b3c2a3e4f5a6b7\",\n \"role\": [\"superadmin\"],\n \"teamId\": \"60f8f7b3a7b3c2a3e4f5a6b8\"\n },\n \"headers\": {\n \"authorization\": \"Bearer \"\n }\n }\n}"
+ },
+ {
+ "simStepId": "6c024d4c-178b-45fc-8bca-4fc6c00549a4",
+ "diagramNodeId": "2ee1c4e8-90fb-4470-ad9f-6ffed5420853",
+ "simStepLabel": "Server: Forward Request to Controller",
+ "simStepDescription": "Since the user's role is authorized, the middleware calls `next()` to pass the request object, now populated with user data, to the next handler in the chain: `inviteController.sendInviteEmail`.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/inviteController.js",
+ "startLine": "61",
+ "endLine": "68",
+ "relevantVariables": [
+ "sendInviteEmail",
+ "this.inviteService.sendInviteEmail"
+ ]
+ },
+ "inputDataExample": "{\n \"req\": {\n \"user\": {\n \"_id\": \"60f8f7b3a7b3c2a3e4f5a6b7\",\n \"firstName\": \"Jane\",\n \"role\": [\"superadmin\"],\n \"teamId\": \"60f8f7b3a7b3c2a3e4f5a6b8\"\n },\n \"body\": {\n \"email\": \"new.developer@example.com\",\n \"role\": [\"user\"]\n }\n }\n}",
+ "outputDataExample": "{\n \"req\": {\n \"user\": {\n \"_id\": \"60f8f7b3a7b3c2a3e4f5a6b7\",\n \"firstName\": \"Jane\",\n \"role\": [\"superadmin\"],\n \"teamId\": \"60f8f7b3a7b3c2a3e4f5a6b8\"\n },\n \"body\": {\n \"email\": \"new.developer@example.com\",\n \"role\": [\"user\"]\n }\n }\n}"
+ },
+ {
+ "simStepId": "55fe9963-30d2-47f8-859f-b485a77064c4",
+ "diagramNodeId": "ae858158-8db6-4917-8fda-0966a953f6ed",
+ "simStepLabel": "Server: Process Invitation in Service Layer",
+ "simStepDescription": "The `inviteController` calls the `inviteService`. The service layer handles the business logic: it first requests an invitation token from the database module and then instructs the email service to send the invitation.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/business/inviteService.js",
+ "startLine": "24",
+ "endLine": "28",
+ "relevantVariables": [
+ "sendInviteEmail",
+ "this.db.inviteModule.requestInviteToken",
+ "this.emailService.sendInviteEmail"
+ ]
+ },
+ "inputDataExample": "{\n \"inviteRequest\": {\n \"email\": \"new.developer@example.com\",\n \"role\": [\"user\"],\n \"teamId\": \"60f8f7b3a7b3c2a3e4f5a6b8\"\n },\n \"firstName\": \"Jane\"\n}",
+ "outputDataExample": "{\n \"inviteToken\": {\n \"token\": \"a1b2c3d4e5f6...\",\n \"email\": \"new.developer@example.com\"\n }\n}"
+ },
+ {
+ "simStepId": "c796f8ad-d548-45f3-8d62-67f59c6c1aa4",
+ "diagramNodeId": "bdd52e6f-ddf7-4966-b3af-957f9553082f",
+ "simStepLabel": "Database Query: Create and Store Invite Token",
+ "simStepDescription": "The `inviteService` calls the `inviteModule` to generate and persist a unique invitation token. This involves creating a new document in the `InviteToken` collection with the invitee's details and a securely generated random token.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/db/v1/modules/inviteModule.js",
+ "startLine": "23",
+ "endLine": "26",
+ "relevantVariables": [
+ "requestInviteToken",
+ "InviteToken",
+ "invite.save"
+ ]
+ },
+ "inputDataExample": "{\n \"email\": \"new.developer@example.com\",\n \"role\": [\"user\"],\n \"teamId\": \"60f8f7b3a7b3c2a3e4f5a6b8\"\n}",
+ "outputDataExample": "{\n \"_id\": \"61f8f7b3a7b3c2a3e4f5a6b9\",\n \"email\": \"new.developer@example.com\",\n \"role\": [\"user\"],\n \"teamId\": \"60f8f7b3a7b3c2a3e4f5a6b8\",\n \"token\": \"a1b2c3d4e5f6...\",\n \"createdAt\": \"2023-10-27T10:00:00.000Z\"\n}"
+ },
+ {
+ "simStepId": "7d1f86f2-c8d5-48fb-a8c1-b337dc56271f",
+ "diagramNodeId": "a7be2d0b-0227-4228-a0a6-074f7324fd89",
+ "simStepLabel": "Server: Dispatch Invitation Email",
+ "simStepDescription": "After successfully storing the token, the `inviteService` uses the `emailService` to compose and send an email to the new user. The email contains a unique registration link including the generated token.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/business/inviteService.js",
+ "startLine": "26",
+ "endLine": "28",
+ "relevantVariables": [
+ "this.emailService.sendInviteEmail",
+ "inviteToken"
+ ]
+ },
+ "inputDataExample": "{\n \"to\": \"new.developer@example.com\",\n \"token\": \"a1b2c3d4e5f6...\",\n \"inviterName\": \"Jane\"\n}",
+ "outputDataExample": "{\n \"status\": \"success\",\n \"messageId\": \"\"\n}"
+ },
+ {
+ "simStepId": "6ba5094d-d1e6-4ec3-8917-28cdb30f2ef9",
+ "diagramNodeId": "52732a37-82b8-40a7-b12d-dc1b82380b2d",
+ "simStepLabel": "API Response: Send Confirmation to Client",
+ "simStepDescription": "The server's controller, having successfully processed the invitation, sends a JSON response back to the client indicating that the invite was sent successfully.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/inviteController.js",
+ "startLine": "69",
+ "endLine": "72",
+ "relevantVariables": [
+ "res.success"
+ ]
+ },
+ "inputDataExample": "{\n \"success\": true,\n \"msg\": \"Invite sent successfully\",\n \"data\": {\n \"token\": \"a1b2c3d4e5f6...\",\n \"email\": \"new.developer@example.com\"\n }\n}",
+ "outputDataExample": "{\n \"success\": true,\n \"msg\": \"Invite sent successfully\",\n \"data\": {\n \"token\": \"a1b2c3d4e5f6...\",\n \"email\": \"new.developer@example.com\"\n }\n}"
+ },
+ {
+ "simStepId": "700f35e0-b39d-417c-b0a8-0612feade977",
+ "diagramNodeId": "02d87257-883c-4822-a421-823c983532ec",
+ "simStepLabel": "Client: Display Success Feedback",
+ "simStepDescription": "The client application receives the successful API response. It then closes the invitation dialog and displays a toast notification to the admin, confirming that the invitation has been sent.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Account/components/TeamPanel.jsx",
+ "startLine": "145",
+ "endLine": "149",
+ "relevantVariables": [
+ "createToast",
+ "closeInviteModal"
+ ]
+ },
+ "inputDataExample": "{\n \"response\": {\n \"status\": 200,\n \"data\": {\n \"success\": true,\n \"msg\": \"Invite sent successfully\"\n }\n }\n}",
+ "outputDataExample": "{\n \"uiState\": {\n \"toast\": {\n \"type\": \"success\",\n \"message\": \"Invite sent successfully\"\n }\n }\n}"
+ }
+ ],
+ "description": "Enables collaborative use of the application within an organization with proper access controls - The system supports multiple users organized into teams - Implements role-based access control (RBAC) with roles like 'superadmin' and 'admin', restricting access to sensitive areas like settings and user management - Superadmins can invite new users to the team via email, assigning them a specific role - Users can manage their own profiles and change their passwords ",
+ "simulationNodesAndEdges": {
+ "3de2d413-5e9b-49f4-9409-74be32c52e0f": {
+ "simStepIds": [
+ "2d5a952a-ce62-4948-9309-96b28ad74e59"
+ ]
+ },
+ "b27f1bba-c655-40c6-a1dc-71e78c590c92": {
+ "simStepIds": [
+ "eebaca25-d588-49c4-a35c-9abc82fde580"
+ ]
+ },
+ "8b3cb6c2-ed57-4510-be15-decb8e3c89e1": {
+ "simStepIds": [
+ "50dd7316-7d01-43f7-9036-d97d51a9c178"
+ ]
+ },
+ "ae858158-8db6-4917-8fda-0966a953f6ed": {
+ "simStepIds": [
+ "55fe9963-30d2-47f8-859f-b485a77064c4"
+ ]
+ },
+ "a7be2d0b-0227-4228-a0a6-074f7324fd89": {
+ "simStepIds": [
+ "7d1f86f2-c8d5-48fb-a8c1-b337dc56271f"
+ ]
+ },
+ "02d87257-883c-4822-a421-823c983532ec": {
+ "simStepIds": [
+ "700f35e0-b39d-417c-b0a8-0612feade977"
+ ]
+ },
+ "c441312f-f3c6-48c6-b1b9-7c12749d91bb": {
+ "simStepIds": [
+ "10f80ada-2d0a-4e98-bd5d-057e7d4fe6de"
+ ]
+ },
+ "3530244f-2689-444e-bcbf-611db2e3c000": {
+ "simStepIds": [
+ "16d87608-4458-430c-b459-cd9b1d413fa6"
+ ]
+ },
+ "2ee1c4e8-90fb-4470-ad9f-6ffed5420853": {
+ "simStepIds": [
+ "6c024d4c-178b-45fc-8bca-4fc6c00549a4"
+ ]
+ },
+ "bdd52e6f-ddf7-4966-b3af-957f9553082f": {
+ "simStepIds": [
+ "c796f8ad-d548-45f3-8d62-67f59c6c1aa4"
+ ]
+ },
+ "52732a37-82b8-40a7-b12d-dc1b82380b2d": {
+ "simStepIds": [
+ "6ba5094d-d1e6-4ec3-8917-28cdb30f2ef9"
+ ]
+ }
+ },
+ "isAIGenerated": true,
+ "keywords": "isAllowed, RoleProtectedRoute, Team",
+ "generationPrompt": "User and Team Management with Role-Based Access",
+ "generationKeywords": "isAllowed, RoleProtectedRoute, Team"
+ },
+ "Bulk Monitor Management": {
+ "name": "Bulk Monitor Management",
+ "simSteps": [
+ {
+ "simStepId": "a29da01e-8c5a-4e6d-8d3c-e49af528e4fa",
+ "diagramNodeId": "c4caeba8-ed9a-4cbc-9888-38cc8ca55362",
+ "simStepLabel": "Flow 1: CSV Import - Navigate to Bulk Import Page",
+ "simStepDescription": "The user navigates to the \"/uptime/bulk-import\" URL, which is routed to the BulkImport component for rendering the UI.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Routes/index.jsx",
+ "startLine": "88",
+ "endLine": "91",
+ "relevantVariables": [
+ "Route",
+ "BulkImport"
+ ]
+ },
+ "inputDataExample": "{\"url\": \"/uptime/bulk-import\"}",
+ "outputDataExample": "{\"component\": \"BulkImport\"}"
+ },
+ {
+ "simStepId": "ef0bbcff-9535-4c0e-a5ca-96c457f89139",
+ "diagramNodeId": "c9a012bc-d53f-494f-82f2-1ee3f40ce646",
+ "simStepLabel": "Flow 1: CSV Import - Render UI",
+ "simStepDescription": "The BulkImport component is rendered, displaying the file upload interface to the user.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Uptime/BulkImport/index.jsx",
+ "startLine": "16",
+ "endLine": "50",
+ "relevantVariables": [
+ "BulkImport"
+ ]
+ },
+ "inputDataExample": "{\"props\": {}}",
+ "outputDataExample": "{\"props\": {}}"
+ },
+ {
+ "simStepId": "bbd04d55-fc6a-4ba8-bcde-5a4dc263d6eb",
+ "diagramNodeId": "6f1948fa-e84a-4f8e-8f3b-8f24c511b3d8",
+ "simStepLabel": "Flow 1: CSV Import - User Selects File",
+ "simStepDescription": "The user clicks the 'Select File' button, triggering the file input. When a file is chosen, the `handleFileChange` function validates that it's a CSV file and updates the component's state.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Uptime/BulkImport/Upload.jsx",
+ "startLine": "23",
+ "endLine": "31",
+ "relevantVariables": [
+ "handleFileChange",
+ "selectedFile",
+ "setError",
+ "setFile"
+ ]
+ },
+ "inputDataExample": "{\"event\": {\"target\": {\"files\": [{\"name\": \"monitors_to_import.csv\", \"type\": \"text/csv\", \"size\": 512}]}}}",
+ "outputDataExample": "{\"stateUpdate\": {\"selectedFile\": {\"name\": \"monitors_to_import.csv\", \"type\": \"text/csv\", \"size\": 512}}}"
+ },
+ {
+ "simStepId": "610120d2-0521-4444-b245-feb97239a389",
+ "diagramNodeId": "e03a8ce2-c2cd-4147-8bab-63d6c2cf0a48",
+ "simStepLabel": "Flow 1: CSV Import - File State Update",
+ "simStepDescription": "The selected file object is passed from the UploadFile component to the parent BulkImport component via the `onFileSelect` prop, updating its `selectedFile` state.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Uptime/BulkImport/index.jsx",
+ "startLine": "22",
+ "endLine": "22",
+ "relevantVariables": [
+ "setSelectedFile"
+ ]
+ },
+ "inputDataExample": "{\"selectedFile\": {\"name\": \"monitors_to_import.csv\", \"type\": \"text/csv\", \"size\": 512}}",
+ "outputDataExample": "{\"selectedFile\": {\"name\": \"monitors_to_import.csv\", \"type\": \"text/csv\", \"size\": 512}}"
+ },
+ {
+ "simStepId": "0139fcaa-eb46-4176-b7bd-1cb15e7f1f4c",
+ "diagramNodeId": "dd2f9356-fad3-47d4-81d5-a5e038298e2b",
+ "simStepLabel": "Flow 1: CSV Import - Submit Upload",
+ "simStepDescription": "The user clicks the submit button, which executes the `handleSubmit` function. This function calls the `createBulkMonitors` function provided by the `useCreateBulkMonitors` custom hook.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Uptime/BulkImport/index.jsx",
+ "startLine": "32",
+ "endLine": "45",
+ "relevantVariables": [
+ "handleSubmit",
+ "createBulkMonitors",
+ "selectedFile",
+ "user"
+ ]
+ },
+ "inputDataExample": "{\"selectedFile\": {\"name\": \"monitors_to_import.csv\", \"type\": \"text/csv\", \"size\": 512}}",
+ "outputDataExample": "null"
+ },
+ {
+ "simStepId": "a365d018-e80c-419c-80b3-5ffc8f14dc5c",
+ "diagramNodeId": "b8c0292d-afbb-4648-b484-1c27be96d52f",
+ "simStepLabel": "Flow 1: CSV Import - Prepare Form Data",
+ "simStepDescription": "The `useCreateBulkMonitors` hook creates a `FormData` object and appends the selected CSV file to it, preparing it for the network request.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Hooks/v1/monitorHooks.js",
+ "startLine": "490",
+ "endLine": "493",
+ "relevantVariables": [
+ "createBulkMonitors",
+ "formData"
+ ]
+ },
+ "inputDataExample": "{\"file\": {\"name\": \"monitors_to_import.csv\", \"type\": \"text/csv\", \"size\": 512}}",
+ "outputDataExample": "{\"formData\": {\"csvFile\": \"\"}}"
+ },
+ {
+ "simStepId": "2fc087b3-033d-4cf5-81f2-e35b99a14bce",
+ "diagramNodeId": "5967e576-f542-4c0b-a3f4-9446ae992721",
+ "simStepLabel": "Flow 1: CSV Import - Send API Request",
+ "simStepDescription": "The NetworkService sends the `FormData` containing the CSV file to the backend via a POST request to the `/monitors/bulk` endpoint. The `Content-Type` is set to `multipart/form-data`.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Utils/NetworkService.js",
+ "startLine": "978",
+ "endLine": "983",
+ "relevantVariables": [
+ "createBulkMonitors",
+ "this.axiosInstance.post"
+ ]
+ },
+ "inputDataExample": "{\"url\": \"/monitors/bulk\", \"formData\": {\"csvFile\": \"\"}}",
+ "outputDataExample": "{\"promise\": \"\"}"
+ },
+ {
+ "simStepId": "e6dec1dc-33cb-4669-a3b0-7a3c0b233185",
+ "diagramNodeId": "23715cce-c83a-44c0-9853-4c95b85e5ecd",
+ "simStepLabel": "Flow 1: CSV Import - Transmit Data to Server",
+ "simStepDescription": "The HTTP POST request containing the multipart form data is transmitted from the client to the server's API endpoint.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/routes/v1/monitorRoute.js",
+ "startLine": "46",
+ "endLine": "46",
+ "relevantVariables": [
+ "this.router.post",
+ "upload.single",
+ "this.monitorController.createBulkMonitors"
+ ]
+ },
+ "inputDataExample": "{\"request\": {\"method\": \"POST\", \"endpoint\": \"/api/v1/monitors/bulk\", \"headers\": {\"Content-Type\": \"multipart/form-data\"}, \"body\": \"---boundary\\nContent-Disposition: form-data; name=\\\"csvFile\\\"; filename=\\\"monitors.csv\\\"\\n...\"}}",
+ "outputDataExample": "{\"request\": {\"method\": \"POST\", \"endpoint\": \"/api/v1/monitors/bulk\", \"headers\": {\"Content-Type\": \"multipart/form-data\"}, \"body\": \"---boundary\\nContent-Disposition: form-data; name=\\\"csvFile\\\"; filename=\\\"monitors.csv\\\"\\n...\"}}"
+ },
+ {
+ "simStepId": "07bf3d83-047d-4e51-9f96-8c9c64848500",
+ "diagramNodeId": "c1ac3c26-996a-43ef-9c34-4c2776188784",
+ "simStepLabel": "Flow 1: CSV Import - Server Receives Request",
+ "simStepDescription": "The server's monitor route receives the request. The `multer` middleware processes the file upload, making the file available in `req.file`. The request is then passed to the `createBulkMonitors` controller method.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/routes/v1/monitorRoute.js",
+ "startLine": "46",
+ "endLine": "46",
+ "relevantVariables": [
+ "this.router.post",
+ "upload.single",
+ "this.monitorController.createBulkMonitors"
+ ]
+ },
+ "inputDataExample": "{\"request\": {\"endpoint\": \"/monitors/bulk\"}}",
+ "outputDataExample": "{\"req.file\": {\"fieldname\": \"csvFile\", \"originalname\": \"monitors.csv\", \"buffer\": \"\"}}"
+ },
+ {
+ "simStepId": "c53d3f62-73fe-4fad-9835-ef2c8cf52894",
+ "diagramNodeId": "20d0e690-a569-4c6f-8238-916cac920553",
+ "simStepLabel": "Flow 1: CSV Import - Route to Controller",
+ "simStepDescription": "The request object, now populated with the uploaded file buffer by the middleware, is passed from the routing layer to the monitor controller for business logic processing.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/monitorController.js",
+ "startLine": "206",
+ "endLine": "212",
+ "relevantVariables": [
+ "createBulkMonitors",
+ "req.file"
+ ]
+ },
+ "inputDataExample": "{\"req\": {\"file\": {\"buffer\": \"\"}, \"user\": {\"_id\": \"user_123\", \"teamId\": \"team_abc\"}}}",
+ "outputDataExample": "{\"req\": {\"file\": {\"buffer\": \"\"}, \"user\": {\"_id\": \"user_123\", \"teamId\": \"team_abc\"}}}"
+ },
+ {
+ "simStepId": "a3799570-64bf-481e-9ffe-31dbad8a36e4",
+ "diagramNodeId": "0e520a6d-34bf-467b-a894-e73979364478",
+ "simStepLabel": "Flow 1: CSV Import - Controller Processes Data",
+ "simStepDescription": "The controller validates the presence of the file and extracts the file data (buffer), `userId`, and `teamId`. It then calls the `monitorService.createBulkMonitors` method to handle the core logic.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/monitorController.js",
+ "startLine": "228",
+ "endLine": "232",
+ "relevantVariables": [
+ "fileData",
+ "monitors",
+ "this.monitorService.createBulkMonitors"
+ ]
+ },
+ "inputDataExample": "{\"file\": {\"buffer\": \"\"}, \"user\": {\"_id\": \"user_123\", \"teamId\": \"team_abc\"}}",
+ "outputDataExample": "null"
+ },
+ {
+ "simStepId": "6038422c-ab83-4fc6-a58b-c75734276118",
+ "diagramNodeId": "7292bf6e-431d-4a9d-94ce-2bcfabcaa79d",
+ "simStepLabel": "Flow 1: CSV Import - Controller to Service",
+ "simStepDescription": "The file buffer and user/team identifiers are passed from the controller to the monitor service layer.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/business/monitorService.js",
+ "startLine": "84",
+ "endLine": "84",
+ "relevantVariables": [
+ "createBulkMonitors"
+ ]
+ },
+ "inputDataExample": "{\"fileData\": \"\", \"userId\": \"user_123\", \"teamId\": \"team_abc\"}",
+ "outputDataExample": "{\"fileData\": \"\", \"userId\": \"user_123\", \"teamId\": \"team_abc\"}"
+ },
+ {
+ "simStepId": "2c7c3082-e4b6-4525-860f-2ffcd9e86db1",
+ "diagramNodeId": "6139e487-1dd5-405a-ba7d-ededec4b282e",
+ "simStepLabel": "Flow 1: CSV Import - Service Parses CSV",
+ "simStepDescription": "The monitor service uses the `papaparse` library to parse the CSV file buffer into a JSON object array. In the `complete` callback, it enriches each record with `userId` and `teamId`.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/business/monitorService.js",
+ "startLine": "85",
+ "endLine": "134",
+ "relevantVariables": [
+ "papaparse.parse",
+ "enrichedData",
+ "this.db.monitorModule.createBulkMonitors"
+ ]
+ },
+ "inputDataExample": "{\"fileData\": \"name,type,url\\nGoogle,http,https://google.com\\n\"}",
+ "outputDataExample": "{\"enrichedData\": [{\"name\": \"Google\", \"type\": \"http\", \"url\": \"https://google.com\", \"userId\": \"user_123\", \"teamId\": \"team_abc\"}]}"
+ },
+ {
+ "simStepId": "0115fb69-4959-4b32-bd90-1dcd3c2231f6",
+ "diagramNodeId": "b55a0dc6-f596-4e5f-9265-63588b46b519",
+ "simStepLabel": "Flow 1: CSV Import - Service to Database Module",
+ "simStepDescription": "The array of parsed and enriched monitor objects is passed to the database module for persistence.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/business/monitorService.js",
+ "startLine": "126",
+ "endLine": "126",
+ "relevantVariables": [
+ "this.db.monitorModule.createBulkMonitors"
+ ]
+ },
+ "inputDataExample": "{\"enrichedData\": [{\"name\": \"Google\", \"type\": \"http\", \"url\": \"https://google.com\", \"userId\": \"user_123\", \"teamId\": \"team_abc\"}]}",
+ "outputDataExample": "{\"enrichedData\": [{\"name\": \"Google\", \"type\": \"http\", \"url\": \"https://google.com\", \"userId\": \"user_123\", \"teamId\": \"team_abc\"}]}"
+ },
+ {
+ "simStepId": "70f18c21-2918-402e-9c35-1bdce8bc82d4",
+ "diagramNodeId": "d9d519af-5c4d-4494-929e-a869581c1dc4",
+ "simStepLabel": "Flow 1: CSV Import - Database Bulk Save",
+ "simStepDescription": "The `monitorModule` uses `Monitor.bulkSave` to efficiently insert all the new monitor documents into the MongoDB database in a single operation.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/db/v1/modules/monitorModule.js",
+ "startLine": "465",
+ "endLine": "468",
+ "relevantVariables": [
+ "createBulkMonitors",
+ "this.Monitor.bulkSave"
+ ]
+ },
+ "inputDataExample": "[{\"name\": \"Google\", \"type\": \"http\", \"url\": \"https://google.com\"}]",
+ "outputDataExample": "[{\"_id\": \"monitor_xyz\", \"name\": \"Google\"}]"
+ },
+ {
+ "simStepId": "2ee7bd94-d131-4dd8-bdf8-68433d00472d",
+ "diagramNodeId": "f27c60ef-fb40-43e2-9750-c2617bdeaf43",
+ "simStepLabel": "Flow 1: CSV Import - Database to Service",
+ "simStepDescription": "The newly created monitor documents, now with database-generated `_id`s, are returned from the database module back to the service layer.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/db/v1/modules/monitorModule.js",
+ "startLine": "467",
+ "endLine": "467",
+ "relevantVariables": [
+ "monitors"
+ ]
+ },
+ "inputDataExample": "[{\"_id\": \"monitor_xyz\", \"name\": \"Google\"}]",
+ "outputDataExample": "[{\"_id\": \"monitor_xyz\", \"name\": \"Google\"}]"
+ },
+ {
+ "simStepId": "66fbf913-e662-4bd9-ada6-077be9e37f07",
+ "diagramNodeId": "1e1d975f-7f0f-4132-bd71-2563b35e99b3",
+ "simStepLabel": "Flow 1: CSV Import - Enqueue Monitor Jobs",
+ "simStepDescription": "After the monitors are saved, the service iterates through them and adds a new job to the `jobQueue` for each one, scheduling them for their first check. It then resolves the promise, returning the data up the call stack.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/business/monitorService.js",
+ "startLine": "128",
+ "endLine": "132",
+ "relevantVariables": [
+ "Promise.all",
+ "this.jobQueue.addJob"
+ ]
+ },
+ "inputDataExample": "[{\"_id\": \"monitor_xyz\", \"name\": \"Google\"}]",
+ "outputDataExample": "[{\"_id\": \"monitor_xyz\", \"name\": \"Google\"}]"
+ },
+ {
+ "simStepId": "94d5772d-3f46-4d43-b764-3493db5c4971",
+ "diagramNodeId": "c449f81a-d6af-4f7a-b2d8-050a9a9ee0f9",
+ "simStepLabel": "Flow 1: CSV Import - Service to Controller",
+ "simStepDescription": "The final list of created monitors is passed back to the controller.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/monitorController.js",
+ "startLine": "230",
+ "endLine": "230",
+ "relevantVariables": [
+ "monitors"
+ ]
+ },
+ "inputDataExample": "[{\"_id\": \"monitor_xyz\", \"name\": \"Google\"}]",
+ "outputDataExample": "[{\"_id\": \"monitor_xyz\", \"name\": \"Google\"}]"
+ },
+ {
+ "simStepId": "f5377571-156f-4c5c-8bab-b6dacdf0fea6",
+ "diagramNodeId": "72b55fa1-d944-4e46-ac98-f6a6cb201afa",
+ "simStepLabel": "Flow 1: CSV Import - Send Success Response",
+ "simStepDescription": "The controller constructs a successful JSON response containing a message and the data for the newly created monitors, and sends it back to the client.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/monitorController.js",
+ "startLine": "232",
+ "endLine": "235",
+ "relevantVariables": [
+ "res.success"
+ ]
+ },
+ "inputDataExample": "[{\"_id\": \"monitor_xyz\", \"name\": \"Google\"}]",
+ "outputDataExample": "{\"msg\": \"Monitors created successfully.\", \"data\": [{\"_id\": \"monitor_xyz\"}]}"
+ },
+ {
+ "simStepId": "c60a3adb-dd81-41b0-95e4-a0a6d23f9dd0",
+ "diagramNodeId": "926a330c-a9c5-4983-a4b5-70359813e49a",
+ "simStepLabel": "Flow 1: CSV Import - Transmit Success to Client",
+ "simStepDescription": "The server sends the successful HTTP response back to the client's browser.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Hooks/v1/monitorHooks.js",
+ "startLine": "494",
+ "endLine": "494",
+ "relevantVariables": [
+ "response"
+ ]
+ },
+ "inputDataExample": "{\"status\": 200, \"body\": {\"msg\": \"Monitors created successfully!\", \"data\": [{\"_id\": \"monitor_xyz\"}]}}",
+ "outputDataExample": "{\"status\": 200, \"body\": {\"msg\": \"Monitors created successfully!\", \"data\": [{\"_id\": \"monitor_xyz\"}]}}"
+ },
+ {
+ "simStepId": "72b3f16d-290d-4a61-8748-2ad90d63ec68",
+ "diagramNodeId": "ae3b7e23-ed64-40aa-9760-389f0a869f8d",
+ "simStepLabel": "Flow 1: CSV Import - Client Handles Success",
+ "simStepDescription": "The client-side promise resolves. The `handleSubmit` function in the `BulkImport` component receives the success status, displays a success toast message, and navigates the user to the main uptime monitoring page.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Uptime/BulkImport/index.jsx",
+ "startLine": "37",
+ "endLine": "41",
+ "relevantVariables": [
+ "createToast",
+ "navigate"
+ ]
+ },
+ "inputDataExample": "{\"success\": true, \"data\": [{\"_id\": \"monitor_xyz\"}]}",
+ "outputDataExample": "{\"navigation\": \"/uptime\"}"
+ },
+ {
+ "simStepId": "63d8d81f-f436-4594-a6eb-8c0fd2110bc1",
+ "diagramNodeId": "26d76516-fd7e-4ae8-956a-d077de4257fa",
+ "simStepLabel": "Flow 2: CSV Export - Initiate Export",
+ "simStepDescription": "An admin user clicks an 'Export to CSV' button in the UI. This action triggers a call to the `/monitors/export` API endpoint to begin the export process.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/routes/v1/monitorRoute.js",
+ "startLine": "45",
+ "endLine": "45",
+ "relevantVariables": [
+ "this.router.get",
+ "this.monitorController.exportMonitorsToCSV"
+ ]
+ },
+ "inputDataExample": "{}",
+ "outputDataExample": "{\"request\": {\"method\": \"GET\", \"endpoint\": \"/api/v1/monitors/export\"}}"
+ },
+ {
+ "simStepId": "53cebb8e-cf45-49eb-a7d3-98fd87af21b4",
+ "diagramNodeId": "febea87e-5033-479c-ba7a-eb35d98db899",
+ "simStepLabel": "Flow 2: CSV Export - Transmit Request to Server",
+ "simStepDescription": "An HTTP GET request is sent from the client to the server to fetch the monitor data as a CSV file.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/routes/v1/monitorRoute.js",
+ "startLine": "45",
+ "endLine": "45",
+ "relevantVariables": [
+ "this.router.get",
+ "this.monitorController.exportMonitorsToCSV"
+ ]
+ },
+ "inputDataExample": "{\"request\": {\"method\": \"GET\", \"endpoint\": \"/api/v1/monitors/export\"}}",
+ "outputDataExample": "{\"request\": {\"method\": \"GET\", \"endpoint\": \"/api/v1/monitors/export\"}}"
+ },
+ {
+ "simStepId": "d9db3f40-8add-4551-9323-274348b9800f",
+ "diagramNodeId": "a3106388-6a3d-4b68-a3fc-3761a0d54f98",
+ "simStepLabel": "Flow 2: CSV Export - Controller Handles Request",
+ "simStepDescription": "The `exportMonitorsToCSV` method in the monitor controller is invoked. It retrieves the `teamId` from the authenticated user's session and calls the monitor service to perform the export.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/monitorController.js",
+ "startLine": "427",
+ "endLine": "434",
+ "relevantVariables": [
+ "exportMonitorsToCSV",
+ "teamId",
+ "this.monitorService.exportMonitorsToCSV"
+ ]
+ },
+ "inputDataExample": "{\"req\": {\"user\": {\"teamId\": \"team_abc\"}}}",
+ "outputDataExample": "{\"teamId\": \"team_abc\"}"
+ },
+ {
+ "simStepId": "74e95fb5-863c-4137-a41b-d7a32efa04a5",
+ "diagramNodeId": "3e0f0966-5c2f-41af-9bcc-e8f615e03a00",
+ "simStepLabel": "Flow 2: CSV Export - Controller to Service",
+ "simStepDescription": "The `teamId` is passed from the controller to the monitor service to ensure only monitors belonging to the correct team are exported.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/business/monitorService.js",
+ "startLine": "245",
+ "endLine": "245",
+ "relevantVariables": [
+ "exportMonitorsToCSV",
+ "teamId"
+ ]
+ },
+ "inputDataExample": "{\"teamId\": \"team_abc\"}",
+ "outputDataExample": "{\"teamId\": \"team_abc\"}"
+ },
+ {
+ "simStepId": "85cd1d5d-7ffb-49c5-aacb-907048003fc5",
+ "diagramNodeId": "ab9cb862-0ac7-45a3-9888-f64e87bdda54",
+ "simStepLabel": "Flow 2: CSV Export - Generate CSV",
+ "simStepDescription": "The monitor service fetches all monitors for the given `teamId` from the database. It then maps the data into the required format and uses `papaparse` to convert the JSON array into a CSV-formatted string.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/business/monitorService.js",
+ "startLine": "245",
+ "endLine": "266",
+ "relevantVariables": [
+ "getMonitorsByTeamId",
+ "csvData",
+ "this.papaparse.unparse"
+ ]
+ },
+ "inputDataExample": "{\"teamId\": \"team_abc\"}",
+ "outputDataExample": "{\"csv\": \"name,description,type,url,interval,port,ignoreTlsErrors,isActive\\nMy Website,,http,https://example.com,60,,false,true\"}"
+ },
+ {
+ "simStepId": "4deb5e47-2ac4-4638-9ee6-48295d027bcd",
+ "diagramNodeId": "ad300b1c-6748-40f9-a7c9-5895e0915991",
+ "simStepLabel": "Flow 2: CSV Export - Service to Controller",
+ "simStepDescription": "The generated CSV string is returned from the service layer to the controller.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/monitorController.js",
+ "startLine": "432",
+ "endLine": "432",
+ "relevantVariables": [
+ "csv"
+ ]
+ },
+ "inputDataExample": "{\"csv\": \"name,description,type,url,...\\nMy Website,,http,https://example.com,...\"}",
+ "outputDataExample": "{\"csv\": \"name,description,type,url,...\\nMy Website,,http,https://example.com,...\"}"
+ },
+ {
+ "simStepId": "256095b8-5e3a-4c20-abd9-be9e6a9d0bad",
+ "diagramNodeId": "f151f89d-6399-48f0-aecd-5eb1572b48cc",
+ "simStepLabel": "Flow 2: CSV Export - Send File Response",
+ "simStepDescription": "The controller receives the CSV string and uses the `res.file` method to send it back to the client as a file. It sets the `Content-Type` to `text/csv` and `Content-Disposition` to trigger a download with the filename `monitors.csv`.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/monitorController.js",
+ "startLine": "434",
+ "endLine": "440",
+ "relevantVariables": [
+ "res.file",
+ "csv"
+ ]
+ },
+ "inputDataExample": "{\"csv\": \"name,description,type,url,...\\nMy Website,,http,https://example.com,...\"}",
+ "outputDataExample": "{\"responseHeaders\": {\"Content-Type\": \"text/csv\", \"Content-Disposition\": \"attachment; filename=\\\"monitors.csv\\\"\"}}"
+ },
+ {
+ "simStepId": "605f4cbd-1f7f-4e89-b028-f18737ac7fb1",
+ "diagramNodeId": "3dfb432a-2ce4-4f92-98ce-5d114b5f7f03",
+ "simStepLabel": "Flow 2: CSV Export - Transmit File to Client",
+ "simStepDescription": "The server sends the HTTP response, which contains the CSV data and headers, to the client's browser.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/monitorController.js",
+ "startLine": "434",
+ "endLine": "440",
+ "relevantVariables": [
+ "res.file"
+ ]
+ },
+ "inputDataExample": "{\"httpResponse\": {\"headers\": {\"Content-Disposition\": \"attachment; filename=\\\"monitors.csv\\\"\"}, \"body\": \"name,description,...\\n...\"}}",
+ "outputDataExample": "{\"httpResponse\": {\"headers\": {\"Content-Disposition\": \"attachment; filename=\\\"monitors.csv\\\"\"}, \"body\": \"name,description,...\\n...\"}}"
+ },
+ {
+ "simStepId": "60abdb27-4078-4678-a010-0f6938d4be48",
+ "diagramNodeId": "d8877474-7203-484f-bd47-fc69e3c7c482",
+ "simStepLabel": "Flow 2: CSV Export - Browser Prompts Download",
+ "simStepDescription": "The browser receives the server's response. The `Content-Disposition` header instructs the browser to prompt the user to save the received data as a file named `monitors.csv`, completing the export process.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/monitorController.js",
+ "startLine": "434",
+ "endLine": "440",
+ "relevantVariables": [
+ "res.file"
+ ]
+ },
+ "inputDataExample": "{\"httpResponse\": {\"headers\": {\"Content-Disposition\": \"attachment; filename=\\\"monitors.csv\\\"\"}}}",
+ "outputDataExample": "{\"userAction\": \"File Save Dialog\"}"
+ }
+ ],
+ "description": "Provides efficiency tools for managing a large number of monitors - Users can import multiple monitors at once by uploading a CSV file, saving significant time during initial setup - A template CSV is provided to guide the user on the required format and available fields - All existing monitors can be exported to a CSV file, useful for backups or migrations - Superadmins have the ability to delete all monitors for a team in a single action ",
+ "simulationNodesAndEdges": {
+ "c4caeba8-ed9a-4cbc-9888-38cc8ca55362": {
+ "simStepIds": [
+ "a29da01e-8c5a-4e6d-8d3c-e49af528e4fa"
+ ]
+ },
+ "6f1948fa-e84a-4f8e-8f3b-8f24c511b3d8": {
+ "simStepIds": [
+ "bbd04d55-fc6a-4ba8-bcde-5a4dc263d6eb"
+ ]
+ },
+ "dd2f9356-fad3-47d4-81d5-a5e038298e2b": {
+ "simStepIds": [
+ "0139fcaa-eb46-4176-b7bd-1cb15e7f1f4c"
+ ]
+ },
+ "5967e576-f542-4c0b-a3f4-9446ae992721": {
+ "simStepIds": [
+ "2fc087b3-033d-4cf5-81f2-e35b99a14bce"
+ ]
+ },
+ "c1ac3c26-996a-43ef-9c34-4c2776188784": {
+ "simStepIds": [
+ "07bf3d83-047d-4e51-9f96-8c9c64848500"
+ ]
+ },
+ "0e520a6d-34bf-467b-a894-e73979364478": {
+ "simStepIds": [
+ "a3799570-64bf-481e-9ffe-31dbad8a36e4"
+ ]
+ },
+ "6139e487-1dd5-405a-ba7d-ededec4b282e": {
+ "simStepIds": [
+ "2c7c3082-e4b6-4525-860f-2ffcd9e86db1"
+ ]
+ },
+ "d9d519af-5c4d-4494-929e-a869581c1dc4": {
+ "simStepIds": [
+ "70f18c21-2918-402e-9c35-1bdce8bc82d4"
+ ]
+ },
+ "1e1d975f-7f0f-4132-bd71-2563b35e99b3": {
+ "simStepIds": [
+ "66fbf913-e662-4bd9-ada6-077be9e37f07"
+ ]
+ },
+ "72b55fa1-d944-4e46-ac98-f6a6cb201afa": {
+ "simStepIds": [
+ "f5377571-156f-4c5c-8bab-b6dacdf0fea6"
+ ]
+ },
+ "ae3b7e23-ed64-40aa-9760-389f0a869f8d": {
+ "simStepIds": [
+ "72b3f16d-290d-4a61-8748-2ad90d63ec68"
+ ]
+ },
+ "26d76516-fd7e-4ae8-956a-d077de4257fa": {
+ "simStepIds": [
+ "63d8d81f-f436-4594-a6eb-8c0fd2110bc1"
+ ]
+ },
+ "a3106388-6a3d-4b68-a3fc-3761a0d54f98": {
+ "simStepIds": [
+ "d9db3f40-8add-4551-9323-274348b9800f"
+ ]
+ },
+ "ab9cb862-0ac7-45a3-9888-f64e87bdda54": {
+ "simStepIds": [
+ "85cd1d5d-7ffb-49c5-aacb-907048003fc5"
+ ]
+ },
+ "f151f89d-6399-48f0-aecd-5eb1572b48cc": {
+ "simStepIds": [
+ "256095b8-5e3a-4c20-abd9-be9e6a9d0bad"
+ ]
+ },
+ "d8877474-7203-484f-bd47-fc69e3c7c482": {
+ "simStepIds": [
+ "60abdb27-4078-4678-a010-0f6938d4be48"
+ ]
+ },
+ "c9a012bc-d53f-494f-82f2-1ee3f40ce646": {
+ "simStepIds": [
+ "ef0bbcff-9535-4c0e-a5ca-96c457f89139"
+ ]
+ },
+ "e03a8ce2-c2cd-4147-8bab-63d6c2cf0a48": {
+ "simStepIds": [
+ "610120d2-0521-4444-b245-feb97239a389"
+ ]
+ },
+ "b8c0292d-afbb-4648-b484-1c27be96d52f": {
+ "simStepIds": [
+ "a365d018-e80c-419c-80b3-5ffc8f14dc5c"
+ ]
+ },
+ "23715cce-c83a-44c0-9853-4c95b85e5ecd": {
+ "simStepIds": [
+ "e6dec1dc-33cb-4669-a3b0-7a3c0b233185"
+ ]
+ },
+ "20d0e690-a569-4c6f-8238-916cac920553": {
+ "simStepIds": [
+ "c53d3f62-73fe-4fad-9835-ef2c8cf52894"
+ ]
+ },
+ "7292bf6e-431d-4a9d-94ce-2bcfabcaa79d": {
+ "simStepIds": [
+ "6038422c-ab83-4fc6-a58b-c75734276118"
+ ]
+ },
+ "b55a0dc6-f596-4e5f-9265-63588b46b519": {
+ "simStepIds": [
+ "0115fb69-4959-4b32-bd90-1dcd3c2231f6"
+ ]
+ },
+ "f27c60ef-fb40-43e2-9750-c2617bdeaf43": {
+ "simStepIds": [
+ "2ee7bd94-d131-4dd8-bdf8-68433d00472d"
+ ]
+ },
+ "c449f81a-d6af-4f7a-b2d8-050a9a9ee0f9": {
+ "simStepIds": [
+ "94d5772d-3f46-4d43-b764-3493db5c4971"
+ ]
+ },
+ "926a330c-a9c5-4983-a4b5-70359813e49a": {
+ "simStepIds": [
+ "c60a3adb-dd81-41b0-95e4-a0a6d23f9dd0"
+ ]
+ },
+ "febea87e-5033-479c-ba7a-eb35d98db899": {
+ "simStepIds": [
+ "53cebb8e-cf45-49eb-a7d3-98fd87af21b4"
+ ]
+ },
+ "3e0f0966-5c2f-41af-9bcc-e8f615e03a00": {
+ "simStepIds": [
+ "74e95fb5-863c-4137-a41b-d7a32efa04a5"
+ ]
+ },
+ "ad300b1c-6748-40f9-a7c9-5895e0915991": {
+ "simStepIds": [
+ "4deb5e47-2ac4-4638-9ee6-48295d027bcd"
+ ]
+ },
+ "3dfb432a-2ce4-4f92-98ce-5d114b5f7f03": {
+ "simStepIds": [
+ "605f4cbd-1f7f-4e89-b028-f18737ac7fb1"
+ ]
+ }
+ },
+ "isAIGenerated": true,
+ "keywords": "bulkImport, createBulkMonitors, exportMonitorsToCSV",
+ "generationPrompt": "Bulk Monitor Management",
+ "generationKeywords": "bulkImport, createBulkMonitors, exportMonitorsToCSV"
+ },
+ "System Administration and Configuration": {
+ "name": "System Administration and Configuration",
+ "simSteps": [
+ {
+ "simStepId": "64e9f1af-13b7-4361-a2a9-3f45760e316d",
+ "diagramNodeId": "97908daf-d071-4df5-8366-1f5f4d8d534a",
+ "simStepLabel": "Flow 1: Render Settings Page",
+ "simStepDescription": "The user navigates to the settings page. The main `Settings` component fetches existing settings using the `useFetchSettings` hook and renders various sub-components for different configuration areas like TimeZone, UI, PageSpeed, Email, etc.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Settings/index.jsx",
+ "startLine": "25",
+ "endLine": "52",
+ "relevantVariables": [
+ "useFetchSettings",
+ "settingsData",
+ "setSettingsData",
+ "isApiKeySet",
+ "isEmailPasswordSet"
+ ]
+ },
+ "inputDataExample": "{}",
+ "outputDataExample": "{\"settingsData\": {}, \"errors\": {}, \"isApiKeySet\": false, \"isEmailPasswordSet\": false}"
+ },
+ {
+ "simStepId": "dca4a33c-b666-47b8-896b-370ed9d6f9c5",
+ "diagramNodeId": "f6d80f09-ae83-4c95-aba0-4efffeda38e0",
+ "simStepLabel": "API Call: Fetch Application Settings",
+ "simStepDescription": "The `useFetchSettings` hook triggers a GET request to the `/api/v1/settings` endpoint to retrieve the current application configuration.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Utils/NetworkService.js",
+ "startLine": "692",
+ "endLine": "698",
+ "relevantVariables": [
+ "getAppSettings"
+ ]
+ },
+ "inputDataExample": "{\"method\": \"GET\", \"url\": \"/api/v1/settings\"}",
+ "outputDataExample": "{\"method\": \"GET\", \"url\": \"/api/v1/settings\"}"
+ },
+ {
+ "simStepId": "441b0c45-5f27-478d-8ca8-58fbd5f460ea",
+ "diagramNodeId": "83596bd1-dfca-4196-b52e-955f6824fafb",
+ "simStepLabel": "Backend: Retrieve Settings from Database",
+ "simStepDescription": "The `settingsController` handles the GET request. It calls the `settingsService` to fetch settings from the database. Sensitive data like passwords is sanitized, and flags like `emailPasswordSet` are added before sending the data to the client.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/settingsController.js",
+ "startLine": "40",
+ "endLine": "49",
+ "relevantVariables": [
+ "getAppSettings",
+ "settingsService.getDBSettings",
+ "buildAppSettings"
+ ]
+ },
+ "inputDataExample": "{\"request\": \"GET /api/v1/settings\"}",
+ "outputDataExample": "{\"msg\": \"Got app settings successfully\", \"data\": {\"emailPasswordSet\": false, \"pagespeedKeySet\": false, \"settings\": {\"checkTTL\": 7, \"timezone\": \"America/Toronto\"}}}"
+ },
+ {
+ "simStepId": "aacc9302-6ca8-45c6-acb8-956d8d0242c2",
+ "diagramNodeId": "195ad532-9858-493e-876a-6ec457811d1d",
+ "simStepLabel": "API Response: Send Settings to Client",
+ "simStepDescription": "The backend responds to the GET request with a JSON object containing the application settings, which will be used to populate the UI.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/settingsController.js",
+ "startLine": "45",
+ "endLine": "48",
+ "relevantVariables": [
+ "res.success"
+ ]
+ },
+ "inputDataExample": "{\"msg\": \"Got app settings successfully\", \"data\": {\"emailPasswordSet\": false, \"pagespeedKeySet\": false, \"settings\": {\"checkTTL\": 7, \"timezone\": \"America/Toronto\", \"systemEmailHost\": \"\", \"systemEmailPort\": \"\", \"systemEmailSecure\": false, \"systemEmailUser\": \"\", \"systemEmailAddress\": \"\"}}}",
+ "outputDataExample": "{\"msg\": \"Got app settings successfully\", \"data\": {\"emailPasswordSet\": false, \"pagespeedKeySet\": false, \"settings\": {\"checkTTL\": 7, \"timezone\": \"America/Toronto\", \"systemEmailHost\": \"\", \"systemEmailPort\": \"\", \"systemEmailSecure\": false, \"systemEmailUser\": \"\", \"systemEmailAddress\": \"\"}}}"
+ },
+ {
+ "simStepId": "ae5c5146-4dbb-4612-9d23-086e57f72474",
+ "diagramNodeId": "09cb194a-0ea4-46e2-b32b-36b62fa87f40",
+ "simStepLabel": "User Interaction: Modify Email Settings",
+ "simStepDescription": "The user interacts with the `SettingsEmail` component, filling in SMTP server details. The component's state is updated via the `handleChange` function passed down from the parent `Settings` component, which prepares the new configuration data.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Settings/SettingsEmail.jsx",
+ "startLine": "119",
+ "endLine": "167",
+ "relevantVariables": [
+ "handleChange",
+ "handlePasswordChange",
+ "TextInput",
+ "systemEmailHost",
+ "systemEmailPort",
+ "systemEmailUser",
+ "systemEmailPassword"
+ ]
+ },
+ "inputDataExample": "{\"settingsData\": {\"settings\": {\"systemEmailHost\": \"\"}}}",
+ "outputDataExample": "{\"settingsData\": {\"settings\": {\"systemEmailHost\": \"smtp.gmail.com\", \"systemEmailPort\": \"465\", \"systemEmailUser\": \"user@gmail.com\", \"systemEmailAddress\": \"user@gmail.com\", \"systemEmailPassword\": \"app-password-123\"}}}"
+ },
+ {
+ "simStepId": "e7e82a97-1409-4bc5-ae27-b4ff5ba746df",
+ "diagramNodeId": "8c107d79-f796-49eb-ab88-f553dd6f9d95",
+ "simStepLabel": "User Action: Save Settings",
+ "simStepDescription": "The user clicks the 'Save' button. This action triggers the `handleSave` function, which validates the new settings and prepares to send them to the backend.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Settings/index.jsx",
+ "startLine": "258",
+ "endLine": "261",
+ "relevantVariables": [
+ "handleSave"
+ ]
+ },
+ "inputDataExample": "{\"settingsData\": {\"settings\": {\"systemEmailHost\": \"smtp.gmail.com\", \"systemEmailPort\": \"465\", \"systemEmailUser\": \"user@gmail.com\", \"systemEmailAddress\": \"user@gmail.com\", \"systemEmailPassword\": \"app-password-123\"}}}",
+ "outputDataExample": "{\"settingsData\": {\"settings\": {\"systemEmailHost\": \"smtp.gmail.com\", \"systemEmailPort\": \"465\", \"systemEmailUser\": \"user@gmail.com\", \"systemEmailAddress\": \"user@gmail.com\", \"systemEmailPassword\": \"app-password-123\"}}}"
+ },
+ {
+ "simStepId": "d7098072-9a83-4369-8176-9646ba34e186",
+ "diagramNodeId": "1b2cf182-9ee3-4218-a7ec-b74e427ccddf",
+ "simStepLabel": "API Call: Update Application Settings",
+ "simStepDescription": "The `handleSave` function calls `saveSettings` from the `useSaveSettings` hook. This triggers a PUT request to `/api/v1/settings` with the new configuration in the request body.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Hooks/v1/settingsHooks.js",
+ "startLine": "41",
+ "endLine": "67",
+ "relevantVariables": [
+ "saveSettings",
+ "networkService.updateAppSettings"
+ ]
+ },
+ "inputDataExample": "{\"settings\": {\"systemEmailHost\": \"smtp.gmail.com\", \"systemEmailPort\": \"465\", \"systemEmailUser\": \"user@gmail.com\", \"systemEmailAddress\": \"user@gmail.com\", \"systemEmailPassword\": \"app-password-123\"}}",
+ "outputDataExample": "{\"settings\": {\"systemEmailHost\": \"smtp.gmail.com\", \"systemEmailPort\": \"465\", \"systemEmailUser\": \"user@gmail.com\", \"systemEmailAddress\": \"user@gmail.com\", \"systemEmailPassword\": \"app-password-123\"}}"
+ },
+ {
+ "simStepId": "383387e5-4623-44ab-b2c4-d9ea046f4547",
+ "diagramNodeId": "ce3ed3ba-a35b-4881-8dfd-f8425b9bf812",
+ "simStepLabel": "Data Transmission: Send Updated Settings to Server",
+ "simStepDescription": "The PUT request containing the new settings is sent to the backend API endpoint for processing and persistence.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Utils/NetworkService.js",
+ "startLine": "711",
+ "endLine": "717",
+ "relevantVariables": [
+ "updateAppSettings",
+ "this.axiosInstance.put"
+ ]
+ },
+ "inputDataExample": "{\"settings\": {\"systemEmailHost\": \"smtp.gmail.com\", \"systemEmailPort\": \"465\", \"systemEmailUser\": \"user@gmail.com\", \"systemEmailAddress\": \"user@gmail.com\", \"systemEmailPassword\": \"app-password-123\"}}",
+ "outputDataExample": "{\"settings\": {\"systemEmailHost\": \"smtp.gmail.com\", \"systemEmailPort\": \"465\", \"systemEmailUser\": \"user@gmail.com\", \"systemEmailAddress\": \"user@gmail.com\", \"systemEmailPassword\": \"app-password-123\"}}"
+ },
+ {
+ "simStepId": "7632e8d2-4086-4169-ae3f-bfbf8d657987",
+ "diagramNodeId": "cce3b9c8-f3b7-434b-b3c6-d8d164714f86",
+ "simStepLabel": "Backend: Persist Updated Settings",
+ "simStepDescription": "The `settingsController` receives the PUT request, validates the body, and calls the `db.settingsModule.updateAppSettings` method to update the settings document in the database.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/settingsController.js",
+ "startLine": "54",
+ "endLine": "64",
+ "relevantVariables": [
+ "updateAppSettings",
+ "updateAppSettingsBodyValidation",
+ "db.settingsModule.updateAppSettings"
+ ]
+ },
+ "inputDataExample": "{\"settings\": {\"systemEmailHost\": \"smtp.gmail.com\", \"systemEmailPort\": \"465\", \"systemEmailUser\": \"user@gmail.com\", \"systemEmailAddress\": \"user@gmail.com\", \"systemEmailPassword\": \"app-password-123\"}}",
+ "outputDataExample": "{\"msg\": \"Updated app settings successfully\", \"data\": {\"emailPasswordSet\": true, \"settings\": {\"systemEmailHost\": \"smtp.gmail.com\", ...}}}"
+ },
+ {
+ "simStepId": "0afcde35-7d51-4939-aae8-bac5ac83c49d",
+ "diagramNodeId": "fbe9b160-307e-4f51-8f72-713fda5373c8",
+ "simStepLabel": "API Response: Confirm Settings Update",
+ "simStepDescription": "The backend sends a success response to the client, including the newly saved (and sanitized) settings, confirming the update.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/settingsController.js",
+ "startLine": "60",
+ "endLine": "63",
+ "relevantVariables": [
+ "res.success"
+ ]
+ },
+ "inputDataExample": "{\"msg\": \"Updated app settings successfully\", \"data\": {\"emailPasswordSet\": true, \"pagespeedKeySet\": false, \"settings\": {\"checkTTL\": 7, \"timezone\": \"America/Toronto\", \"systemEmailHost\": \"smtp.gmail.com\", \"systemEmailPort\": 465, \"systemEmailUser\": \"user@gmail.com\", \"systemEmailAddress\": \"user@gmail.com\"}}}",
+ "outputDataExample": "{\"msg\": \"Updated app settings successfully\", \"data\": {\"emailPasswordSet\": true, \"pagespeedKeySet\": false, \"settings\": {\"checkTTL\": 7, \"timezone\": \"America/Toronto\", \"systemEmailHost\": \"smtp.gmail.com\", \"systemEmailPort\": 465, \"systemEmailUser\": \"user@gmail.com\", \"systemEmailAddress\": \"user@gmail.com\"}}}"
+ },
+ {
+ "simStepId": "a157bd07-b969-44f8-914c-3d8393c1605a",
+ "diagramNodeId": "fff5dde4-8eb8-4c21-9c6d-0845a9749ff3",
+ "simStepLabel": "UI Update: Display Success Confirmation",
+ "simStepDescription": "The frontend network service receives the successful API response. The `saveSettings` hook then calls `createToast` with a success message, which is displayed to the user.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Hooks/v1/settingsHooks.js",
+ "startLine": "58",
+ "endLine": "58",
+ "relevantVariables": [
+ "createToast",
+ "t(\"settingsSuccessSaved\")"
+ ]
+ },
+ "inputDataExample": "{\"body\": \"Settings saved successfully\"}",
+ "outputDataExample": "{}"
+ },
+ {
+ "simStepId": "a9b24b5f-534b-4baf-a185-bac7c32a1c42",
+ "diagramNodeId": "704e2ec8-9f2f-47e5-852f-c6ada67a02e0",
+ "simStepLabel": "Flow 2: Navigate to Diagnostics Page",
+ "simStepDescription": "The user navigates to the 'Logs' page and selects the 'Diagnostics' tab. The `Diagnostics` component renders and immediately calls the `useFetchDiagnostics` hook to retrieve system health data.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Logs/Diagnostics/index.jsx",
+ "startLine": "14",
+ "endLine": "15",
+ "relevantVariables": [
+ "useFetchDiagnostics",
+ "diagnostics"
+ ]
+ },
+ "inputDataExample": "{}",
+ "outputDataExample": "{}"
+ },
+ {
+ "simStepId": "23b8ad7d-8967-4e48-814c-1a7bf5b4dfac",
+ "diagramNodeId": "499dfe1f-77d4-4b26-a12c-45240691109e",
+ "simStepLabel": "API Call: Fetch System Diagnostics",
+ "simStepDescription": "The `useFetchDiagnostics` hook triggers a GET request to the `/api/v1/diagnostic/system` endpoint to fetch real-time system metrics.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Utils/NetworkService.js",
+ "startLine": "1113",
+ "endLine": "1119",
+ "relevantVariables": [
+ "getDiagnostics",
+ "this.axiosInstance.get"
+ ]
+ },
+ "inputDataExample": "{\"method\": \"GET\", \"url\": \"/api/v1/diagnostic/system\"}",
+ "outputDataExample": "{\"method\": \"GET\", \"url\": \"/api/v1/diagnostic/system\"}"
+ },
+ {
+ "simStepId": "8fb704ca-67d9-437c-90d8-01302634d568",
+ "diagramNodeId": "09f07e80-185e-4547-aab4-edaf8ae7501e",
+ "simStepLabel": "Backend: Gather System Statistics",
+ "simStepDescription": "The `diagnosticController` receives the request and calls `diagnosticService.getSystemStats()`. This service uses native Node.js modules like `os` and `v8` to collect metrics on memory, CPU, heap statistics, and process uptime.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "server/src/service/v1/business/diagnosticService.js",
+ "startLine": "39",
+ "endLine": "95",
+ "relevantVariables": [
+ "getSystemStats",
+ "os",
+ "process.memoryUsage",
+ "process.cpuUsage",
+ "v8.getHeapStatistics",
+ "process.uptime"
+ ]
+ },
+ "inputDataExample": "{\"request\": \"GET /api/v1/diagnostic/system\"}",
+ "outputDataExample": "{\"osStats\": {\"totalMemoryBytes\": 16777216000, \"freeMemoryBytes\": 1572864000}, \"memoryUsage\": {\"rss\": 123456789}, \"cpuUsage\": {\"usagePercentage\": 12.5}, \"v8HeapStats\": {\"usedHeapSizeBytes\": 54321098}, \"uptimeMs\": 86400000}"
+ },
+ {
+ "simStepId": "44a4372f-a903-4c43-a9a5-c7549c875e11",
+ "diagramNodeId": "d0f92604-a6fa-4d85-9716-ea8f32db78e0",
+ "simStepLabel": "API Response: Send Diagnostics Data to Client",
+ "simStepDescription": "The backend responds with a JSON object containing the comprehensive system diagnostics data.",
+ "isEdge": 1,
+ "sourceCodeMapping": {
+ "filePath": "server/src/controllers/v1/diagnosticController.js",
+ "startLine": "52",
+ "endLine": "55",
+ "relevantVariables": [
+ "res.success"
+ ]
+ },
+ "inputDataExample": "{\"msg\": \"OK\", \"data\": {\"osStats\": {\"totalMemoryBytes\": 16777216000, \"freeMemoryBytes\": 1572864000}, \"memoryUsage\": {\"rss\": 123456789}, \"cpuUsage\": {\"usagePercentage\": 12.5}, \"v8HeapStats\": {\"usedHeapSizeBytes\": 54321098}, \"uptimeMs\": 86400000}}",
+ "outputDataExample": "{\"msg\": \"OK\", \"data\": {\"osStats\": {\"totalMemoryBytes\": 16777216000, \"freeMemoryBytes\": 1572864000}, \"memoryUsage\": {\"rss\": 123456789}, \"cpuUsage\": {\"usagePercentage\": 12.5}, \"v8HeapStats\": {\"usedHeapSizeBytes\": 54321098}, \"uptimeMs\": 86400000}}"
+ },
+ {
+ "simStepId": "e6c94f52-a599-4568-a06f-2db18f963817",
+ "diagramNodeId": "4b418736-6b72-47e0-917a-d86d4bed9ecd",
+ "simStepLabel": "UI Update: Display System Health",
+ "simStepDescription": "The frontend receives the diagnostic data. The `Diagnostics` component passes this data to its children, `Gauges` and `StatusBoxes`, which then render the information as interactive gauges and statistical boxes.",
+ "isEdge": 0,
+ "sourceCodeMapping": {
+ "filePath": "client/src/Pages/v1/Logs/Diagnostics/index.jsx",
+ "startLine": "59",
+ "endLine": "63",
+ "relevantVariables": [
+ "Gauges",
+ "StatusBoxes",
+ "diagnostics"
+ ]
+ },
+ "inputDataExample": "{\"diagnostics\": {\"osStats\": {\"totalMemoryBytes\": 16777216000, \"freeMemoryBytes\": 1572864000}, \"cpuUsage\": {\"usagePercentage\": 12.5}, \"v8HeapStats\": {\"usedHeapSizeBytes\": 54321098}, \"uptimeMs\": 86400000}}",
+ "outputDataExample": "{}"
+ }
+ ],
+ "description": "Provides centralized settings for administrators to configure and maintain the Checkmate instance - Admins can configure global application settings, including email server (SMTP) details for sending alerts - UI settings like theme (light/dark) and default timezone can be customized - Data retention policies, such as the Time-To-Live (TTL) for check history, can be adjusted - A diagnostics page provides system information and logs for troubleshooting and monitoring the health of the Checkmate application itself ",
+ "simulationNodesAndEdges": {
+ "97908daf-d071-4df5-8366-1f5f4d8d534a": {
+ "simStepIds": [
+ "64e9f1af-13b7-4361-a2a9-3f45760e316d"
+ ]
+ },
+ "83596bd1-dfca-4196-b52e-955f6824fafb": {
+ "simStepIds": [
+ "441b0c45-5f27-478d-8ca8-58fbd5f460ea"
+ ]
+ },
+ "09cb194a-0ea4-46e2-b32b-36b62fa87f40": {
+ "simStepIds": [
+ "ae5c5146-4dbb-4612-9d23-086e57f72474"
+ ]
+ },
+ "1b2cf182-9ee3-4218-a7ec-b74e427ccddf": {
+ "simStepIds": [
+ "d7098072-9a83-4369-8176-9646ba34e186"
+ ]
+ },
+ "cce3b9c8-f3b7-434b-b3c6-d8d164714f86": {
+ "simStepIds": [
+ "7632e8d2-4086-4169-ae3f-bfbf8d657987"
+ ]
+ },
+ "fff5dde4-8eb8-4c21-9c6d-0845a9749ff3": {
+ "simStepIds": [
+ "a157bd07-b969-44f8-914c-3d8393c1605a"
+ ]
+ },
+ "704e2ec8-9f2f-47e5-852f-c6ada67a02e0": {
+ "simStepIds": [
+ "a9b24b5f-534b-4baf-a185-bac7c32a1c42"
+ ]
+ },
+ "09f07e80-185e-4547-aab4-edaf8ae7501e": {
+ "simStepIds": [
+ "8fb704ca-67d9-437c-90d8-01302634d568"
+ ]
+ },
+ "4b418736-6b72-47e0-917a-d86d4bed9ecd": {
+ "simStepIds": [
+ "e6c94f52-a599-4568-a06f-2db18f963817"
+ ]
+ },
+ "f6d80f09-ae83-4c95-aba0-4efffeda38e0": {
+ "simStepIds": [
+ "dca4a33c-b666-47b8-896b-370ed9d6f9c5"
+ ]
+ },
+ "195ad532-9858-493e-876a-6ec457811d1d": {
+ "simStepIds": [
+ "aacc9302-6ca8-45c6-acb8-956d8d0242c2"
+ ]
+ },
+ "8c107d79-f796-49eb-ab88-f553dd6f9d95": {
+ "simStepIds": [
+ "e7e82a97-1409-4bc5-ae27-b4ff5ba746df"
+ ]
+ },
+ "ce3ed3ba-a35b-4881-8dfd-f8425b9bf812": {
+ "simStepIds": [
+ "383387e5-4623-44ab-b2c4-d9ea046f4547"
+ ]
+ },
+ "fbe9b160-307e-4f51-8f72-713fda5373c8": {
+ "simStepIds": [
+ "0afcde35-7d51-4939-aae8-bac5ac83c49d"
+ ]
+ },
+ "499dfe1f-77d4-4b26-a12c-45240691109e": {
+ "simStepIds": [
+ "23b8ad7d-8967-4e48-814c-1a7bf5b4dfac"
+ ]
+ },
+ "d0f92604-a6fa-4d85-9716-ea8f32db78e0": {
+ "simStepIds": [
+ "44a4372f-a903-4c43-a9a5-c7549c875e11"
+ ]
+ }
+ },
+ "isAIGenerated": true,
+ "keywords": "Settings, updateAppSettings, diagnostic",
+ "generationPrompt": "System Administration and Configuration",
+ "generationKeywords": "Settings, updateAppSettings, diagnostic"
+ }
+ },
+ "cellToPath": {
+ "0b23ea66-3e37-4643-b3b9-acb87ea02bae": "client",
+ "98af25c7-efc2-43d2-a273-c8da4e1038ad": "server",
+ "d3264577-10f0-42fe-9411-7e9df0754d1e": "client/src",
+ "35864a2c-7bd0-4428-84fc-c0aa90c7f1be": "server/src",
+ "f0feab85-316a-41b5-a2f1-629220018846": "client/src/Pages",
+ "4295a047-076f-4c07-95dd-b3123b6614f7": "client/src/Hooks",
+ "02b07589-ec21-4366-b7d2-9071e8f3bad8": "client/src/Utils",
+ "ffaaf471-ac3c-4ea3-a5a9-d5255169e54b": "server/src/routes",
+ "478e649c-1d1b-41d2-9b7b-6927e1dec7e1": "server/src/controllers",
+ "99e9b746-7809-41f4-a433-1de7cc90574b": "server/src/service",
+ "15ee5d81-a0cf-4614-80b8-7a1123ab20b8": "server/src/db",
+ "1a083b4a-787c-4eb4-8029-6700de987679": "client/src/Pages/v1",
+ "3bf0c0f9-cbb0-4f0c-8cb3-6e2a4abc0961": "client/src/Hooks/v1",
+ "16731e9d-3f73-4055-9d79-5ca9be679f68": "client/src/Utils/NetworkService.js",
+ "d3375a20-2776-4771-b806-649bbeb541bd": "server/src/routes/v1",
+ "fc591f7a-d4ac-4565-949d-a87a58a79bbc": "server/src/controllers/v1",
+ "bbbe53d6-f595-498a-ae45-e08611f39c3d": "server/src/service/v1",
+ "58c5a3fd-1e69-4ca4-a64e-314f5644bdaf": "server/src/db/v1",
+ "7090fcdb-09be-4c85-86fd-e73553bbb2ff": "client/src/Pages/v1/Uptime",
+ "595cf0e8-2f86-4543-b51c-b2aeac307f4b": "client/src/Hooks/v1/monitorHooks.js",
+ "10611636-ec73-4a26-87a5-654ae64a4843": "server/src/routes/v1/monitorRoute.js",
+ "341f2784-67ee-4477-9445-a8c240ee6261": "server/src/controllers/v1/monitorController.js",
+ "87d3b101-9443-4ed4-9c4f-f759d41677eb": "server/src/service/v1/business",
+ "1670815f-860d-4ea4-92e1-b238a799d3dc": "server/src/service/v1/infrastructure",
+ "82cb3dbe-c922-438a-a912-255376f4b380": "server/src/db/v1/modules",
+ "3dba5acc-cf25-40b7-ad21-9cd715454cd5": "client/src/Pages/v1/Uptime/Create",
+ "b4e479df-9b21-4430-9383-84831f503c2e": "server/src/service/v1/business/monitorService.js",
+ "dda70a36-2d1d-4e46-8bcc-52d0d474bf7b": "server/src/service/v1/infrastructure/SuperSimpleQueue",
+ "00406786-9663-440a-96b6-919d544732c0": "server/src/service/v1/infrastructure/networkService.js",
+ "fbd7d244-2f37-453c-8c83-a23a260b26cc": "server/src/service/v1/infrastructure/statusService.js",
+ "2ff1f030-2080-48da-bbbe-90302fdfc2f7": "server/src/service/v1/infrastructure/notificationService.js",
+ "630b3ece-2648-48d5-99b5-ef9611de2ad5": "server/src/db/v1/modules/monitorModule.js",
+ "391ad99e-3114-4f54-a6ca-fd89b33c7e06": "client/src/Pages/v1/Uptime/Create/index.jsx",
+ "d9c0127e-31aa-4509-b5c9-f7017d10d69a": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js",
+ "04c12e9a-e025-457c-a634-5f7472a42985": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js",
+ "7a2da928-a65c-49d8-b0a3-c159a6df2bc5": "client/src/Pages/v1/Uptime/Create/index.jsx-simstep-ab5de0b9-c70d-492c-98d9-8ddee66284ad",
+ "a81f4ae8-41fe-4d1b-95ce-86abcc67b53c": "client/src/Pages/v1/Uptime/Create/index.jsx-simstep-e02ddf57-270e-4f71-b4c4-d5183bb7879a",
+ "a30ac765-c546-4993-988e-8c8cfdb7532f": "client/src/Utils/NetworkService.js-simstep-14e9986b-c6ce-475e-97ed-da1fa2b6968d",
+ "f0409948-75dc-457f-a02d-6b9774258e48": "server/src/controllers/v1/monitorController.js-simstep-38fd069d-9c44-4078-954b-3a0899e9acf4",
+ "2d407bfd-e4b0-46b8-99e7-121c507d525f": "server/src/db/v1/modules/monitorModule.js-simstep-ca3ad309-323a-418d-a171-c16f56fae23c",
+ "51e8db5f-2fdd-4ddc-9605-43981db9d940": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js-simstep-2702bfca-7611-4562-8d3e-a05af8257871",
+ "18c13391-0cf7-4303-8180-0a40cfd64b58": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js-simstep-e22a708a-a348-4775-8ee3-f32a35446e24",
+ "5c900373-e6ba-44d5-b341-676a32d659c8": "server/src/service/v1/infrastructure/networkService.js-simstep-84f82fea-e306-4e0b-8222-e9705803d51a",
+ "45e38f3a-a7c3-4de7-b3dc-589361d793fd": "server/src/service/v1/infrastructure/statusService.js-simstep-c66a04c6-113e-4391-ab71-93fc612279ef",
+ "9f3ea7fc-16d3-4d36-97eb-66f8808425cc": "server/src/service/v1/infrastructure/notificationService.js-simstep-1a260a75-cf50-4f7a-90b0-c280b512a5cb",
+ "0e09aa33-d7c7-4c89-9da8-f0937658f4d3": "generated-edge-simstep-888df086-41bb-469d-9c0a-daf74bdecb9f-0e09aa33-d7c7-4c89-9da8-f0937658f4d3",
+ "1f6f04a9-0123-4729-b06a-8f3da6d1b58c": "generated-edge-simstep-3fe0afaa-c673-4b52-a0c4-02a3ffd3a075-1f6f04a9-0123-4729-b06a-8f3da6d1b58c",
+ "b6d963d5-c613-407a-9ce9-60a04048dea7": "generated-edge-simstep-13dd0564-44a5-43ea-a163-ce1148cc48e7-b6d963d5-c613-407a-9ce9-60a04048dea7",
+ "a6bccd1d-4272-4df7-a7c8-b3979e37b8d7": "generated-edge-simstep-a6f4c967-b1a8-4804-bc2e-67f47a7b68f9-a6bccd1d-4272-4df7-a7c8-b3979e37b8d7",
+ "de320ac1-0949-4297-be7c-b9d7a14e0fec": "generated-edge-simstep-71ec03b6-1166-4c5d-9ada-0875a0866e30-de320ac1-0949-4297-be7c-b9d7a14e0fec",
+ "38ba9b22-5b26-432d-a856-0eceb792c737": "generated-edge-simstep-1d7580f2-f107-4008-a207-b8ec07998721-38ba9b22-5b26-432d-a856-0eceb792c737",
+ "5e80734c-7b72-41be-9068-30cda3ffb425": "generated-edge-simstep-9b0ada30-d3e6-4b2c-b3ff-f50615139588-5e80734c-7b72-41be-9068-30cda3ffb425",
+ "0a7e4a72-b644-4bd8-afa1-ae8a1d6c111c": "generated-edge-simstep-9896c387-4fca-47ad-82eb-7301286decb7-0a7e4a72-b644-4bd8-afa1-ae8a1d6c111c",
+ "3f26b33e-477f-41df-a78a-3e3e4f4209ae": "client/src/Pages/v1/Infrastructure",
+ "74019e8a-3148-4a32-aae2-5afd16f3eb9b": "client/src/Pages/v1/Infrastructure/Create",
+ "591128f9-0994-4e01-a1ab-f23357f97aff": "client/src/Pages/v1/Infrastructure/Details",
+ "4940977f-933d-4bbc-b19e-749647b44d25": "server/src/service/v1/infrastructure/notificationUtils.js",
+ "a0b22a5a-59e1-47e3-8218-a4ad083b1b71": "server/src/db/v1/modules/monitorModuleQueries.js",
+ "ceded1fc-87a9-43c8-a756-746be3d4ad8d": "client/src/Pages/v1/Infrastructure/Create/Components",
+ "e292c552-b3df-4bac-bd9a-8f44c260a5aa": "client/src/Pages/v1/Infrastructure/Details/index.jsx",
+ "e97d4e61-483e-4ac5-85a1-0ff72fcdd449": "client/src/Pages/v1/Infrastructure/Details/Components",
+ "83800be3-2a0e-4419-b379-3f995f4050e7": "client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx",
+ "6a7c7720-a399-4d4d-8225-36e0b2f59731": "client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes",
+ "8fe3d890-3605-407d-b06c-1d890a354e46": "client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/index.jsx",
+ "9651d084-5c16-4417-89c1-f52ae6572a95": "client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx-simstep-80dd889a-1fc9-4d56-b9cf-5b45bf1110b1",
+ "4c47b5f2-5363-4816-9745-30e61d184c7a": "server/src/service/v1/infrastructure/networkService.js-simstep-ed6e7faf-69dd-4d0b-bda8-137739ed288b",
+ "c1bf10c9-8c1a-44f4-8e23-39708f0301e8": "server/src/service/v1/infrastructure/statusService.js-simstep-e5b90cf2-9369-4741-9803-c835a81c009b",
+ "d4f48f1a-61de-4cde-87d2-4b5215464899": "server/src/service/v1/infrastructure/notificationUtils.js-simstep-07fe8489-1d94-4afb-83f8-7cebb0dff49a",
+ "801fe70a-1a19-406c-8f0d-770b0cb74409": "client/src/Pages/v1/Infrastructure/Details/index.jsx-simstep-c27365db-9db6-4cc9-96f0-4f046fd38562",
+ "9ef7c423-f4fc-4caf-9bf5-c1aa3002ca03": "server/src/db/v1/modules/monitorModule.js-simstep-8502ae1f-f029-446e-9dde-59c2aba87a8a",
+ "9ea2e32d-1ea8-42f2-9a17-613844723ae6": "client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/index.jsx-simstep-6eedce82-b9c8-4022-95e5-3a6e00628105",
+ "1c5c9857-bf32-40e4-921b-957c252dac2a": "generated-edge-simstep-815f96af-c28e-4f80-b4ab-18a6fb23eee0-1c5c9857-bf32-40e4-921b-957c252dac2a",
+ "a64547c9-d9e6-4941-8e5e-f49add06516b": "generated-edge-simstep-4434dca1-2048-4458-a70b-b70d569e378d-a64547c9-d9e6-4941-8e5e-f49add06516b",
+ "7a250d09-50c6-4935-b10c-626b0ebe1fba": "generated-edge-simstep-b019a6d0-a23a-4a1d-bd21-644f01996f14-7a250d09-50c6-4935-b10c-626b0ebe1fba",
+ "3d357de8-9fd2-4d0b-9623-f36fce4cf1de": "generated-edge-simstep-c691b3b9-88b3-4c88-a399-ff9fd5866194-3d357de8-9fd2-4d0b-9623-f36fce4cf1de",
+ "00921831-b1d9-4a5a-ae6f-5ddee7cfc1e4": "generated-edge-simstep-a5d20172-1c95-4c9e-af39-f86a498734bb-00921831-b1d9-4a5a-ae6f-5ddee7cfc1e4",
+ "60308696-6f8a-43c8-b9a1-da199ef500cd": "generated-edge-simstep-627fcb0c-0282-4b4d-8c55-aceb636e4da0-60308696-6f8a-43c8-b9a1-da199ef500cd",
+ "feefae96-61b8-4ac5-a37c-592ed4b2330e": "server/src/service/v2",
+ "b3575a92-df07-4cf7-810f-8e5e0672d666": "client/src/Pages/v1/Notifications",
+ "cd2428fd-6545-48dd-b685-5261311b7532": "client/src/Hooks/v1/useNotifications.js",
+ "96250197-23af-4059-9904-fd6751f70ad5": "server/src/routes/v1/notificationRoute.js",
+ "8e5941e1-e577-455f-bbf2-83a418ad5908": "server/src/controllers/v1/notificationController.js",
+ "3f8ff792-40c8-4ae6-9af5-ff8b90da2f9f": "server/src/service/v2/infrastructure",
+ "a6fb23f0-73bc-40ee-bb3c-ed93605a2bcb": "client/src/Pages/v1/Notifications/create",
+ "62daeac4-9402-448a-972c-cb8cb760671c": "server/src/db/v1/modules/notificationModule.js",
+ "159e569d-31d8-4883-b443-4ec08b21a044": "server/src/service/v2/infrastructure/JobGenerator.ts",
+ "c6d9907f-0a05-4153-a27f-47c3a1c4c0aa": "server/src/service/v2/infrastructure/NotificationService.ts",
+ "b71b02fb-d08e-4fcf-b959-de1b9547d9c2": "client/src/Pages/v1/Notifications/create/index.jsx",
+ "15486629-84b3-463a-962b-06083f69e4bf": "client/src/Pages/v1/Notifications/create/index.jsx-simstep-e924ec15-1bea-4e3b-befb-a8bf30cbf740",
+ "a07b805e-e55f-46cb-b54b-765b7813b0ca": "client/src/Hooks/v1/useNotifications.js-simstep-383c6be8-aa51-4398-9ae3-21035413f73a",
+ "d0a4737f-715f-4f50-b6d5-5e4b31f86f93": "server/src/routes/v1/notificationRoute.js-simstep-2f9266c9-4cbc-4b50-a280-cf8b5800d540",
+ "4a796ccf-40a9-4446-a109-6a6eed52e898": "server/src/db/v1/modules/notificationModule.js-simstep-d29ab4ab-be50-4f17-af6e-2c61cec9d0ab",
+ "29c2c56e-9dc0-4304-bfbf-e0c0f8915a39": "server/src/routes/v1/notificationRoute.js-simstep-c1b17b64-90e9-48bc-8927-25d7ebfc3036",
+ "1cc01e08-4a6c-4526-b8a7-5ee8a06c1709": "server/src/service/v1/infrastructure/notificationService.js-simstep-f29f9be1-a0cd-43a3-b259-e5105a25ce41",
+ "28f1eabb-4f24-4944-8a0a-04f41ceb8589": "server/src/service/v2/infrastructure/JobGenerator.ts-simstep-157cec87-9b76-4a1c-9d85-0a6d3decbaa3",
+ "31fe5a28-0640-4b52-841b-dd02e2ae646e": "server/src/service/v2/infrastructure/JobGenerator.ts-simstep-55ad645b-118a-478e-a00c-09ef82357008",
+ "78e5cb9f-6a97-4d31-a487-fe7dbce5d7d1": "server/src/service/v2/infrastructure/NotificationService.ts-simstep-8d1176f3-c755-401d-9ca8-234689b4b837",
+ "54497b9a-4910-4df4-b016-480b9ac250a7": "generated-edge-simstep-71dbcd41-c6d4-4514-b5a4-6c966ca7ba6f-54497b9a-4910-4df4-b016-480b9ac250a7",
+ "dc135d8d-0515-403d-ae58-f3c04647b2ae": "generated-edge-simstep-162d8d2f-6cd1-45fa-8711-1ee85c5f86ef-dc135d8d-0515-403d-ae58-f3c04647b2ae",
+ "7b9ebb0e-a6b2-438c-bfed-9c851a1bccbc": "generated-edge-simstep-73e63d2f-2b43-488d-9e86-170f511673f5-7b9ebb0e-a6b2-438c-bfed-9c851a1bccbc",
+ "279155cd-ff31-41a2-8f5f-a538f01bfa66": "generated-edge-simstep-d390ef45-f2fe-4806-a45a-cc9c84b48a5a-279155cd-ff31-41a2-8f5f-a538f01bfa66",
+ "57018b30-e2f4-4eb2-a1da-fe75afbb8c2f": "generated-edge-simstep-6f2bf027-50d9-472e-9569-808a64fce7cf-57018b30-e2f4-4eb2-a1da-fe75afbb8c2f",
+ "50797935-1447-47a4-891b-9e886e5601fa": "generated-edge-simstep-b7ec7415-fcae-4077-8c19-302a015b00b5-50797935-1447-47a4-891b-9e886e5601fa",
+ "c778c6b9-c12e-45c0-8682-2c9e892867b0": "generated-edge-simstep-b7259d77-c8df-4060-ac03-58728ae3eb54-c778c6b9-c12e-45c0-8682-2c9e892867b0",
+ "987f145b-5df6-4c30-bf1c-2a2d2b0e5cdc": "client/src/Routes",
+ "c4d7e7dd-75b1-44f1-a34a-6ed290a8e562": "client/src/Routes/index.jsx",
+ "9d662180-8b65-4426-ad37-cfed4bc01fb4": "client/src/Pages/v1/StatusPage",
+ "773e1065-b0a5-46a9-b0cc-6b963f69f5cc": "server/src/routes/v1/statusPageRoute.js",
+ "17a4f435-5a8e-4433-84a6-9ac17362709a": "server/src/controllers/v1/statusPageController.js",
+ "08602a28-f073-484e-9403-7eb798126dd4": "client/src/Pages/v1/StatusPage/Create",
+ "8ef70566-37d9-48fb-b5e8-37e5b68768a2": "client/src/Pages/v1/StatusPage/Status",
+ "f8541fe3-4bcf-4833-a226-66b5301adf58": "server/src/db/v1/modules/statusPageModule.js",
+ "e2d2bbf6-dffd-4edf-8c5d-34088d9d4745": "client/src/Pages/v1/StatusPage/Create/index.jsx",
+ "56f6775e-9843-4ca2-bc8c-1c263b6c35e9": "client/src/Pages/v1/StatusPage/Create/Hooks",
+ "e1e6f5e0-fd8c-4260-aa4f-cdb096c6b1c3": "client/src/Pages/v1/StatusPage/Status/index.jsx",
+ "79da0721-d810-4f7f-9cf9-3ea462bf0bff": "client/src/Pages/v1/StatusPage/Status/Hooks",
+ "3945e05f-2d99-4d6d-9a3e-b6c75ea3c45a": "client/src/Pages/v1/StatusPage/Create/Hooks/useCreateStatusPage.jsx",
+ "5b2cdab8-66f1-40a1-9621-0efaf54e04ae": "client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageFetch.jsx",
+ "00289d11-0015-4b2a-9c5b-dc9fd0f32b80": "client/src/Pages/v1/StatusPage/Create/index.jsx-simstep-de7b5d1f-b370-42a4-a410-4178f1e2d3a6",
+ "aa5d6d07-05f0-45f3-b19a-199af50e35f1": "client/src/Pages/v1/StatusPage/Create/Hooks/useCreateStatusPage.jsx-simstep-3c99ae6a-b671-4f47-bed0-ddccd8ff5d35",
+ "3efddcad-588f-4492-9808-b0c81c823c96": "server/src/routes/v1/statusPageRoute.js-simstep-84f11567-b3c4-42a2-aa7f-d78c1412099a",
+ "f15ef77e-380f-4768-98cb-7f0dd4113841": "server/src/controllers/v1/statusPageController.js-simstep-01694730-8a75-4949-8815-bb4d6e3e2b42",
+ "e234917e-a950-48f5-b864-cd957b2216e1": "server/src/db/v1/modules/statusPageModule.js-simstep-b6fa93cd-6fca-4d9e-969a-5400e2437ee8",
+ "058ded1f-83ce-40a7-a99d-d178c81fe557": "server/src/controllers/v1/statusPageController.js-simstep-2a115421-5175-4c8f-8cdd-62f3fe148af5",
+ "30aaee38-f509-4e19-a917-e6d2f4bba61c": "client/src/Routes/index.jsx-simstep-132354d7-df7a-4e03-9d00-16d54f836a1b",
+ "ecfb2d04-2681-4f16-af75-632f69e1a868": "client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageFetch.jsx-simstep-b57bb249-9066-454e-aa8c-89108fdfcc2e",
+ "2e26cd2f-1413-4c16-8e63-ea7808ab6c52": "server/src/routes/v1/statusPageRoute.js-simstep-477c6821-b29b-4b38-8930-ced21b8a75e5",
+ "9f217674-4cd7-4ef8-b088-d5620042a7ba": "server/src/controllers/v1/statusPageController.js-simstep-5bdbd179-ce8f-4171-a073-af72a99b4468",
+ "26da60f9-dde4-44ec-be89-91858cc14f90": "server/src/db/v1/modules/statusPageModule.js-simstep-ceb044e9-6545-4d04-bf79-ec8146eb8ec9",
+ "d76e70e0-41f9-46cc-b775-b2027c5a0d5b": "server/src/controllers/v1/statusPageController.js-simstep-83224406-2f1d-4030-9beb-d65c2baf544d",
+ "f59b91de-5196-4a43-9d57-3f28402c0672": "client/src/Pages/v1/StatusPage/Status/index.jsx-simstep-3d51e811-a659-442e-97e0-00798cd15647",
+ "de23eee2-b60a-4236-a890-3c5b78388bb3": "generated-edge-simstep-d419f7b6-7b9b-4494-9fad-83e3ce208ca4-de23eee2-b60a-4236-a890-3c5b78388bb3",
+ "d7faa713-4204-4a97-a172-d621da36c287": "generated-edge-simstep-8a2ab372-73db-4b9f-bc0e-6a20f15a4d79-d7faa713-4204-4a97-a172-d621da36c287",
+ "a3f28fa6-8268-4c13-95a0-83e3b1abdcb0": "generated-edge-simstep-0923a533-c572-4da3-ad87-cdfff111f3bb-a3f28fa6-8268-4c13-95a0-83e3b1abdcb0",
+ "9735611c-924b-49aa-92e8-10d8a86ac288": "generated-edge-simstep-67603f03-c088-4ed0-9f18-072435134cdd-9735611c-924b-49aa-92e8-10d8a86ac288",
+ "40cb7d7b-2888-44de-bb7d-7735af70ba34": "generated-edge-simstep-ea4b3532-baae-4890-a73e-2ca385d0319e-40cb7d7b-2888-44de-bb7d-7735af70ba34",
+ "c3f83811-8674-4dbb-9f84-79499f97fb1f": "generated-edge-simstep-210acc07-81c3-4394-b566-41f6031e8eeb-c3f83811-8674-4dbb-9f84-79499f97fb1f",
+ "0575bf87-5362-4988-b895-7e514148d420": "generated-edge-simstep-2ba49e28-1cac-47f5-9118-ab930788736a-0575bf87-5362-4988-b895-7e514148d420",
+ "ae1c9341-0e39-4d91-a9ac-5cc8a627c247": "generated-edge-simstep-de0714d2-bf1e-4a1a-a389-60793de8b772-ae1c9341-0e39-4d91-a9ac-5cc8a627c247",
+ "5735f30c-b55f-444a-9236-f3d75f68ed27": "generated-edge-simstep-b79b8922-4de6-477d-8648-37b4bd9a6767-5735f30c-b55f-444a-9236-f3d75f68ed27",
+ "dace45d6-fb25-4dae-b940-49ef7d77bbed": "generated-edge-simstep-788307ef-4cfa-4f5a-b2e9-a0bfc7a378d8-dace45d6-fb25-4dae-b940-49ef7d77bbed",
+ "137a5ab7-1e49-42ec-869d-58f7f2380cb3": "generated-edge-simstep-4d3fad92-8fa9-4a8f-be3b-f09eda2f8dd4-137a5ab7-1e49-42ec-869d-58f7f2380cb3",
+ "04a33e4f-1dfe-4c5a-8b30-01ed0591c380": "generated-edge-simstep-d5f3c300-9777-4e09-bb05-f1281c2ff29a-04a33e4f-1dfe-4c5a-8b30-01ed0591c380",
+ "933a6cdc-349c-4725-b7ae-14cf780bfde8": "client/src/Hooks/v1/checkHooks.js",
+ "d18dbe1d-2f2c-4336-bd21-47c1670f7bab": "server/src/routes/v1/checkRoute.js",
+ "d983e54f-2fbe-496a-932d-a827e1124804": "server/src/controllers/v1/checkController.js",
+ "273f3009-69d0-46fe-af57-3cd94a24b55b": "client/src/Pages/v1/Uptime/Details",
+ "f90db867-e1e7-4554-b446-fa78fc6a8578": "server/src/service/v1/business/checkService.js",
+ "860b5eec-a1ce-4e1f-a316-e1586f03af96": "server/src/db/v1/modules/checkModule.js",
+ "1807d5b9-54be-430b-8997-cada4dd9b521": "client/src/Pages/v1/Uptime/Details/index.jsx",
+ "6aed78bb-62f1-4ac1-9b9a-9d5977e87404": "client/src/Pages/v1/Uptime/Details/Components",
+ "5ac0878f-d238-4763-8464-a85b4885a40f": "client/src/Pages/v1/Uptime/Details/Components/ResponseTable",
+ "d3f74ac7-3b9a-4cae-8d77-a0d5d605bb27": "client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx",
+ "78a8a7c5-41d3-4453-a7f7-ac632bbef288": "client/src/Pages/v1/Uptime/Details/index.jsx-simstep-35195328-9847-4678-b014-9331553f0d19",
+ "4a5847d0-91a0-4b4c-8517-2f4ff9a7e8d4": "client/src/Hooks/v1/checkHooks.js-simstep-8dc4347c-3e9e-40d7-ae6f-54684f2e6956",
+ "cdd090be-0b62-45b4-baa0-fec01fc3ffd1": "server/src/routes/v1/checkRoute.js-simstep-c5fd192f-ea28-48a0-b3e7-5bc04f521192",
+ "499b46a2-f2a2-4ccd-aecd-012eade2bd23": "server/src/service/v1/business/checkService.js-simstep-83fea05d-8306-4a73-a43f-882cdd14f50f",
+ "14df9f01-1358-442d-96f5-eab9a4c4abbf": "server/src/controllers/v1/checkController.js-simstep-5768fdc5-045c-48ef-b0c9-79769403648f",
+ "ceb9cda3-968d-44aa-b223-49902adb93b4": "client/src/Hooks/v1/checkHooks.js-simstep-faba07f3-7460-47ae-924d-f92f5b2e821d",
+ "c996f7ca-9384-4252-b343-7bfc049d14bb": "client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx-simstep-2b1651cd-2e70-4cc6-b2bb-23f94fa73d66",
+ "e90e4943-6108-46e3-8a2c-a9a1c7361279": "generated-edge-simstep-49979250-1a32-4da5-95e0-58652fe218f9-e90e4943-6108-46e3-8a2c-a9a1c7361279",
+ "2b8ae59e-ed8d-4837-9a9b-64eb480f982c": "generated-edge-simstep-336b534d-8d6a-4639-af8e-889b38f51ab1-2b8ae59e-ed8d-4837-9a9b-64eb480f982c",
+ "839ba9f6-8a34-4919-a04a-13fba1795c61": "generated-edge-simstep-4f73f0fe-b390-4fe1-a354-eb0857835126-839ba9f6-8a34-4919-a04a-13fba1795c61",
+ "42d3bc70-701a-4be0-96b4-d6c2f0519b49": "generated-edge-simstep-65da1a9e-041c-4656-9c5b-0a92b761a9be-42d3bc70-701a-4be0-96b4-d6c2f0519b49",
+ "6c5b028c-356c-43c8-a35a-b47448cd8695": "generated-edge-simstep-2c1bdec8-d0e6-4163-a99c-f27899b987c5-6c5b028c-356c-43c8-a35a-b47448cd8695",
+ "74b0dd89-9fea-4219-af1f-9571316a9d5a": "generated-edge-simstep-9405dce1-0565-4dfa-9539-a048472acda9-74b0dd89-9fea-4219-af1f-9571316a9d5a",
+ "e2f99143-54b7-44dc-b566-1c2fb901bd16": "server/src/db/v2",
+ "043d3dac-d4bb-449b-a3a7-270d53b66dac": "client/src/Pages/v1/PageSpeed",
+ "7ef79284-534a-466f-a1f1-4e3a2f56be9d": "server/src/service/v2/business",
+ "b53384d7-b989-4ac7-bd68-1c3621174ef4": "server/src/db/v2/models",
+ "414c500b-7b68-42ba-8110-e855efbee750": "client/src/Pages/v1/PageSpeed/Create",
+ "581b4919-8b0c-4f0d-ae67-ad6de11d8c8b": "client/src/Pages/v1/PageSpeed/Details",
+ "50aeed0b-bc13-43f7-bb15-cb1b12b2caa7": "server/src/service/v2/infrastructure/NetworkService.ts",
+ "f0ebd5da-21bb-4d96-8ff9-ca10409acc80": "server/src/service/v2/business/CheckService.ts",
+ "7a3824cd-24c8-4b32-8b1d-e0bf357262c4": "server/src/service/v2/business/MonitorService.ts",
+ "64e3bb0a-67f5-4269-9e7d-94fe91ac78fc": "server/src/db/v2/models/checks",
+ "5ffe0baa-054a-4e5b-8b29-1254f07ea975": "client/src/Pages/v1/PageSpeed/Create/index.jsx",
+ "c4a7bb58-1d9c-4690-b6b8-460c1cc33702": "client/src/Pages/v1/PageSpeed/Details/index.jsx",
+ "1425ec43-f0f4-4c8b-a140-176c079219d6": "server/src/db/v2/models/checks/Check.ts",
+ "0622b212-3a4a-484d-9b7a-125864d785cd": "client/src/Pages/v1/PageSpeed/Create/index.jsx-simstep-b0cc6e6a-961e-4ea9-aa8a-6bfa44d6ba12",
+ "6527fb47-157e-4bba-9aed-adde061c5689": "server/src/service/v2/infrastructure/NetworkService.ts-simstep-53968267-0c6e-42f4-88b3-cd90f44532ae",
+ "f89cbd87-c961-4f8b-92b6-714da57718d5": "server/src/service/v2/business/CheckService.ts-simstep-01206383-2db8-4b18-84d0-be2a0289c196",
+ "ca6ee27b-1fbf-49de-978a-d6de259cc3d5": "server/src/service/v2/business/MonitorService.ts-simstep-5616ac6d-c281-4879-82f1-6590c721c8f7",
+ "9ef9e218-4742-487a-9a88-5c70e788ccdc": "client/src/Pages/v1/PageSpeed/Details/index.jsx-simstep-0ec0eb06-05eb-4204-b077-9e47eb2cc316",
+ "b8e83b0a-69ff-4c34-a7fe-cb8c9ba85438": "generated-edge-simstep-f8df1cad-6051-466e-a13e-47d710ba1c14-b8e83b0a-69ff-4c34-a7fe-cb8c9ba85438",
+ "0d3c8091-3d91-4e42-9b46-7e35ed726a7a": "generated-edge-simstep-0b380571-3896-4365-b5dc-187494f02e0d-0d3c8091-3d91-4e42-9b46-7e35ed726a7a",
+ "27c7191b-80a2-475b-9371-e106108aff1c": "generated-edge-simstep-a629a102-0df1-4798-a7a2-45c8f6cdad29-27c7191b-80a2-475b-9371-e106108aff1c",
+ "7be432cd-2587-4eab-abdc-f7b9c1923597": "generated-edge-simstep-4ed43eaf-fa8f-4fce-af87-8a5f74dd5c08-7be432cd-2587-4eab-abdc-f7b9c1923597",
+ "fe47576c-c186-4950-82a5-b2fd24eb2561": "server/src/config",
+ "cb53d612-9b89-47da-911c-165d0382a7d8": "server/src/config/routes.js",
+ "8021b55d-2d3a-4416-b598-e134e76c9434": "client/src/Pages/v1/Maintenance",
+ "5f21f109-1967-416f-a649-9b48e78b90f6": "server/src/routes/v1/maintenanceWindowRoute.js",
+ "a0cae18c-3c0e-40d3-ab03-eef7eace65d6": "server/src/controllers/v1/maintenanceWindowController.js",
+ "6f7119ac-d65a-4fdf-a6f2-31052e0f5549": "client/src/Pages/v1/Maintenance/CreateMaintenance",
+ "1eae6263-aaa2-4206-b92e-1e67f529c1c9": "client/src/Pages/v1/Maintenance/index.jsx",
+ "5ad4d33f-0932-479c-bb57-22ce6405758f": "client/src/Pages/v1/Maintenance/MaintenanceTable",
+ "26b8daff-aa48-4baa-9d6c-05f771bf0990": "server/src/service/v1/business/maintenanceWindowService.js",
+ "95ed6c41-ebeb-4c08-98c3-bab8b426789e": "server/src/db/v1/modules/maintenanceWindowModule.js",
+ "92c244d2-e6a7-4804-a643-1e8d36baf9c6": "client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx",
+ "015a17fc-3fcf-45e0-8936-19f5e2ca86a3": "client/src/Pages/v1/Maintenance/CreateMaintenance/hooks",
+ "e7ec5ca9-6d25-4bf2-9695-639556f7d141": "client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx",
+ "6ad0396e-9b38-4f5d-9ad9-6c9fcb4434f4": "client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx",
+ "20335f29-a2ac-4e0c-aba6-28707748ecd4": "client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx-simstep-2401ef7a-8d8e-4056-868a-547ab213daf1",
+ "5bdfdfa4-5d93-49fa-b05c-905813e6cf9d": "client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx-simstep-5e0d7085-099b-478a-afaa-47ad4dd07043",
+ "a391e8ea-9dbe-4fe4-90df-bf475d8b7ef8": "server/src/config/routes.js-simstep-3e93ef65-8e2c-405b-8dc1-56d8e93937b3",
+ "0fae6e75-38ea-444d-a1bf-0f820ba443b9": "server/src/controllers/v1/maintenanceWindowController.js-simstep-95fc1919-339d-4659-98cb-3dc50f662075",
+ "560d268b-ef0f-4cd2-bcfe-9dc0a0e2ce6f": "server/src/service/v1/business/maintenanceWindowService.js-simstep-6e2cf739-1514-4c37-9874-10b68a47010e",
+ "3890b98d-a133-4279-a961-5d2facc6a17a": "server/src/db/v1/modules/maintenanceWindowModule.js-simstep-a3c47fd4-c701-46c2-9aff-94a667b3e368",
+ "a5a84b08-8c70-4023-8d08-b74e23aab4d7": "client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx-simstep-ebe8fd52-f2a1-4a98-a71b-b04d6f4cdadc",
+ "d145bb4c-e1e0-473a-b6bc-decc932b738b": "client/src/Pages/v1/Maintenance/index.jsx-simstep-82b5ea14-bb00-4511-aba4-76196029fd81",
+ "c35a065c-2808-402c-877e-9055d956ce0d": "server/src/controllers/v1/maintenanceWindowController.js-simstep-1251da83-5949-4e24-a1d2-fc74ff785aa9",
+ "27d9160a-4f8d-4dff-a09b-1678bc28cf2c": "server/src/db/v1/modules/maintenanceWindowModule.js-simstep-9bfbae5a-f487-40ef-86f5-04a1cc3c7662",
+ "970e2002-7c0e-40ef-80d8-e5e6d0a60735": "client/src/Pages/v1/Maintenance/MaintenanceTable/index.jsx-simstep-acf97639-e291-4ca6-81a6-d0487397f42a",
+ "23630a1f-7452-4373-a864-7ec7821137f2": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js-simstep-a47affaf-b952-46d6-9709-6120a77690e1",
+ "fc78b5b6-7fad-4f8f-a5a3-c7c38931daba": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js-simstep-2127e749-80b4-499a-ae14-8dc2dcc9db9b",
+ "0fe32416-db43-49b9-9438-3796ba1a0add": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js-simstep-6ba6b9d1-5fc3-454e-8744-2022b5192ed9",
+ "01e042c3-40b4-416a-8d9e-6d6fd323d60b": "server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js-simstep-e59fe64e-f06b-4855-b631-923bb5a68540",
+ "d3bdc6ff-875e-472e-88c8-eb10b20c526e": "generated-edge-simstep-f881435b-3bc2-453d-b6bf-56ad3a12f120-d3bdc6ff-875e-472e-88c8-eb10b20c526e",
+ "fd5e4447-123b-4b2d-8bce-1d1ae7cb00af": "generated-edge-simstep-0660b48b-139f-4ecf-b7d9-f12f544e0ab5-fd5e4447-123b-4b2d-8bce-1d1ae7cb00af",
+ "e2588541-3ffc-47c6-8f8a-41d30ea7fc72": "generated-edge-simstep-e4cc5ce8-6da9-4281-9204-29c061ab13ce-e2588541-3ffc-47c6-8f8a-41d30ea7fc72",
+ "e1d59cf6-0d96-4e0a-ad17-2a7984a9157f": "generated-edge-simstep-c3d1a95c-e93d-499a-8830-a6d870902414-e1d59cf6-0d96-4e0a-ad17-2a7984a9157f",
+ "12d0ab8a-392f-48fa-9efa-06ffe1f3a252": "generated-edge-simstep-80aed28e-68b6-47bf-8bc9-ededb4824066-12d0ab8a-392f-48fa-9efa-06ffe1f3a252",
+ "fcf4eef4-394c-4872-9124-f7a0b32cdc3b": "generated-edge-simstep-69fdc2a9-837f-466c-9562-a12319019580-fcf4eef4-394c-4872-9124-f7a0b32cdc3b",
+ "7f921125-500f-4902-abc3-936ecac033d8": "generated-edge-simstep-7e20cd6c-d24e-4540-86d6-d6a9fa087314-7f921125-500f-4902-abc3-936ecac033d8",
+ "c3127c74-43f1-42d7-8da6-b0a494c3658f": "generated-edge-simstep-feb8bd4f-339f-4132-a709-89c63c4efa9c-c3127c74-43f1-42d7-8da6-b0a494c3658f",
+ "6c9979cd-1cc1-4227-b535-2ec3a10ca27e": "generated-edge-simstep-eda86dca-dea6-4671-a079-7d212f4f18c4-6c9979cd-1cc1-4227-b535-2ec3a10ca27e",
+ "c99291bb-7316-440e-9a7f-323ee39e26b8": "generated-edge-simstep-87583096-f1ec-4160-ae52-5238f32ed0ab-c99291bb-7316-440e-9a7f-323ee39e26b8",
+ "8a13e585-2310-420b-9e92-211b6d65f201": "generated-edge-simstep-e6542e14-556d-482a-8c35-f7b03997be49-8a13e585-2310-420b-9e92-211b6d65f201",
+ "40f4d9df-4c0f-4384-901c-8d7117e3b538": "generated-edge-simstep-a715d98f-60bf-4f32-aae6-21ee4e09f4da-40f4d9df-4c0f-4384-901c-8d7117e3b538",
+ "a968db5d-8f6d-4fd7-9df0-8b332b521703": "server/src/middleware",
+ "09082f73-e215-465f-9206-0a963fb4c7cb": "server/src/middleware/v1",
+ "3cad7666-0d1a-4e31-b6ca-70d9c3a46b19": "client/src/Pages/v1/Account",
+ "9edafb8a-3661-4a7e-8510-a47f3cbc7564": "server/src/routes/v1/inviteRoute.js",
+ "001fa2e4-eadd-4474-b2ec-79d7ea43465f": "server/src/middleware/v1/isAllowed.js",
+ "6a186805-9d50-4bc3-9d56-e627a7ec2eb8": "server/src/controllers/v1/inviteController.js",
+ "33960806-1b9e-4d61-a163-e4bebf82d87a": "client/src/Pages/v1/Account/index.jsx",
+ "425a4c15-043f-4d05-8832-da00e18f97b9": "client/src/Pages/v1/Account/components",
+ "570278b7-9b72-4299-9fd9-46b253b48e9b": "server/src/service/v1/business/inviteService.js",
+ "08853f8a-f596-4df0-9190-dad04f54caef": "server/src/db/v1/modules/inviteModule.js",
+ "1145f351-2257-411d-86f7-30bf8c26ad68": "client/src/Pages/v1/Account/components/TeamPanel.jsx",
+ "3de2d413-5e9b-49f4-9409-74be32c52e0f": "client/src/Pages/v1/Account/index.jsx-simstep-2d5a952a-ce62-4948-9309-96b28ad74e59",
+ "b27f1bba-c655-40c6-a1dc-71e78c590c92": "client/src/Pages/v1/Account/components/TeamPanel.jsx-simstep-eebaca25-d588-49c4-a35c-9abc82fde580",
+ "8b3cb6c2-ed57-4510-be15-decb8e3c89e1": "server/src/middleware/v1/isAllowed.js-simstep-50dd7316-7d01-43f7-9036-d97d51a9c178",
+ "ae858158-8db6-4917-8fda-0966a953f6ed": "server/src/service/v1/business/inviteService.js-simstep-55fe9963-30d2-47f8-859f-b485a77064c4",
+ "a7be2d0b-0227-4228-a0a6-074f7324fd89": "server/src/service/v1/business/inviteService.js-simstep-7d1f86f2-c8d5-48fb-a8c1-b337dc56271f",
+ "02d87257-883c-4822-a421-823c983532ec": "client/src/Pages/v1/Account/components/TeamPanel.jsx-simstep-700f35e0-b39d-417c-b0a8-0612feade977",
+ "c441312f-f3c6-48c6-b1b9-7c12749d91bb": "generated-edge-simstep-10f80ada-2d0a-4e98-bd5d-057e7d4fe6de-c441312f-f3c6-48c6-b1b9-7c12749d91bb",
+ "3530244f-2689-444e-bcbf-611db2e3c000": "generated-edge-simstep-16d87608-4458-430c-b459-cd9b1d413fa6-3530244f-2689-444e-bcbf-611db2e3c000",
+ "2ee1c4e8-90fb-4470-ad9f-6ffed5420853": "generated-edge-simstep-6c024d4c-178b-45fc-8bca-4fc6c00549a4-2ee1c4e8-90fb-4470-ad9f-6ffed5420853",
+ "bdd52e6f-ddf7-4966-b3af-957f9553082f": "generated-edge-simstep-c796f8ad-d548-45f3-8d62-67f59c6c1aa4-bdd52e6f-ddf7-4966-b3af-957f9553082f",
+ "52732a37-82b8-40a7-b12d-dc1b82380b2d": "generated-edge-simstep-6ba5094d-d1e6-4ec3-8917-28cdb30f2ef9-52732a37-82b8-40a7-b12d-dc1b82380b2d",
+ "56dae13e-5735-4089-a9a4-e18be315aff7": "client/src/Pages/v1/Uptime/BulkImport",
+ "29b6ad3c-9538-47bc-8796-aab942dc4b71": "client/src/Pages/v1/Uptime/BulkImport/index.jsx",
+ "cd84476a-1c8b-4781-811f-58fb9944365b": "client/src/Pages/v1/Uptime/BulkImport/Upload.jsx",
+ "c4caeba8-ed9a-4cbc-9888-38cc8ca55362": "client/src/Routes/index.jsx-simstep-a29da01e-8c5a-4e6d-8d3c-e49af528e4fa",
+ "6f1948fa-e84a-4f8e-8f3b-8f24c511b3d8": "client/src/Pages/v1/Uptime/BulkImport/Upload.jsx-simstep-bbd04d55-fc6a-4ba8-bcde-5a4dc263d6eb",
+ "dd2f9356-fad3-47d4-81d5-a5e038298e2b": "client/src/Pages/v1/Uptime/BulkImport/index.jsx-simstep-0139fcaa-eb46-4176-b7bd-1cb15e7f1f4c",
+ "5967e576-f542-4c0b-a3f4-9446ae992721": "client/src/Utils/NetworkService.js-simstep-2fc087b3-033d-4cf5-81f2-e35b99a14bce",
+ "c1ac3c26-996a-43ef-9c34-4c2776188784": "server/src/routes/v1/monitorRoute.js-simstep-07bf3d83-047d-4e51-9f96-8c9c64848500",
+ "0e520a6d-34bf-467b-a894-e73979364478": "server/src/controllers/v1/monitorController.js-simstep-a3799570-64bf-481e-9ffe-31dbad8a36e4",
+ "6139e487-1dd5-405a-ba7d-ededec4b282e": "server/src/service/v1/business/monitorService.js-simstep-2c7c3082-e4b6-4525-860f-2ffcd9e86db1",
+ "d9d519af-5c4d-4494-929e-a869581c1dc4": "server/src/db/v1/modules/monitorModule.js-simstep-70f18c21-2918-402e-9c35-1bdce8bc82d4",
+ "1e1d975f-7f0f-4132-bd71-2563b35e99b3": "server/src/service/v1/business/monitorService.js-simstep-66fbf913-e662-4bd9-ada6-077be9e37f07",
+ "72b55fa1-d944-4e46-ac98-f6a6cb201afa": "server/src/controllers/v1/monitorController.js-simstep-f5377571-156f-4c5c-8bab-b6dacdf0fea6",
+ "ae3b7e23-ed64-40aa-9760-389f0a869f8d": "client/src/Pages/v1/Uptime/BulkImport/index.jsx-simstep-72b3f16d-290d-4a61-8748-2ad90d63ec68",
+ "26d76516-fd7e-4ae8-956a-d077de4257fa": "server/src/routes/v1/monitorRoute.js-simstep-63d8d81f-f436-4594-a6eb-8c0fd2110bc1",
+ "a3106388-6a3d-4b68-a3fc-3761a0d54f98": "server/src/controllers/v1/monitorController.js-simstep-d9db3f40-8add-4551-9323-274348b9800f",
+ "ab9cb862-0ac7-45a3-9888-f64e87bdda54": "server/src/service/v1/business/monitorService.js-simstep-85cd1d5d-7ffb-49c5-aacb-907048003fc5",
+ "f151f89d-6399-48f0-aecd-5eb1572b48cc": "server/src/controllers/v1/monitorController.js-simstep-256095b8-5e3a-4c20-abd9-be9e6a9d0bad",
+ "d8877474-7203-484f-bd47-fc69e3c7c482": "server/src/controllers/v1/monitorController.js-simstep-60abdb27-4078-4678-a010-0f6938d4be48",
+ "c9a012bc-d53f-494f-82f2-1ee3f40ce646": "generated-edge-simstep-ef0bbcff-9535-4c0e-a5ca-96c457f89139-c9a012bc-d53f-494f-82f2-1ee3f40ce646",
+ "e03a8ce2-c2cd-4147-8bab-63d6c2cf0a48": "generated-edge-simstep-610120d2-0521-4444-b245-feb97239a389-e03a8ce2-c2cd-4147-8bab-63d6c2cf0a48",
+ "b8c0292d-afbb-4648-b484-1c27be96d52f": "generated-edge-simstep-a365d018-e80c-419c-80b3-5ffc8f14dc5c-b8c0292d-afbb-4648-b484-1c27be96d52f",
+ "23715cce-c83a-44c0-9853-4c95b85e5ecd": "generated-edge-simstep-e6dec1dc-33cb-4669-a3b0-7a3c0b233185-23715cce-c83a-44c0-9853-4c95b85e5ecd",
+ "20d0e690-a569-4c6f-8238-916cac920553": "generated-edge-simstep-c53d3f62-73fe-4fad-9835-ef2c8cf52894-20d0e690-a569-4c6f-8238-916cac920553",
+ "7292bf6e-431d-4a9d-94ce-2bcfabcaa79d": "generated-edge-simstep-6038422c-ab83-4fc6-a58b-c75734276118-7292bf6e-431d-4a9d-94ce-2bcfabcaa79d",
+ "b55a0dc6-f596-4e5f-9265-63588b46b519": "generated-edge-simstep-0115fb69-4959-4b32-bd90-1dcd3c2231f6-b55a0dc6-f596-4e5f-9265-63588b46b519",
+ "f27c60ef-fb40-43e2-9750-c2617bdeaf43": "generated-edge-simstep-2ee7bd94-d131-4dd8-bdf8-68433d00472d-f27c60ef-fb40-43e2-9750-c2617bdeaf43",
+ "c449f81a-d6af-4f7a-b2d8-050a9a9ee0f9": "generated-edge-simstep-94d5772d-3f46-4d43-b764-3493db5c4971-c449f81a-d6af-4f7a-b2d8-050a9a9ee0f9",
+ "926a330c-a9c5-4983-a4b5-70359813e49a": "generated-edge-simstep-c60a3adb-dd81-41b0-95e4-a0a6d23f9dd0-926a330c-a9c5-4983-a4b5-70359813e49a",
+ "febea87e-5033-479c-ba7a-eb35d98db899": "generated-edge-simstep-53cebb8e-cf45-49eb-a7d3-98fd87af21b4-febea87e-5033-479c-ba7a-eb35d98db899",
+ "3e0f0966-5c2f-41af-9bcc-e8f615e03a00": "generated-edge-simstep-74e95fb5-863c-4137-a41b-d7a32efa04a5-3e0f0966-5c2f-41af-9bcc-e8f615e03a00",
+ "ad300b1c-6748-40f9-a7c9-5895e0915991": "generated-edge-simstep-4deb5e47-2ac4-4638-9ee6-48295d027bcd-ad300b1c-6748-40f9-a7c9-5895e0915991",
+ "3dfb432a-2ce4-4f92-98ce-5d114b5f7f03": "generated-edge-simstep-605f4cbd-1f7f-4e89-b028-f18737ac7fb1-3dfb432a-2ce4-4f92-98ce-5d114b5f7f03",
+ "50f77798-f41b-4f5b-b096-7afc921a0419": "client/src/Pages/v1/Settings",
+ "1716ec41-8b42-4386-9850-55b8a607638e": "client/src/Pages/v1/Logs",
+ "8ee5d524-e61d-4e39-a729-babf54929def": "client/src/Hooks/v1/settingsHooks.js",
+ "a1fea5b0-fb7c-4189-9484-9d3312660767": "server/src/controllers/v1/settingsController.js",
+ "5882419e-8a92-4edd-a71a-1252983f9af7": "server/src/controllers/v1/diagnosticController.js",
+ "d4b34195-7e1e-47a0-9303-603987c81f95": "client/src/Pages/v1/Settings/index.jsx",
+ "d8aa5b9e-f2a7-44c8-8813-d79c3f3238ee": "client/src/Pages/v1/Settings/SettingsEmail.jsx",
+ "e0cb598c-e300-4fd3-8554-db7ffd6201f1": "client/src/Pages/v1/Logs/Diagnostics",
+ "643b26c1-3bfb-4c41-8179-96dfc237d99e": "server/src/service/v1/business/diagnosticService.js",
+ "87e0e2ea-4a6d-460d-95bc-31e2efd9c9cf": "client/src/Pages/v1/Logs/Diagnostics/index.jsx",
+ "97908daf-d071-4df5-8366-1f5f4d8d534a": "client/src/Pages/v1/Settings/index.jsx-simstep-64e9f1af-13b7-4361-a2a9-3f45760e316d",
+ "83596bd1-dfca-4196-b52e-955f6824fafb": "server/src/controllers/v1/settingsController.js-simstep-441b0c45-5f27-478d-8ca8-58fbd5f460ea",
+ "09cb194a-0ea4-46e2-b32b-36b62fa87f40": "client/src/Pages/v1/Settings/SettingsEmail.jsx-simstep-ae5c5146-4dbb-4612-9d23-086e57f72474",
+ "1b2cf182-9ee3-4218-a7ec-b74e427ccddf": "client/src/Hooks/v1/settingsHooks.js-simstep-d7098072-9a83-4369-8176-9646ba34e186",
+ "cce3b9c8-f3b7-434b-b3c6-d8d164714f86": "server/src/controllers/v1/settingsController.js-simstep-7632e8d2-4086-4169-ae3f-bfbf8d657987",
+ "fff5dde4-8eb8-4c21-9c6d-0845a9749ff3": "client/src/Hooks/v1/settingsHooks.js-simstep-a157bd07-b969-44f8-914c-3d8393c1605a",
+ "704e2ec8-9f2f-47e5-852f-c6ada67a02e0": "client/src/Pages/v1/Logs/Diagnostics/index.jsx-simstep-a9b24b5f-534b-4baf-a185-bac7c32a1c42",
+ "09f07e80-185e-4547-aab4-edaf8ae7501e": "server/src/service/v1/business/diagnosticService.js-simstep-8fb704ca-67d9-437c-90d8-01302634d568",
+ "4b418736-6b72-47e0-917a-d86d4bed9ecd": "client/src/Pages/v1/Logs/Diagnostics/index.jsx-simstep-e6c94f52-a599-4568-a06f-2db18f963817",
+ "f6d80f09-ae83-4c95-aba0-4efffeda38e0": "generated-edge-simstep-dca4a33c-b666-47b8-896b-370ed9d6f9c5-f6d80f09-ae83-4c95-aba0-4efffeda38e0",
+ "195ad532-9858-493e-876a-6ec457811d1d": "generated-edge-simstep-aacc9302-6ca8-45c6-acb8-956d8d0242c2-195ad532-9858-493e-876a-6ec457811d1d",
+ "8c107d79-f796-49eb-ab88-f553dd6f9d95": "generated-edge-simstep-e7e82a97-1409-4bc5-ae27-b4ff5ba746df-8c107d79-f796-49eb-ab88-f553dd6f9d95",
+ "ce3ed3ba-a35b-4881-8dfd-f8425b9bf812": "generated-edge-simstep-383387e5-4623-44ab-b2c4-d9ea046f4547-ce3ed3ba-a35b-4881-8dfd-f8425b9bf812",
+ "fbe9b160-307e-4f51-8f72-713fda5373c8": "generated-edge-simstep-0afcde35-7d51-4939-aae8-bac5ac83c49d-fbe9b160-307e-4f51-8f72-713fda5373c8",
+ "499dfe1f-77d4-4b26-a12c-45240691109e": "generated-edge-simstep-23b8ad7d-8967-4e48-814c-1a7bf5b4dfac-499dfe1f-77d4-4b26-a12c-45240691109e",
+ "d0f92604-a6fa-4d85-9716-ea8f32db78e0": "generated-edge-simstep-44a4372f-a903-4c43-a9a5-c7549c875e11-d0f92604-a6fa-4d85-9716-ea8f32db78e0"
+ }
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index b93794d4f..129953fb9 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,7 @@ Usage instructions can be found [here](https://docs.checkmate.so/checkmate-2.1).
See installation instructions in [Checkmate documentation portal](https://docs.checkmate.so/checkmate-2.1/users-guide/quickstart).
-Alternatively, you can also use [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (South Africa) or [Pikapods](https://www.pikapods.com/) to quickly spin off a Checkmate instance. If you would like to monitor your server infrastructure, you'll need [Capture agent](https://github.com/bluewave-labs/capture). Capture repository also contains the installation instructions.
+Alternatively, you can also use [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (South Africa) or [Pikapods](https://www.pikapods.com/) to quickly spin off a Checkmate instance. If you would like to monitor your server infrastructure, you'll need [Capture agent](https://github.com/bluewave-labs/capture). Capture repository also contains the installation instructions.
### Using a Custom CA
@@ -91,6 +91,7 @@ Feel free to ask questions or share your ideas - we'd love to hear from you!
- Website monitoring
- Page speed monitoring
- Infrastructure monitoring (memory, disk usage, CPU performance, network etc) - requires [Capture](https://github.com/bluewave-labs/capture) agent
+ - Selective disk monitoring with mountpoint selection
- Docker monitoring
- Ping monitoring
- SSL monitoring
@@ -162,6 +163,7 @@ Here's how you can contribute:
4. Open an issue if you believe you've encountered a bug.
5. Check for good-first-issue's if you are a newcomer.
6. Make a pull request to add new features/make quality-of-life improvements/fix bugs.
+7. Check out this interactive walkthrough of the `Checkmate` codebase on CodeCanvas [here](https://www.code-canvas.com/?session=unauthenticatedGithub&repo=Checkmate&owner=bluewave-labs&branch=develop&OnboardingTutorial=true). To refine existing dataflow simulation or create new ones, follow the quick tutorial [here](https://docs.code-canvas.com/updating-diagram).
diff --git a/charts/helm/checkmate/INSTALLATION.md b/charts/helm/checkmate/INSTALLATION.md
index 9ca321ad3..06cf9f7b9 100644
--- a/charts/helm/checkmate/INSTALLATION.md
+++ b/charts/helm/checkmate/INSTALLATION.md
@@ -21,7 +21,12 @@ cd checkmate/charts/helm/checkmate
Edit `values.yaml` to update:
- `client.ingress.host` and `server.ingress.host` with your domain names
- `server.protocol` (usually http or https)
+- **If upgrading**: Migrate persistence settings from flat structure to nested:
+ - Old: `persistence.mongodbSize` → New: `persistence.mongo.size`
+ - Old: `persistence.redisSize` → New: `persistence.redis.size`
+ - Add: `persistence.mongo.storageClass` and `persistence.redis.storageClass` (leave empty for default)
- Secrets under the `secrets` section (`JWT_SECRET`, email credentials, API keys, etc.) — replace all change_me values
+- **For TLS/HTTPS**: Configure ingress TLS settings (see section below)
### 3. Deploy the Helm chart
```bash
@@ -37,3 +42,75 @@ kubectl get svc
```
Once all pods are `Running` and `Ready`, you can access Checkmate via the configured ingress hosts.
+
+## Enabling TLS/HTTPS with cert-manager
+
+If you have [cert-manager](https://cert-manager.io/) installed in your cluster, you can enable automatic TLS certificate provisioning using Let's Encrypt or other certificate issuers.
+
+### Prerequisites
+- cert-manager installed in your cluster
+- A ClusterIssuer or Issuer configured (e.g., `letsencrypt-prod`)
+
+### Configuration
+
+Edit `values.yaml` to enable TLS (and update protocols to https):
+
+```yaml
+client:
+ protocol: https
+ ingress:
+ enabled: true
+ host: checkmate.example.com
+ className: nginx
+ annotations:
+ cert-manager.io/cluster-issuer: "letsencrypt-prod"
+ tls:
+ enabled: true
+ secretName: checkmate-client-tls
+
+server:
+ protocol: https
+ ingress:
+ enabled: true
+ host: checkmate.example.com
+ className: nginx
+ annotations:
+ cert-manager.io/cluster-issuer: "letsencrypt-prod"
+ tls:
+ enabled: true
+ secretName: checkmate-server-tls
+```
+
+### Alternative: Using --set flags
+
+You can also enable TLS during installation using Helm's `--set` flags:
+
+```bash
+helm install checkmate ./charts/helm/checkmate \
+ --set client.protocol=https \
+ --set server.protocol=https \
+ --set client.ingress.annotations."cert-manager\.io/cluster-issuer"="letsencrypt-prod" \
+ --set client.ingress.tls.enabled=true \
+ --set client.ingress.tls.secretName=checkmate-client-tls \
+ --set server.ingress.annotations."cert-manager\.io/cluster-issuer"="letsencrypt-prod" \
+ --set server.ingress.tls.enabled=true \
+ --set server.ingress.tls.secretName=checkmate-server-tls
+```
+
+### Verification
+
+After deployment, cert-manager will automatically create the TLS secrets. You can verify the certificate status:
+
+```bash
+# Check certificates
+kubectl get certificates
+
+# Check certificate details
+kubectl describe certificate checkmate-client-tls
+kubectl describe certificate checkmate-server-tls
+
+# Verify the secrets were created
+kubectl get secrets | grep checkmate-tls
+```
+
+The ingress will automatically use these secrets to enable HTTPS access to your Checkmate instance.
diff --git a/charts/helm/checkmate/templates/_prechecks.yaml b/charts/helm/checkmate/templates/_prechecks.yaml
deleted file mode 100644
index 870ab5c77..000000000
--- a/charts/helm/checkmate/templates/_prechecks.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-{{- if eq .Values.client.ingress.host "change_me" }}
- {{- fail "client.ingress.host must be overridden and not set to 'change_me'" }}
-{{- end }}
-
-{{- if eq .Values.server.ingress.host "change_me" }}
- {{- fail "server.ingress.host must be overridden and not set to 'change_me'" }}
-{{- end }}
-
-{{- $protocol := .Values.server.protocol }}
-{{- if not (or (eq $protocol "http") (eq $protocol "https")) }}
- {{- fail "server.protocol must be either 'http' or 'https'" }}
-{{- end }}
diff --git a/charts/helm/checkmate/templates/client-ingress.yaml b/charts/helm/checkmate/templates/client-ingress.yaml
index 89e001e15..4fe126ea6 100644
--- a/charts/helm/checkmate/templates/client-ingress.yaml
+++ b/charts/helm/checkmate/templates/client-ingress.yaml
@@ -11,6 +11,12 @@ metadata:
{{- end }}
spec:
ingressClassName: {{ .Values.client.ingress.className }}
+ {{- if .Values.client.ingress.tls.enabled }}
+ tls:
+ - hosts:
+ - {{ .Values.client.ingress.host }}
+ secretName: {{ default (printf "%s-client-tls" .Release.Name) .Values.client.ingress.tls.secretName }}
+ {{- end }}
rules:
- host: {{ .Values.client.ingress.host }}
http:
diff --git a/charts/helm/checkmate/templates/mongodb-statefulsets.yaml b/charts/helm/checkmate/templates/mongodb-statefulsets.yaml
index f849e4e21..8149b77e2 100644
--- a/charts/helm/checkmate/templates/mongodb-statefulsets.yaml
+++ b/charts/helm/checkmate/templates/mongodb-statefulsets.yaml
@@ -37,8 +37,8 @@ spec:
- metadata:
name: checkmate-mongo-persistent-storage
spec:
- storageClassName: "gp3"
+ storageClassName: {{ .Values.persistence.mongo.storageClass | quote }}
accessModes: ["ReadWriteOnce"]
resources:
requests:
- storage: {{ .Values.persistence.mongodbSize }}
\ No newline at end of file
+ storage: {{ .Values.persistence.mongo.size | default "5Gi" | quote }}
\ No newline at end of file
diff --git a/charts/helm/checkmate/templates/prechecks.yaml b/charts/helm/checkmate/templates/prechecks.yaml
new file mode 100644
index 000000000..e2b9ec5c2
--- /dev/null
+++ b/charts/helm/checkmate/templates/prechecks.yaml
@@ -0,0 +1,48 @@
+{{- if eq .Values.client.ingress.host "change_me" }}
+ {{- fail "client.ingress.host must be overridden and not set to 'change_me'" }}
+{{- end }}
+
+{{- if eq .Values.server.ingress.host "change_me" }}
+ {{- fail "server.ingress.host must be overridden and not set to 'change_me'" }}
+{{- end }}
+
+{{- $serverProtocol := .Values.server.protocol }}
+{{- if not (or (eq $serverProtocol "http") (eq $serverProtocol "https")) }}
+ {{- fail "server.protocol must be either 'http' or 'https'" }}
+{{- end }}
+
+{{- $clientProtocol := .Values.client.protocol }}
+{{- if not (or (eq $clientProtocol "http") (eq $clientProtocol "https")) }}
+ {{- fail "client.protocol must be either 'http' or 'https'" }}
+{{- end }}
+
+{{/* Enforce protocol when TLS is enabled to avoid mixed-content */}}
+{{- if and .Values.client.ingress.tls.enabled (ne $clientProtocol "https") }}
+ {{- fail "client.ingress.tls.enabled is true but client.protocol is not 'https'. Set client.protocol: https to avoid mixed content." }}
+{{- end }}
+
+{{- if and .Values.server.ingress.tls.enabled (ne $serverProtocol "https") }}
+ {{- fail "server.ingress.tls.enabled is true but server.protocol is not 'https'. Set server.protocol: https to ensure correct API base URL." }}
+{{- end }}
+
+{{/* If client runs on https, API must also be https to avoid mixed content */}}
+{{- if and (eq $clientProtocol "https") (ne $serverProtocol "https") }}
+ {{- fail "client.protocol is 'https' but server.protocol is not. Set server.protocol: https to prevent browser mixed-content issues." }}
+{{- end }}
+
+{{/* Fail early if TLS enabled without cert-manager annotations (cluster-issuer or issuer) */}}
+{{- $cAnn := .Values.client.ingress.annotations | default dict }}
+{{- $sAnn := .Values.server.ingress.annotations | default dict }}
+
+{{- $clientHasIssuer := or (hasKey $cAnn "cert-manager.io/cluster-issuer") (hasKey $cAnn "cert-manager.io/issuer") }}
+{{- $serverHasIssuer := or (hasKey $sAnn "cert-manager.io/cluster-issuer") (hasKey $sAnn "cert-manager.io/issuer") }}
+
+{{- if and .Values.client.ingress.tls.enabled (not $clientHasIssuer) }}
+ {{- fail "client.ingress.tls.enabled is true but no cert-manager issuer annotation found. Add 'cert-manager.io/cluster-issuer' or 'cert-manager.io/issuer'." }}
+{{- end }}
+
+{{- if and .Values.server.ingress.tls.enabled (not $serverHasIssuer) }}
+ {{- fail "server.ingress.tls.enabled is true but no cert-manager issuer annotation found. Add 'cert-manager.io/cluster-issuer' or 'cert-manager.io/issuer'." }}
+{{- end }}
+
+{{/* Secret name can be omitted; we default to -client|server-tls in templates */}}
diff --git a/charts/helm/checkmate/templates/redis-statefulsets.yaml b/charts/helm/checkmate/templates/redis-statefulsets.yaml
index 252361730..acf7c85a8 100644
--- a/charts/helm/checkmate/templates/redis-statefulsets.yaml
+++ b/charts/helm/checkmate/templates/redis-statefulsets.yaml
@@ -25,9 +25,9 @@ spec:
- metadata:
name: checkmate-redis-persistent-storage
spec:
- storageClassName: "gp3"
+ storageClassName: {{ .Values.persistence.redis.storageClass | quote }}
accessModes: ["ReadWriteOnce"]
resources:
requests:
- storage: {{ .Values.persistence.redisSize }}
+ storage: {{ .Values.persistence.redis.size | default "1Gi" | quote }}
{{- end }}
\ No newline at end of file
diff --git a/charts/helm/checkmate/templates/server-ingress.yaml b/charts/helm/checkmate/templates/server-ingress.yaml
index 6e2d0d4d6..34c9ac0dc 100644
--- a/charts/helm/checkmate/templates/server-ingress.yaml
+++ b/charts/helm/checkmate/templates/server-ingress.yaml
@@ -18,6 +18,12 @@ metadata:
#nginx.ingress.kubernetes.io/cors-allow-credentials: "true"*/}}
spec:
ingressClassName: {{ .Values.server.ingress.className }}
+ {{- if .Values.server.ingress.tls.enabled }}
+ tls:
+ - hosts:
+ - {{ .Values.server.ingress.host }}
+ secretName: {{ default (printf "%s-server-tls" .Release.Name) .Values.server.ingress.tls.secretName }}
+ {{- end }}
rules:
- host: {{ .Values.server.ingress.host }}
http:
diff --git a/charts/helm/checkmate/values.yaml b/charts/helm/checkmate/values.yaml
index 0ba0051f8..9a586dd54 100644
--- a/charts/helm/checkmate/values.yaml
+++ b/charts/helm/checkmate/values.yaml
@@ -7,6 +7,15 @@ client:
host: change_me
className: nginx
annotations: {}
+ # Example annotations for cert-manager:
+ # annotations:
+ # cert-manager.io/cluster-issuer: "letsencrypt-prod"
+ tls:
+ enabled: false
+ # secretName: {{ .Release.Name }}-client-tls # Optional; defaults to -client-tls if omitted
+ # Note: when enabling TLS, also set client.protocol: https and add
+ # a cert-manager issuer annotation (e.g. cert-manager.io/cluster-issuer: "letsencrypt-prod").
+ # The secret will be automatically created by cert-manager when using the cert-manager.io/cluster-issuer annotation
server:
image: ghcr.io/bluewave-labs/checkmate-backend:v3.2.0
@@ -17,6 +26,15 @@ server:
host: change_me
className: nginx
annotations: {}
+ # Example annotations for cert-manager:
+ # annotations:
+ # cert-manager.io/cluster-issuer: "letsencrypt-prod"
+ tls:
+ enabled: false
+ # secretName: {{ .Release.Name }}-server-tls # Optional; defaults to -server-tls if omitted
+ # Note: when enabling TLS, also set server.protocol: https and add
+ # a cert-manager issuer annotation (e.g. cert-manager.io/cluster-issuer: "letsencrypt-prod").
+ # The secret will be automatically created by cert-manager when using the cert-manager.io/cluster-issuer annotation
redis:
enabled: false
@@ -44,5 +62,9 @@ secrets:
# REFRESH_TOKEN_TTL: 99d
persistence:
- mongodbSize: 5Gi
- redisSize: 1Gi
\ No newline at end of file
+ mongo:
+ size: 5Gi
+ storageClass: ""
+ redis:
+ size: 1Gi
+ storageClass: ""
\ No newline at end of file
diff --git a/client/.gitignore b/client/.gitignore
index 83f6fe9ed..23b6e49af 100644
--- a/client/.gitignore
+++ b/client/.gitignore
@@ -28,4 +28,5 @@ dist-ssr
# Scripts
*.sh
-!env.sh
\ No newline at end of file
+!env.sh
+.env.development
\ No newline at end of file
diff --git a/client/package-lock.json b/client/package-lock.json
index 7cf0e8631..b4ad9f73c 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -12,7 +12,6 @@
"@emotion/styled": "^11.13.0",
"@hello-pangea/dnd": "^18.0.0",
"@hookform/resolvers": "5.2.2",
- "@mui/icons-material": "6.4.11",
"@mui/lab": "6.0.0-dev.240424162023-9968b4889d",
"@mui/material": "6.4.11",
"@mui/x-charts": "^7.5.1",
@@ -25,8 +24,9 @@
"human-interval": "2.0.1",
"i18next": "25.4.2",
"joi": "17.13.3",
+ "lucide-react": "^0.562.0",
"mui-color-input": "^6.0.0",
- "pretty-ms": "9.3.0",
+ "pretty-ms": "^9.3.0",
"react": "18.3.1",
"react-dom": "^18.2.0",
"react-hook-form": "7.63.0",
@@ -38,7 +38,7 @@
"react-toastify": "^10.0.5",
"recharts": "2.15.2",
"redux-persist": "6.0.0",
- "swr": "2.3.6",
+ "swr": "^2.3.6",
"vite-plugin-svgr": "^4.2.0",
"zod": "4.1.11"
},
@@ -97,6 +97,7 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz",
"integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.27.1",
@@ -408,6 +409,7 @@
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5",
@@ -451,6 +453,7 @@
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz",
"integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5",
@@ -496,6 +499,262 @@
"integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
"license": "MIT"
},
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz",
+ "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz",
+ "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz",
+ "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz",
+ "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz",
+ "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz",
+ "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz",
+ "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz",
+ "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz",
+ "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz",
+ "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz",
+ "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz",
+ "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==",
+ "cpu": [
+ "loong64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz",
+ "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz",
+ "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz",
+ "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz",
+ "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@esbuild/linux-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz",
@@ -512,6 +771,134 @@
"node": ">=18"
}
},
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz",
+ "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz",
+ "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz",
+ "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz",
+ "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz",
+ "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz",
+ "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz",
+ "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz",
+ "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
@@ -802,32 +1189,6 @@
"url": "https://opencollective.com/mui-org"
}
},
- "node_modules/@mui/icons-material": {
- "version": "6.4.11",
- "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.11.tgz",
- "integrity": "sha512-+jjJGIrB1awNbMv4ZVPPdN/p7O1UKFZ+xqRvNIQ8B1KnlID5hPMPBLM6UUbRF4bu3UDCbu79rn9Nye5LGNzmeA==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.26.0"
- },
- "engines": {
- "node": ">=14.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/mui-org"
- },
- "peerDependencies": {
- "@mui/material": "^6.4.11",
- "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
- "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
"node_modules/@mui/lab": {
"version": "6.0.0-dev.240424162023-9968b4889d",
"resolved": "https://registry.npmjs.org/@mui/lab/-/lab-6.0.0-dev.240424162023-9968b4889d.tgz",
@@ -989,6 +1350,7 @@
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.11.tgz",
"integrity": "sha512-k2D3FLJS+/qD0qnd6ZlAjGFvaaxe1Dl10NyvpeDzIebMuYdn8VqYe6XBgGueEAtnzSJM4V03VD9kb5Fi24dnTA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/runtime": "^7.26.0",
"@mui/core-downloads-tracker": "^6.4.11",
@@ -1153,7 +1515,6 @@
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.1.0.tgz",
"integrity": "sha512-4Kck4jxhqF6YxNwJdSae1WgDfXVg0lIH6JVJ7gtuFfuKcQCgomJxPvUEOySTFRPz1IZzwz5OAcToskRdffElDA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.27.1",
"@mui/utils": "^7.1.0",
@@ -1181,7 +1542,6 @@
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.1.0.tgz",
"integrity": "sha512-/OM3S8kSHHmWNOP+NH9xEtpYSG10upXeQ0wLZnfDgmgadTAk5F4MQfFLyZ5FCRJENB3eRzltMmaNl6UtDnPovw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.27.1",
"@mui/types": "^7.4.2",
@@ -1212,7 +1572,6 @@
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.1.0.tgz",
"integrity": "sha512-m0mJ0c6iRC+f9hMeRe0W7zZX1wme3oUX0+XTVHjPG7DJz6OdQ6K/ggEOq7ZdwilcpdsDUwwMfOmvO71qDkYd2w==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.27.1",
"@emotion/cache": "^11.13.5",
@@ -1288,7 +1647,6 @@
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.1.0.tgz",
"integrity": "sha512-/OM3S8kSHHmWNOP+NH9xEtpYSG10upXeQ0wLZnfDgmgadTAk5F4MQfFLyZ5FCRJENB3eRzltMmaNl6UtDnPovw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.27.1",
"@mui/types": "^7.4.2",
@@ -1666,9 +2024,9 @@
}
},
"node_modules/@remix-run/router": {
- "version": "1.23.0",
- "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz",
- "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==",
+ "version": "1.23.2",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz",
+ "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==",
"license": "MIT",
"engines": {
"node": ">=14.0.0"
@@ -1696,6 +2054,123 @@
}
}
},
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.0.tgz",
+ "integrity": "sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.0.tgz",
+ "integrity": "sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.0.tgz",
+ "integrity": "sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.0.tgz",
+ "integrity": "sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.0.tgz",
+ "integrity": "sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.0.tgz",
+ "integrity": "sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.0.tgz",
+ "integrity": "sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.0.tgz",
+ "integrity": "sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.0.tgz",
+ "integrity": "sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.0.tgz",
@@ -1709,6 +2184,71 @@
"linux"
]
},
+ "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.0.tgz",
+ "integrity": "sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==",
+ "cpu": [
+ "loong64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.0.tgz",
+ "integrity": "sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.0.tgz",
+ "integrity": "sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.0.tgz",
+ "integrity": "sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.0.tgz",
+ "integrity": "sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.0.tgz",
@@ -1735,6 +2275,45 @@
"linux"
]
},
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.0.tgz",
+ "integrity": "sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.0.tgz",
+ "integrity": "sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.0.tgz",
+ "integrity": "sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
"node_modules/@sideway/address": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
@@ -2133,6 +2712,7 @@
"integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==",
"devOptional": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"undici-types": "~7.12.0"
}
@@ -2154,6 +2734,7 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.22.tgz",
"integrity": "sha512-vUhG0YmQZ7kL/tmKLrD3g5zXbXXreZXB3pmROW8bg3CnLnpjkRVwUlLne7Ufa2r9yJ8+/6B73RzhAek5TBKh2Q==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
@@ -2217,6 +2798,7 @@
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2523,6 +3105,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001716",
"electron-to-chromium": "^1.5.149",
@@ -2935,7 +3518,8 @@
"version": "1.11.13",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/debug": {
"version": "4.4.1",
@@ -3342,6 +3926,7 @@
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
@@ -4149,6 +4734,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/runtime": "^7.27.6"
},
@@ -4685,9 +5271,9 @@
"license": "MIT"
},
"node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
@@ -4852,6 +5438,15 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/lucide-react": {
+ "version": "0.562.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.562.0.tgz",
+ "integrity": "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -5416,6 +6011,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
@@ -5429,6 +6025,7 @@
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.63.0.tgz",
"integrity": "sha512-ZwueDMvUeucovM2VjkCf7zIHcs1aAlDimZu2Hvel5C5907gUzMpm4xCrQXtRzCvsBqFjonB4m3x4LzCFI1ZKWA==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=18.0.0"
},
@@ -5515,12 +6112,12 @@
}
},
"node_modules/react-router": {
- "version": "6.30.1",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz",
- "integrity": "sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==",
+ "version": "6.30.3",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz",
+ "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==",
"license": "MIT",
"dependencies": {
- "@remix-run/router": "1.23.0"
+ "@remix-run/router": "1.23.2"
},
"engines": {
"node": ">=14.0.0"
@@ -5530,13 +6127,13 @@
}
},
"node_modules/react-router-dom": {
- "version": "6.30.1",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.1.tgz",
- "integrity": "sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==",
+ "version": "6.30.3",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz",
+ "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==",
"license": "MIT",
"dependencies": {
- "@remix-run/router": "1.23.0",
- "react-router": "6.30.1"
+ "@remix-run/router": "1.23.2",
+ "react-router": "6.30.3"
},
"engines": {
"node": ">=14.0.0"
@@ -5632,7 +6229,8 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/redux-persist": {
"version": "6.0.0",
@@ -5770,6 +6368,7 @@
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.0.tgz",
"integrity": "sha512-HqMFpUbWlf/tvcxBFNKnJyzc7Lk+XO3FGc3pbNBLqEbOz0gPLRgcrlS3UF4MfUrVlstOaP/q0kM6GVvi+LrLRg==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/estree": "1.0.7"
},
@@ -6405,6 +7004,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"license": "Apache-2.0",
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -6524,6 +7124,7 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz",
"integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
@@ -6744,20 +7345,6 @@
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"license": "ISC"
},
- "node_modules/yaml": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz",
- "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==",
- "license": "ISC",
- "optional": true,
- "peer": true,
- "bin": {
- "yaml": "bin.mjs"
- },
- "engines": {
- "node": ">= 14.6"
- }
- },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
diff --git a/client/package.json b/client/package.json
index 376671942..a5c17e343 100644
--- a/client/package.json
+++ b/client/package.json
@@ -17,7 +17,6 @@
"@emotion/styled": "^11.13.0",
"@hello-pangea/dnd": "^18.0.0",
"@hookform/resolvers": "5.2.2",
- "@mui/icons-material": "6.4.11",
"@mui/lab": "6.0.0-dev.240424162023-9968b4889d",
"@mui/material": "6.4.11",
"@mui/x-charts": "^7.5.1",
@@ -30,8 +29,9 @@
"human-interval": "2.0.1",
"i18next": "25.4.2",
"joi": "17.13.3",
+ "lucide-react": "^0.562.0",
"mui-color-input": "^6.0.0",
- "pretty-ms": "9.3.0",
+ "pretty-ms": "^9.3.0",
"react": "18.3.1",
"react-dom": "^18.2.0",
"react-hook-form": "7.63.0",
@@ -43,7 +43,7 @@
"react-toastify": "^10.0.5",
"recharts": "2.15.2",
"redux-persist": "6.0.0",
- "swr": "2.3.6",
+ "swr": "^2.3.6",
"vite-plugin-svgr": "^4.2.0",
"zod": "4.1.11"
},
diff --git a/client/src/App.jsx b/client/src/App.jsx
index d21850ebb..da83f9061 100644
--- a/client/src/App.jsx
+++ b/client/src/App.jsx
@@ -22,13 +22,29 @@ function App() {
};
}, []);
+ const theme = mode === "light" ? lightTheme : darkTheme;
+
return (
-
+
+
-
+
);
}
diff --git a/client/src/Components/LanguageSelector.jsx b/client/src/Components/LanguageSelector.jsx
index 9b46204e3..fdda73f4a 100644
--- a/client/src/Components/LanguageSelector.jsx
+++ b/client/src/Components/LanguageSelector.jsx
@@ -16,7 +16,7 @@ const langMap = {
const LanguageSelector = () => {
const { i18n } = useTranslation();
const theme = useTheme();
- const { language } = useSelector((state) => state.ui);
+ const { language = "en" } = useSelector((state) => state.ui);
const dispatch = useDispatch();
const handleChange = (event) => {
const newLang = event.target.value;
diff --git a/client/src/Components/v1/ActionsMenu/index.jsx b/client/src/Components/v1/ActionsMenu/index.jsx
index 229c7c875..ea0d029b8 100644
--- a/client/src/Components/v1/ActionsMenu/index.jsx
+++ b/client/src/Components/v1/ActionsMenu/index.jsx
@@ -2,7 +2,7 @@
import IconButton from "@mui/material/IconButton";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
-import Settings from "../../../assets/icons/settings-bold.svg?react";
+import Icon from "../Icon";
import Dialog from "../Dialog/index.jsx";
// Utils
@@ -12,7 +12,7 @@ import { useNavigate } from "react-router-dom";
import { createToast } from "../../../Utils/toastUtils.jsx";
import PropTypes from "prop-types";
-import { usePauseMonitor, useDeleteMonitor } from "../../../Hooks/v1/monitorHooks.js";
+import { usePauseMonitor, useDeleteMonitor } from "../../../Hooks/monitorHooks.js";
const ActionsMenu = ({
monitor,
@@ -31,7 +31,7 @@ const ActionsMenu = ({
const handleRemove = async (event) => {
event.preventDefault();
event.stopPropagation();
- let monitor = { _id: actions.id };
+ let monitor = { id: actions.id };
await deleteMonitor({ monitor });
updateRowCallback();
};
@@ -39,7 +39,7 @@ const ActionsMenu = ({
const handlePause = async () => {
try {
setIsLoading(true);
- await pauseMonitor({ monitorId: monitor._id });
+ await pauseMonitor({ monitorId: monitor.id });
pauseCallback();
} catch (error) {
createToast({ body: "Failed to pause monitor." });
@@ -72,7 +72,7 @@ const ActionsMenu = ({
aria-label="monitor actions"
onClick={(event) => {
event.stopPropagation();
- openMenu(event, monitor._id, monitor.type === "ping" ? null : monitor.url);
+ openMenu(event, monitor.id, monitor.type === "ping" ? null : monitor.url);
}}
sx={{
"&:focus": {
@@ -83,7 +83,10 @@ const ActionsMenu = ({
},
}}
>
-
+
{
+ console.log(e);
e.stopPropagation();
handleRemove(e);
}}
@@ -210,7 +214,7 @@ const ActionsMenu = ({
ActionsMenu.propTypes = {
monitor: PropTypes.shape({
- _id: PropTypes.string,
+ id: PropTypes.string,
url: PropTypes.string,
type: PropTypes.string,
isActive: PropTypes.bool,
diff --git a/client/src/Components/v1/Alert/index.jsx b/client/src/Components/v1/Alert/index.jsx
index 921d4fd6e..dd3b2f9f2 100644
--- a/client/src/Components/v1/Alert/index.jsx
+++ b/client/src/Components/v1/Alert/index.jsx
@@ -1,10 +1,7 @@
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { Box, Button, IconButton, Stack, Typography } from "@mui/material";
-import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
-import ErrorOutlineOutlinedIcon from "@mui/icons-material/ErrorOutlineOutlined";
-import WarningAmberOutlinedIcon from "@mui/icons-material/WarningAmberOutlined";
-import CloseIcon from "@mui/icons-material/Close";
+import Icon from "../Icon";
import "./index.css";
/**
@@ -13,9 +10,24 @@ import "./index.css";
*/
const icons = {
- info: ,
- error: ,
- warning: ,
+ info: (
+
+ ),
+ error: (
+
+ ),
+ warning: (
+
+ ),
};
/**
@@ -97,10 +109,9 @@ const Alert = ({ variant, title, body, isToast, hasIcon = true, onClick }) => {
},
}}
>
-
)}
diff --git a/client/src/Components/v1/ArrowLeft/index.jsx b/client/src/Components/v1/ArrowLeft/index.jsx
index a327d6bb7..e18a7f2d5 100644
--- a/client/src/Components/v1/ArrowLeft/index.jsx
+++ b/client/src/Components/v1/ArrowLeft/index.jsx
@@ -1,27 +1,28 @@
-import LeftArrow from "../../../assets/icons/left-arrow.svg?react";
-import LeftArrowDouble from "../../../assets/icons/left-arrow-double.svg?react";
-import LeftArrowLong from "../../../assets/icons/left-arrow-long.svg?react";
+import Icon from "../Icon";
import PropTypes from "prop-types";
const ArrowLeft = ({ type, color = "#667085", ...props }) => {
if (type === "double") {
return (
-
);
} else if (type === "long") {
return (
-
);
} else {
return (
-
);
diff --git a/client/src/Components/v1/ArrowRight/index.jsx b/client/src/Components/v1/ArrowRight/index.jsx
index 08a2639b0..3ae64a580 100644
--- a/client/src/Components/v1/ArrowRight/index.jsx
+++ b/client/src/Components/v1/ArrowRight/index.jsx
@@ -1,19 +1,20 @@
-import RightArrow from "../../../assets/icons/right-arrow.svg?react";
-import RightArrowDouble from "../../../assets/icons/right-arrow-double.svg?react";
+import Icon from "../Icon";
import PropTypes from "prop-types";
const ArrowRight = ({ type, color = "#667085", ...props }) => {
if (type === "double") {
return (
-
);
} else {
return (
-
);
diff --git a/client/src/Components/v1/Charts/BarChart/index.jsx b/client/src/Components/v1/Charts/BarChart/index.jsx
index 2bf8e1798..77b836cae 100644
--- a/client/src/Components/v1/Charts/BarChart/index.jsx
+++ b/client/src/Components/v1/Charts/BarChart/index.jsx
@@ -1,6 +1,6 @@
import { useTheme } from "@emotion/react";
import { Box, Stack, Tooltip, Typography } from "@mui/material";
-import { formatDateWithTz } from "../../../../Utils/timeUtils.js";
+import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js";
import { useEffect, useState } from "react";
import "./index.css";
import { useSelector } from "react-redux";
@@ -103,7 +103,7 @@ const BarChart = ({ checks = [] }) => {
>
}
placement="top"
- key={`check-${check?._id}`}
+ key={`check-${check?.id}`}
slotProps={{
popper: {
className: "bar-tooltip",
@@ -145,7 +145,7 @@ const BarChart = ({ checks = [] }) => {
position="relative"
width="9px"
height="100%"
- backgroundColor={theme.palette.primary.lowContrast} // CAIO_REVIEW
+ backgroundColor={theme.palette.primary.lowContrast}
sx={{
borderRadius: theme.spacing(1.5),
/*
diff --git a/client/src/Components/v1/Charts/DePINStatusPageBarChart/index.jsx b/client/src/Components/v1/Charts/DePINStatusPageBarChart/index.jsx
index b37990e46..8b6c2b165 100644
--- a/client/src/Components/v1/Charts/DePINStatusPageBarChart/index.jsx
+++ b/client/src/Components/v1/Charts/DePINStatusPageBarChart/index.jsx
@@ -5,7 +5,7 @@ import { Stack, Box, Tooltip, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import { useState, useEffect } from "react";
import PropTypes from "prop-types";
-import { formatDateWithTz } from "../../../../Utils/timeUtils.js";
+import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js";
import { useSelector } from "react-redux";
const PlaceholderCheck = ({ daysToShow }) => {
diff --git a/client/src/Components/v1/Charts/MonitorDetailsAreaChart/index.jsx b/client/src/Components/v1/Charts/MonitorDetailsAreaChart/index.jsx
index a9ef04a53..dafca7b40 100644
--- a/client/src/Components/v1/Charts/MonitorDetailsAreaChart/index.jsx
+++ b/client/src/Components/v1/Charts/MonitorDetailsAreaChart/index.jsx
@@ -12,7 +12,7 @@ import { Box, Stack, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import { useMemo, useState } from "react";
import { useSelector } from "react-redux";
-import { formatDateWithTz } from "../../../../Utils/timeUtils.js";
+import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js";
import {
tooltipDateFormatLookup,
tickDateFormatLookup,
diff --git a/client/src/Components/v1/Charts/StatusPageBarChart/index.jsx b/client/src/Components/v1/Charts/StatusPageBarChart/index.jsx
index 7da4d58ba..2dc5e61f0 100644
--- a/client/src/Components/v1/Charts/StatusPageBarChart/index.jsx
+++ b/client/src/Components/v1/Charts/StatusPageBarChart/index.jsx
@@ -1,6 +1,6 @@
import { useTheme } from "@emotion/react";
import { Box, Stack, Tooltip, Typography } from "@mui/material";
-import { formatDateWithTz } from "../../../../Utils/timeUtils.js";
+import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js";
import { useEffect, useState, forwardRef } from "react";
import { useSelector } from "react-redux";
import PropTypes from "prop-types";
@@ -148,7 +148,7 @@ const StatusPageBarChart = ({ checks = [] }) => {
>
}
placement="top"
- key={`check-${check?._id}`}
+ key={`check-${check?.id}`}
slotProps={{
popper: {
className: "bar-tooltip",
diff --git a/client/src/Components/v1/Charts/Utils/chartUtils.jsx b/client/src/Components/v1/Charts/Utils/chartUtils.jsx
index 1bab80595..7797ba462 100644
--- a/client/src/Components/v1/Charts/Utils/chartUtils.jsx
+++ b/client/src/Components/v1/Charts/Utils/chartUtils.jsx
@@ -2,7 +2,7 @@ import PropTypes from "prop-types";
import { useSelector } from "react-redux";
import { useTheme } from "@mui/material";
import { Text } from "recharts";
-import { formatDateWithTz } from "../../../../Utils/timeUtils.js";
+import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js";
import { Box, Stack, Typography } from "@mui/material";
import { tickDateFormatLookup, tooltipDateFormatLookup } from "./chartUtilFunctions.js";
/**
diff --git a/client/src/Components/v1/Check/Check.jsx b/client/src/Components/v1/Check/Check.jsx
index 53971316b..c40f3de3e 100644
--- a/client/src/Components/v1/Check/Check.jsx
+++ b/client/src/Components/v1/Check/Check.jsx
@@ -1,7 +1,6 @@
import "./check.css";
import PropTypes from "prop-types";
-import CheckGrey from "../../../assets/icons/check.svg?react";
-import CheckOutlined from "../../../assets/icons/check-outlined.svg?react";
+import Icon from "../Icon";
import { Box, Stack, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
@@ -35,16 +34,16 @@ const Check = ({ text, noHighlightText, variant = "info", outlined = false }) =>
alignItems="center"
>
{outlined ? (
-
+
) : (
- path": { fill: colors[variant] },
- }}
- >
-
-
+
)}
fontWeight: 450,
}}
>
- {noHighlightText && {noHighlightText} }{" "}
+ {noHighlightText && (
+
+ {noHighlightText}
+
+ )}{" "}
{text}
diff --git a/client/src/Components/v1/Common/AppBar.jsx b/client/src/Components/v1/Common/AppBar.jsx
index 1d5a38ddd..5cd3db5c1 100644
--- a/client/src/Components/v1/Common/AppBar.jsx
+++ b/client/src/Components/v1/Common/AppBar.jsx
@@ -8,8 +8,7 @@ import IconButton from "@mui/material/IconButton";
import Container from "@mui/material/Container";
import MenuItem from "@mui/material/MenuItem";
import Drawer from "@mui/material/Drawer";
-import MenuIcon from "@mui/icons-material/Menu";
-import CloseRoundedIcon from "@mui/icons-material/CloseRounded";
+import Icon from "../Icon";
import ThemeSwitch from "../ThemeSwitch/index.jsx";
import { useTheme } from "@mui/material/styles";
import { useSelector } from "react-redux";
@@ -153,7 +152,11 @@ const AppAppBar = () => {
aria-label="Menu button"
onClick={toggleDrawer(true)}
>
-
+
{
}}
>
-
+
diff --git a/client/src/Components/v1/FilterHeader/index.jsx b/client/src/Components/v1/FilterHeader/index.jsx
index 36dd04081..de2c7f0e9 100644
--- a/client/src/Components/v1/FilterHeader/index.jsx
+++ b/client/src/Components/v1/FilterHeader/index.jsx
@@ -1,7 +1,7 @@
import { Checkbox, FormControl, ListItemText, MenuItem, Select } from "@mui/material";
import { useTheme } from "@emotion/react";
import PropTypes from "prop-types";
-import AddCircleOutlineIcon from "@mui/icons-material/AddCircleOutline";
+import Icon from "../Icon";
/**
* A reusable filter header component that displays a dropdown menu with selectable options.
@@ -29,9 +29,10 @@ const FilterHeader = ({ header, options, value, onChange, multiple = true }) =>
(
-
)}
displayEmpty
diff --git a/client/src/Components/v1/GenericFallback/index.jsx b/client/src/Components/v1/GenericFallback/index.jsx
index 137cdd1da..188c09f37 100644
--- a/client/src/Components/v1/GenericFallback/index.jsx
+++ b/client/src/Components/v1/GenericFallback/index.jsx
@@ -40,13 +40,13 @@ const GenericFallback = ({ children }) => {
{
+const Host = ({ url, title, percentageColor, percentage, showURL, status }) => {
const theme = useTheme();
return (
@@ -24,19 +25,34 @@ const Host = ({ url, title, percentageColor, percentage, showURL }) => {
alignItems="center"
gap={theme.spacing(4)}
>
- {title}
+
+ {title}
+
+
{percentageColor && percentage && (
<>
{percentage}%
+ {status && (
+
+ )}
>
)}
@@ -51,6 +67,7 @@ Host.propTypes = {
percentage: PropTypes.string,
url: PropTypes.string,
showURL: PropTypes.bool,
+ status: PropTypes.string,
};
export default Host;
diff --git a/client/src/Components/v1/I18nLoader/index.jsx b/client/src/Components/v1/I18nLoader/index.jsx
index 2a6a4966d..676e63d7b 100644
--- a/client/src/Components/v1/I18nLoader/index.jsx
+++ b/client/src/Components/v1/I18nLoader/index.jsx
@@ -2,7 +2,7 @@ import i18n from "../../../Utils/i18n.js";
import { useSelector } from "react-redux";
import { useEffect } from "react";
const I18nLoader = () => {
- const language = useSelector((state) => state.ui.language);
+ const language = useSelector((state) => state.ui.language ?? "en");
useEffect(() => {
if (language && i18n.language !== language) {
diff --git a/client/src/Components/v1/Icon/iconMap.js b/client/src/Components/v1/Icon/iconMap.js
new file mode 100644
index 000000000..58da7304b
--- /dev/null
+++ b/client/src/Components/v1/Icon/iconMap.js
@@ -0,0 +1,120 @@
+/**
+ * Maps legacy icon names to Lucide icon names
+ * Use: import { iconMap } from './iconMap'; LucideIcons[iconMap['monitors']]
+ */
+export const iconMap = {
+ // Navigation/Sidebar icons
+ monitors: "Globe",
+ incidents: "AlertTriangle",
+ integrations: "Link",
+ "page-speed": "Gauge",
+ settings: "Settings",
+ notifications: "Bell",
+ maintenance: "Wrench",
+ "status-pages": "Wifi",
+ docs: "FileText",
+ discussions: "MessageCircle",
+ support: "HelpCircle",
+ changeLog: "Code",
+ logs: "Database",
+
+ // User icons
+ user: "User",
+ "user-two": "Users",
+ "user-edit": "UserPen",
+ groups: "Users",
+ lock: "Lock",
+ key: "Key",
+ logout: "LogOut",
+
+ // Action icons
+ edit: "Pencil",
+ "trash-bin": "Trash2",
+ search: "Search",
+ "close-icon": "X",
+ check: "Check",
+ "check-icon": "CheckCircle",
+ "check-outlined": "CheckCircle",
+
+ // Arrow icons
+ "left-arrow": "ChevronLeft",
+ "right-arrow": "ChevronRight",
+ "up-arrow": "ChevronUp",
+ "down-arrow": "ChevronDown",
+ "left-arrow-double": "ChevronsLeft",
+ "right-arrow-double": "ChevronsRight",
+ "left-arrow-long": "ArrowLeft",
+ "top-right-arrow": "ArrowUpRight",
+ "open-in-new-page": "ExternalLink",
+ "selector-vertical": "ChevronsUpDown",
+
+ // Status/Alert icons
+ "alert-icon": "Bell",
+ "warning-icon": "AlertCircle",
+ "history-icon": "History",
+
+ // Data/Chart icons
+ dashboard: "LayoutGrid",
+ "monitor-graph-line": "TrendingUp",
+ "response-time-icon": "TrendingUp",
+ "uptime-icon": "Activity",
+ "speedometer-icon": "Gauge",
+ "spedometer-icon": "Gauge",
+ "ruler-icon": "Ruler",
+ "performance-report": "Layers",
+ "average-response-icon": "BarChart3",
+ "distributed-uptime": "Network",
+
+ // Misc icons
+ calendar: "Calendar",
+ "calendar-check": "CalendarCheck",
+ "clock-snooze": "Clock",
+ mail: "Mail",
+ email: "Mail",
+ folder: "Folder",
+ "dots-vertical": "MoreVertical",
+ "pause-icon": "PauseCircle",
+ "resume-icon": "PlayCircle",
+ "checkbox-filled": "CheckSquare",
+ "checkbox-outline": "Square",
+ "radio-checked": "Circle",
+ "cpu-chip": "Cpu",
+ certificate: "Award",
+ "interval-check": "RefreshCw",
+};
+
+/**
+ * MUI icon to Lucide mapping
+ */
+export const muiToLucideMap = {
+ AddCircleOutline: "PlusCircle",
+ ArrowDownwardRounded: "ArrowDown",
+ ArrowUpwardRounded: "ArrowUp",
+ ArrowDropDown: "ChevronDown",
+ ArrowOutward: "ExternalLink",
+ CheckCircle: "CheckCircle2",
+ Clear: "X",
+ Close: "X",
+ CloseRounded: "X",
+ CloudUpload: "Upload",
+ Delete: "Trash2",
+ DeleteOutlineRounded: "Trash2",
+ Email: "Mail",
+ ErrorOutline: "AlertTriangle",
+ ErrorOutlineOutlined: "AlertTriangle",
+ Image: "ImageIcon",
+ InfoOutlined: "Info",
+ KeyboardArrowDown: "ChevronDown",
+ Menu: "Menu",
+ PauseCircleOutline: "PauseCircle",
+ PauseOutlined: "Pause",
+ PlayArrowOutlined: "Play",
+ PlayCircleOutlineRounded: "PlayCircle",
+ RadioButtonChecked: "Circle",
+ ReorderRounded: "GripVertical",
+ SettingsOutlined: "Settings",
+ Share: "Share2",
+ Visibility: "Eye",
+ VisibilityOff: "EyeOff",
+ WarningAmberOutlined: "AlertTriangle",
+};
diff --git a/client/src/Components/v1/Icon/index.jsx b/client/src/Components/v1/Icon/index.jsx
new file mode 100644
index 000000000..9f7c61585
--- /dev/null
+++ b/client/src/Components/v1/Icon/index.jsx
@@ -0,0 +1,198 @@
+import { useTheme } from "@mui/material";
+import PropTypes from "prop-types";
+import {
+ Activity,
+ AlertCircle,
+ AlertTriangle,
+ ArrowDown,
+ ArrowLeft,
+ ArrowUp,
+ ArrowUpRight,
+ Bell,
+ Calendar,
+ Check,
+ CheckCircle,
+ CheckCircle2,
+ CheckSquare,
+ ChevronDown,
+ ChevronLeft,
+ ChevronRight,
+ ChevronsLeft,
+ ChevronsRight,
+ ChevronsUpDown,
+ Circle,
+ CircleDot,
+ Clock,
+ Code,
+ Cpu,
+ Database,
+ ExternalLink,
+ Eye,
+ EyeOff,
+ FileText,
+ Gauge,
+ Globe,
+ GripVertical,
+ HelpCircle,
+ History,
+ Image as ImageIcon,
+ Info,
+ Key,
+ Layers,
+ Link,
+ Lock,
+ LogOut,
+ Mail,
+ Menu,
+ MessageCircle,
+ MoreVertical,
+ Pause,
+ PauseCircle,
+ Play,
+ PlayCircle,
+ PlusCircle,
+ RefreshCw,
+ Ruler,
+ Search,
+ Settings,
+ Square,
+ Trash2,
+ TrendingUp,
+ Upload,
+ User,
+ Users,
+ Wifi,
+ Wrench,
+ X,
+} from "lucide-react";
+
+/**
+ * Map of icon names to Lucide icon components.
+ * Only icons explicitly imported here will be included in the bundle (tree-shaking).
+ * To add a new icon: import it above and add it to this object.
+ */
+const iconComponents = {
+ Activity,
+ AlertCircle,
+ AlertTriangle,
+ ArrowDown,
+ ArrowLeft,
+ ArrowUp,
+ ArrowUpRight,
+ Bell,
+ Calendar,
+ Check,
+ CheckCircle,
+ CheckCircle2,
+ CheckSquare,
+ ChevronDown,
+ ChevronLeft,
+ ChevronRight,
+ ChevronsLeft,
+ ChevronsRight,
+ ChevronsUpDown,
+ Circle,
+ CircleDot,
+ Clock,
+ Code,
+ Cpu,
+ Database,
+ ExternalLink,
+ Eye,
+ EyeOff,
+ FileText,
+ Gauge,
+ Globe,
+ GripVertical,
+ HelpCircle,
+ History,
+ Image: ImageIcon,
+ Info,
+ Key,
+ Layers,
+ Link,
+ Lock,
+ LogOut,
+ Mail,
+ Menu,
+ MessageCircle,
+ MoreVertical,
+ Pause,
+ PauseCircle,
+ Play,
+ PlayCircle,
+ PlusCircle,
+ RefreshCw,
+ Ruler,
+ Search,
+ Settings,
+ Square,
+ Trash2,
+ TrendingUp,
+ Upload,
+ User,
+ Users,
+ Wifi,
+ Wrench,
+ X,
+};
+
+/**
+ * Theme-aware icon component wrapping Lucide icons
+ *
+ * @param {string} name - Lucide icon name (e.g., "Bell", "Check", "ArrowLeft")
+ * @param {number} size - Icon size in pixels (default: 20)
+ * @param {string} color - Direct color value OR theme path like "primary.contrastText"
+ * @param {number} strokeWidth - Stroke width (default: 1.5)
+ * @param {string} fill - Fill color (default: "none")
+ */
+const Icon = ({ name, size = 20, color, strokeWidth = 1.5, fill = "none", ...props }) => {
+ const theme = useTheme();
+
+ // Resolve color from theme path or use direct value
+ const resolveColor = (colorValue) => {
+ if (!colorValue) {
+ return theme.palette.primary.contrastTextTertiary; // Default icon color
+ }
+
+ // If it's a theme path like "primary.contrastText"
+ if (typeof colorValue === "string" && colorValue.includes(".")) {
+ const parts = colorValue.split(".");
+ let resolved = theme.palette;
+ for (const part of parts) {
+ resolved = resolved?.[part];
+ }
+ return resolved || colorValue;
+ }
+
+ // Direct color value
+ return colorValue;
+ };
+
+ const LucideIcon = iconComponents[name];
+
+ if (!LucideIcon) {
+ console.warn(`Icon "${name}" not found in Icon component`);
+ return null;
+ }
+
+ return (
+
+ );
+};
+
+Icon.propTypes = {
+ name: PropTypes.string.isRequired,
+ size: PropTypes.number,
+ color: PropTypes.string,
+ strokeWidth: PropTypes.number,
+ fill: PropTypes.string,
+};
+
+export default Icon;
diff --git a/client/src/Components/v1/Inputs/Checkbox/index.jsx b/client/src/Components/v1/Inputs/Checkbox/index.jsx
index 64f3cc3db..caa6830e7 100644
--- a/client/src/Components/v1/Inputs/Checkbox/index.jsx
+++ b/client/src/Components/v1/Inputs/Checkbox/index.jsx
@@ -1,8 +1,7 @@
import PropTypes from "prop-types";
import { FormControlLabel, Checkbox as MuiCheckbox } from "@mui/material";
import { useTheme } from "@emotion/react";
-import CheckboxOutline from "../../../../assets/icons/checkbox-outline.svg?react";
-import CheckboxFilled from "../../../../assets/icons/checkbox-filled.svg?react";
+import Icon from "../../Icon";
/**
* Checkbox Component
@@ -65,8 +64,8 @@ const Checkbox = ({
name={name}
value={value}
onChange={onChange}
- icon={ }
- checkedIcon={ }
+ icon={ }
+ checkedIcon={ }
inputProps={{
"aria-label": "controlled checkbox",
id: id,
diff --git a/client/src/Components/v1/Inputs/ImageUpload/index.jsx b/client/src/Components/v1/Inputs/ImageUpload/index.jsx
index e7415bf8f..d224418e0 100644
--- a/client/src/Components/v1/Inputs/ImageUpload/index.jsx
+++ b/client/src/Components/v1/Inputs/ImageUpload/index.jsx
@@ -1,11 +1,10 @@
// Components
import { Box, Stack, Typography } from "@mui/material";
-import CloudUploadIcon from "@mui/icons-material/CloudUpload";
+import Icon from "../../Icon";
import Image from "../../Image/index.jsx";
import TextField from "@mui/material/TextField";
import IconButton from "@mui/material/IconButton";
import ProgressUpload from "../../ProgressBars/index.jsx";
-import ImageIcon from "@mui/icons-material/Image";
// Utils
import PropTypes from "prop-types";
@@ -176,7 +175,10 @@ const ImageUpload = ({
boxShadow: theme.shape.boxShadow,
}}
>
-
+
{(localError || progress.isLoading || progress.value !== 0) && (
}
+ icon={
+
+ }
label={file?.name || "Upload failed"}
size={file?.size}
progress={progress.value}
diff --git a/client/src/Components/v1/Inputs/Radio/index.jsx b/client/src/Components/v1/Inputs/Radio/index.jsx
index 575c1af74..b6bd4e69c 100644
--- a/client/src/Components/v1/Inputs/Radio/index.jsx
+++ b/client/src/Components/v1/Inputs/Radio/index.jsx
@@ -1,7 +1,7 @@
import PropTypes from "prop-types";
import { FormControlLabel, Radio as MUIRadio, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
-import RadioChecked from "../../../../assets/icons/radio-checked.svg?react";
+import Icon from "../../Icon";
import "./index.css";
/**
@@ -47,7 +47,12 @@ const Radio = ({
}
+ checkedIcon={
+
+ }
sx={{
color: "transparent",
width: 16,
diff --git a/client/src/Components/v1/Inputs/Search/index.jsx b/client/src/Components/v1/Inputs/Search/index.jsx
index 7a3771d47..4371ef971 100644
--- a/client/src/Components/v1/Inputs/Search/index.jsx
+++ b/client/src/Components/v1/Inputs/Search/index.jsx
@@ -8,7 +8,7 @@ import {
Checkbox,
} from "@mui/material";
import { useTheme } from "@emotion/react";
-import SearchIcon from "../../../../assets/icons/search.svg?react";
+import Icon from "../../Icon";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import FieldWrapper from "../FieldWrapper/index.jsx";
@@ -31,20 +31,13 @@ import FieldWrapper from "../FieldWrapper/index.jsx";
const SearchAdornment = () => {
const theme = useTheme();
return (
-
-
+
+
);
};
@@ -85,7 +78,12 @@ const Search = ({
const enhancedOptions = React.useMemo(() => {
return multiple && isAdorned
? [
- { [filteredBy]: t("selectAll"), isSelectAll: true, _id: "select_all" },
+ {
+ [filteredBy]: t("selectAll"),
+ isSelectAll: true,
+ _id: "select_all",
+ id: "select_all",
+ },
...options,
]
: options;
@@ -93,7 +91,7 @@ const Search = ({
const isOptionSelected = (option) => {
if (!multiple && !isAdorned) return false;
if (Array.isArray(value)) {
- return value.some((item) => item._id === option._id);
+ return value.some((item) => (item._id ?? item.id) === (option._id ?? option.id));
}
return false;
};
@@ -145,7 +143,9 @@ const Search = ({
disableClearable
options={enhancedOptions}
getOptionLabel={(option) => option[filteredBy]}
- isOptionEqualToValue={(option, value) => option._id === value._id} // Compare by unique identifier
+ isOptionEqualToValue={(option, value) =>
+ (option._id ?? option.id) === (value?._id ?? value?.id)
+ } // Compare by unique identifier
renderInput={(params) => (
{
- return option._id;
+ return option._id ?? option.id;
}}
renderOption={(props, option) => {
const { key, ...optionProps } = props;
diff --git a/client/src/Components/v1/Inputs/Select/index.jsx b/client/src/Components/v1/Inputs/Select/index.jsx
index 37ce30dc1..0f96a2ff6 100644
--- a/client/src/Components/v1/Inputs/Select/index.jsx
+++ b/client/src/Components/v1/Inputs/Select/index.jsx
@@ -1,7 +1,7 @@
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { MenuItem, Select as MuiSelect, Stack, Typography } from "@mui/material";
-import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
+import Icon from "../../Icon";
import FieldWrapper from "../FieldWrapper/index.jsx";
import "./index.css";
@@ -62,6 +62,7 @@ const Select = ({
fieldWrapperSx = {},
}) => {
const theme = useTheme();
+ const getItemValue = (item) => item?._id ?? item?.id;
const itemStyles = {
fontSize: "var(--env-var-font-size-medium)",
color: theme.palette.primary.contrastTextTertiary,
@@ -97,7 +98,13 @@ const Select = ({
error={error}
name={name}
inputProps={{ id: id }}
- IconComponent={KeyboardArrowDownIcon}
+ IconComponent={(props) => (
+
+ )}
MenuProps={{ disableScrollLock: true }}
sx={{
fontSize: 13,
@@ -123,7 +130,7 @@ const Select = ({
...sx,
}}
renderValue={(selected) => {
- const selectedItem = items.find((item) => item._id === selected);
+ const selectedItem = items.find((item) => getItemValue(item) === selected);
const displayName = selectedItem ? selectedItem.name : placeholder;
return (
)}
- {items.map((item) => (
-
- {item.name}
-
- ))}
+ {items
+ .map((item) => {
+ const itemValue = getItemValue(item);
+ if (itemValue === undefined || itemValue === null) {
+ return null;
+ }
+ return (
+
+ {item.name}
+
+ );
+ })
+ .filter(Boolean)}
);
@@ -179,8 +194,8 @@ Select.propTypes = {
.isRequired,
items: PropTypes.arrayOf(
PropTypes.shape({
- _id: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool])
- .isRequired,
+ _id: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]),
+ id: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]),
name: PropTypes.string.isRequired,
})
diff --git a/client/src/Components/v1/Inputs/TextInput/Adornments/index.jsx b/client/src/Components/v1/Inputs/TextInput/Adornments/index.jsx
index 4e4bbd2af..078684405 100644
--- a/client/src/Components/v1/Inputs/TextInput/Adornments/index.jsx
+++ b/client/src/Components/v1/Inputs/TextInput/Adornments/index.jsx
@@ -1,10 +1,7 @@
import { Stack, Typography, InputAdornment, IconButton } from "@mui/material";
import { useTheme } from "@mui/material/styles";
import PropTypes from "prop-types";
-import VisibilityOff from "@mui/icons-material/VisibilityOff";
-import Visibility from "@mui/icons-material/Visibility";
-import ReorderRoundedIcon from "@mui/icons-material/ReorderRounded";
-import DeleteIcon from "../../../../../assets/icons/trash-bin.svg?react";
+import Icon from "../../../Icon";
export const HttpAdornment = ({ https }) => {
const theme = useTheme();
@@ -56,7 +53,17 @@ export const PasswordEndAdornment = ({ fieldType, setFieldType }) => {
},
}}
>
- {fieldType === "password" ? : }
+ {fieldType === "password" ? (
+
+ ) : (
+
+ )}
);
diff --git a/client/src/Components/v1/Label/index.jsx b/client/src/Components/v1/Label/index.jsx
index 8c4278fed..2ed9b93bf 100644
--- a/client/src/Components/v1/Label/index.jsx
+++ b/client/src/Components/v1/Label/index.jsx
@@ -147,11 +147,9 @@ const StatusLabel = ({ status, text, customStyles }) => {
}}
>
);
diff --git a/client/src/Components/v1/Layouts/HomeLayout/index.css b/client/src/Components/v1/Layouts/HomeLayout/index.css
index 56606a935..61808aa57 100644
--- a/client/src/Components/v1/Layouts/HomeLayout/index.css
+++ b/client/src/Components/v1/Layouts/HomeLayout/index.css
@@ -3,6 +3,8 @@
min-height: 100vh;
margin: 0 auto;
padding: 0;
+ overflow-x: hidden;
+ width: 100%;
}
/* TODO go for this approach for responsiveness. The aside needs to be taken care of */
@@ -12,15 +14,7 @@
}
} */
-/* .home-layout aside {
- position: sticky;
- top: 0;
- left: 0;
- height: 100vh;
- max-width: var(--env-var-side-bar-width);
-} */
-
-.home-layout > div {
+.home-layout > .home-content-wrapper {
min-height: calc(100vh - var(--env-var-spacing-2) * 2);
flex: 1;
}
@@ -30,4 +24,6 @@
max-width: 1400px;
margin: 0 auto;
flex: 1;
+ min-width: 0;
+ overflow-x: hidden;
}
diff --git a/client/src/Components/v1/Layouts/HomeLayout/index.jsx b/client/src/Components/v1/Layouts/HomeLayout/index.jsx
index 3e63aff37..b078fe048 100644
--- a/client/src/Components/v1/Layouts/HomeLayout/index.jsx
+++ b/client/src/Components/v1/Layouts/HomeLayout/index.jsx
@@ -1,17 +1,27 @@
import Sidebar from "../../Sidebar/index.jsx";
import { Outlet } from "react-router";
-import { Stack } from "@mui/material";
+import { Box, Stack } from "@mui/material";
+import { useSidebar } from "@/Hooks/useSidebar.js";
import "./index.css";
const HomeLayout = () => {
+ const { width, transition } = useSidebar();
+
return (
+ {/* Spacer for fixed sidebar */}
+
diff --git a/client/src/Components/v1/MonitorActions/index.jsx b/client/src/Components/v1/MonitorActions/index.jsx
index 23cebc972..f63fc23fe 100644
--- a/client/src/Components/v1/MonitorActions/index.jsx
+++ b/client/src/Components/v1/MonitorActions/index.jsx
@@ -1,7 +1,7 @@
import * as React from "react";
import Button from "@mui/material/Button";
import ButtonGroup from "@mui/material/ButtonGroup";
-import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
+import Icon from "../Icon";
import ClickAwayListener from "@mui/material/ClickAwayListener";
import Grow from "@mui/material/Grow";
import Paper from "@mui/material/Paper";
@@ -11,7 +11,7 @@ import MenuList from "@mui/material/MenuList";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { createToast } from "../../../Utils/toastUtils.jsx";
-import { useExportMonitors } from "../../../Hooks/v1/monitorHooks.js";
+import { useExportMonitors } from "../../../Hooks/monitorHooks.js";
const MonitorActions = ({ isLoading }) => {
const [open, setOpen] = React.useState(false);
@@ -70,7 +70,10 @@ const MonitorActions = ({ isLoading }) => {
aria-haspopup="menu"
onClick={handleToggle}
>
-
+
@@ -75,10 +72,15 @@ const MonitorDetailsControlHeader = ({
variant="contained"
color="secondary"
loading={isSending}
- startIcon={ }
+ startIcon={
+
+ }
disabled={isTestNotificationsDisabled}
onClick={() => {
- testAllNotifications({ monitorId: monitor?._id });
+ testAllNotifications({ monitorId: monitor?.id });
}}
sx={{
whiteSpace: "nowrap",
@@ -92,7 +94,7 @@ const MonitorDetailsControlHeader = ({
variant="contained"
color="secondary"
onClick={(e) => {
- navigate(`/incidents/${monitor?._id}`);
+ navigate(`/incidents/${monitor?.id}`);
}}
>
{t("menu.incidents")}
@@ -103,24 +105,39 @@ const MonitorDetailsControlHeader = ({
color="secondary"
loading={isPausing}
startIcon={
- monitor?.isActive ? :
+ monitor?.isActive ? (
+
+ ) : (
+
+ )
}
onClick={() => {
pauseMonitor({
- monitorId: monitor?._id,
+ monitorId: monitor?.id,
triggerUpdate,
});
}}
>
- {monitor?.isActive ? "Pause" : "Resume"}
+ {monitor?.isActive ? t("pause") : t("resume")}
)}
{isAdmin && (
}
- onClick={() => navigate(`/${path}/configure/${monitor._id}`)}
+ startIcon={
+
+ }
+ onClick={() => navigate(`/${path}/configure/${monitor.id}`)}
>
Configure
diff --git a/client/src/Components/v1/MonitorDetailsControlHeader/status.jsx b/client/src/Components/v1/MonitorDetailsControlHeader/status.jsx
index d9b7afc40..c6a3cfcfa 100644
--- a/client/src/Components/v1/MonitorDetailsControlHeader/status.jsx
+++ b/client/src/Components/v1/MonitorDetailsControlHeader/status.jsx
@@ -4,10 +4,10 @@ import PulseDot from "../Animated/PulseDot.jsx";
import Typography from "@mui/material/Typography";
import Dot from "../Dot/index.jsx";
// Utils
-import { formatDurationRounded } from "../../../Utils/timeUtils.js";
+import { formatDurationRounded } from "../../../Utils/timeUtilsLegacy.js";
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
-import { useMonitorUtils } from "../../../Hooks/v1/useMonitorUtils.js";
+import { useMonitorUtils } from "../../../Hooks/useMonitorUtils.js";
import { formatMonitorUrl } from "../../../Utils/utils.js";
/**
* Status component displays the status information of a monitor.
diff --git a/client/src/Components/v1/NotificationConfig/index.jsx b/client/src/Components/v1/NotificationConfig/index.jsx
index 58096881e..f1ed30ad1 100644
--- a/client/src/Components/v1/NotificationConfig/index.jsx
+++ b/client/src/Components/v1/NotificationConfig/index.jsx
@@ -2,7 +2,7 @@
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import Divider from "@mui/material/Divider";
-import DeleteOutlineRoundedIcon from "@mui/icons-material/DeleteOutlineRounded";
+import Icon from "../Icon";
import Search from "../Inputs/Search/index.jsx";
// Utils
@@ -31,7 +31,7 @@ const NotificationConfig = ({
setMonitor((prev) => {
return {
...prev,
- notifications: value.map((notification) => notification._id),
+ notifications: value.map((notification) => notification.id),
};
});
};
@@ -39,14 +39,14 @@ const NotificationConfig = ({
// Handlers
const handleDelete = (id) => {
const updatedNotifications = selectedNotifications.filter(
- (notification) => notification._id !== id
+ (notification) => notification.id !== id
);
setSelectedNotifications(updatedNotifications);
setMonitor((prev) => {
return {
...prev,
- notifications: updatedNotifications.map((notification) => notification._id),
+ notifications: updatedNotifications.map((notification) => notification.id),
};
});
};
@@ -57,7 +57,7 @@ const NotificationConfig = ({
useEffect(() => {
if (setNotifications) {
const toSet = setNotifications.map((notification) => {
- return notifications.find((n) => n._id === notification);
+ return notifications.find((n) => n.id === notification);
});
setSelectedNotifications(toSet);
}
@@ -94,7 +94,7 @@ const NotificationConfig = ({
{notification.notificationName}
- {
- handleDelete(notification._id);
+ handleDelete(notification.id);
}}
- sx={{ cursor: "pointer" }}
+ style={{ cursor: "pointer" }}
/>
{index < selectedNotifications.length - 1 && }
diff --git a/client/src/Components/v1/PageStateWrapper/index.jsx b/client/src/Components/v1/PageStateWrapper/index.jsx
index ac97704df..08adbe514 100644
--- a/client/src/Components/v1/PageStateWrapper/index.jsx
+++ b/client/src/Components/v1/PageStateWrapper/index.jsx
@@ -3,7 +3,7 @@ import Fallback from "../Fallback/index.jsx";
import { Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
-import { useIsAdmin } from "../../../Hooks/v1/useIsAdmin.js";
+import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
import PropTypes from "prop-types";
const PageStateWrapper = ({
networkError,
diff --git a/client/src/Components/v1/ProgressBars/index.jsx b/client/src/Components/v1/ProgressBars/index.jsx
index ad7ceb3dc..86cedcbaa 100644
--- a/client/src/Components/v1/ProgressBars/index.jsx
+++ b/client/src/Components/v1/ProgressBars/index.jsx
@@ -1,8 +1,7 @@
import { useTheme } from "@emotion/react";
import PropTypes from "prop-types";
import { Box, IconButton, LinearProgress, Stack, Typography } from "@mui/material";
-import CloseIcon from "@mui/icons-material/Close";
-import ErrorOutlineOutlinedIcon from "@mui/icons-material/ErrorOutlineOutlined";
+import Icon from "../Icon";
import "./index.css";
/**
@@ -51,7 +50,10 @@ const ProgressUpload = ({ icon, label, size, progress = 0, onClick, error }) =>
alignItems={error ? "center" : "flex-start"}
>
{error ? (
-
+
) : icon ? (
}
}
>
-
diff --git a/client/src/Components/v1/ProgressStepper/index.jsx b/client/src/Components/v1/ProgressStepper/index.jsx
index 4ab071c58..9fb0d15dc 100644
--- a/client/src/Components/v1/ProgressStepper/index.jsx
+++ b/client/src/Components/v1/ProgressStepper/index.jsx
@@ -1,15 +1,22 @@
import React from "react";
import { Step, Stepper, StepLabel, Typography } from "@mui/material";
-import RadioButtonCheckedIcon from "@mui/icons-material/RadioButtonChecked";
-import CheckCircle from "@mui/icons-material/CheckCircle";
+import Icon from "../Icon";
import PropTypes from "prop-types";
const CustomStepIcon = (props) => {
const { completed, active } = props;
return completed ? (
-
+
) : (
-
+
);
};
diff --git a/client/src/Components/v1/ShareComponent/index.jsx b/client/src/Components/v1/ShareComponent/index.jsx
index 073ed2243..476b3672f 100644
--- a/client/src/Components/v1/ShareComponent/index.jsx
+++ b/client/src/Components/v1/ShareComponent/index.jsx
@@ -1,5 +1,5 @@
import html2canvas from "html2canvas";
-import ShareIcon from "@mui/icons-material/Share";
+import Icon from "../Icon";
import { Button } from "@mui/material";
import { useTheme } from "@emotion/react";
@@ -55,7 +55,13 @@ const ShareComponent = ({ elementToCapture, fileName = "screenshot" }) => {
return (
}
+ startIcon={
+
+ }
color="success"
onClick={captureAndShare}
>
diff --git a/client/src/Components/v1/Sidebar/components/authFooter.jsx b/client/src/Components/v1/Sidebar/components/authFooter.jsx
index 2c33a2402..16b60d9f2 100644
--- a/client/src/Components/v1/Sidebar/components/authFooter.jsx
+++ b/client/src/Components/v1/Sidebar/components/authFooter.jsx
@@ -7,8 +7,7 @@ import Avatar from "../../Avatar/index.jsx";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import Divider from "@mui/material/Divider";
-import DotsVertical from "../../../../assets/icons/dots-vertical.svg?react";
-import LogoutSvg from "../../../../assets/icons/logout.svg?react";
+import Icon from "../../Icon";
import { useTheme } from "@emotion/react";
import { useSelector } from "react-redux";
@@ -81,6 +80,10 @@ const AuthFooter = ({ collapsed, accountMenuItems }) => {
gap: theme.spacing(2),
borderRadius: theme.shape.borderRadius,
pl: theme.spacing(4),
+ color: theme.palette.primary.contrastTextTertiary,
+ "& svg": {
+ stroke: theme.palette.primary.contrastTextTertiary,
+ },
}}
>
{item.icon}
@@ -160,19 +163,14 @@ const AuthFooter = ({ collapsed, accountMenuItems }) => {
ml: "50px",
"&:focus": { outline: "none" },
alignSelf: "center",
-
- "& svg": {
- width: "22px",
- height: "22px",
- },
- "& svg path": {
- /* Vertical three dots */
- stroke: theme.palette.primary.contrastTextTertiary,
- },
}}
onClick={(event) => openPopup(event)}
>
-
+
@@ -256,12 +254,13 @@ const AuthFooter = ({ collapsed, accountMenuItems }) => {
gap: theme.spacing(4),
borderRadius: theme.shape.borderRadius,
pl: theme.spacing(4),
- "& svg path": {
- stroke: theme.palette.primary.contrastTextTertiary,
- },
}}
>
-
+
{t("menu.logOut", "Log out")}
diff --git a/client/src/Components/v1/Sidebar/components/navItem.jsx b/client/src/Components/v1/Sidebar/components/navItem.jsx
index 2c569edde..170d39bee 100644
--- a/client/src/Components/v1/Sidebar/components/navItem.jsx
+++ b/client/src/Components/v1/Sidebar/components/navItem.jsx
@@ -52,12 +52,11 @@ const NavItem = ({ item, collapsed, selected, onClick }) => {
[
- { name: t("menu.uptime"), path: "uptime", icon: },
- { name: t("menu.pagespeed"), path: "pagespeed", icon: },
-
- { name: t("menu.infrastructure"), path: "infrastructure", icon: },
+ { name: t("menu.uptime"), path: "uptime", icon: },
+ { name: t("menu.pagespeed"), path: "pagespeed", icon: },
+ { name: t("menu.infrastructure"), path: "infrastructure", icon: },
{
name: t("menu.notifications"),
path: "notifications",
- icon: ,
+ icon: ,
},
- { name: t("menu.incidents"), path: "incidents", icon: },
-
- { name: t("menu.statusPages"), path: "status", icon: },
- { name: t("menu.maintenance"), path: "maintenance", icon: },
- { name: t("menu.logs"), path: "logs", icon: },
-
+ { name: t("menu.checks"), path: "checks", icon: },
+ { name: t("menu.incidents"), path: "incidents", icon: },
+ { name: t("menu.statusPages"), path: "status", icon: },
+ { name: t("menu.maintenance"), path: "maintenance", icon: },
+ { name: t("menu.logs"), path: "logs", icon: },
{
name: t("menu.settings"),
- icon: ,
+ icon: ,
path: "settings",
},
];
const getOtherMenuItems = (t) => [
- { name: t("menu.support"), path: "support", icon: },
+ { name: t("menu.support"), path: "support", icon: },
{
name: t("menu.discussions"),
path: "discussions",
- icon: ,
+ icon: ,
},
- { name: t("menu.docs"), path: "docs", icon: },
- { name: t("menu.changelog"), path: "changelog", icon: },
+ { name: t("menu.docs"), path: "docs", icon: },
+ { name: t("menu.changelog"), path: "changelog", icon: },
];
const getAccountMenuItems = (t) => [
- { name: t("menu.profile"), path: "account/profile", icon: },
- { name: t("menu.password"), path: "account/password", icon: },
- { name: t("menu.team"), path: "account/team", icon: },
+ { name: t("menu.profile"), path: "account/profile", icon: },
+ { name: t("menu.password"), path: "account/password", icon: },
+ { name: t("menu.team"), path: "account/team", icon: },
];
const Sidebar = () => {
const theme = useTheme();
const { t } = useTranslation();
const navigate = useNavigate();
- // Redux state
- const collapsed = useSelector((state) => state.ui.sidebar.collapsed);
+ const { collapsed, width, transition } = useSidebar();
const menu = getMenu(t);
const otherMenuItems = getOtherMenuItems(t);
@@ -92,20 +74,19 @@ const Sidebar = () => {
return (
diff --git a/client/src/Components/v1/StarPrompt/index.jsx b/client/src/Components/v1/StarPrompt/index.jsx
index 6151544c1..f1dd5e136 100644
--- a/client/src/Components/v1/StarPrompt/index.jsx
+++ b/client/src/Components/v1/StarPrompt/index.jsx
@@ -1,6 +1,6 @@
import React from "react";
import { Typography, IconButton, Stack, Box } from "@mui/material";
-import CloseIcon from "@mui/icons-material/Close";
+import Icon from "../Icon";
import { useTheme } from "@emotion/react";
import { useSelector, useDispatch } from "react-redux";
import { useTranslation } from "react-i18next";
@@ -67,7 +67,10 @@ const StarPrompt = ({ repoUrl = "https://github.com/bluewave-labs/checkmate" })
},
}}
>
-
+
diff --git a/client/src/Components/v1/StatBox/index.jsx b/client/src/Components/v1/StatBox/index.jsx
index 1b5f1cf6f..e72f947b3 100644
--- a/client/src/Components/v1/StatBox/index.jsx
+++ b/client/src/Components/v1/StatBox/index.jsx
@@ -2,7 +2,7 @@ import { Stack, Typography } from "@mui/material";
import Image from "../Image/index.jsx";
import { useTheme } from "@mui/material/styles";
import PropTypes from "prop-types";
-import { useMonitorUtils } from "../../../Hooks/v1/useMonitorUtils.js";
+import { useMonitorUtils } from "../../../Hooks/useMonitorUtils.js";
/**
* StatBox Component
diff --git a/client/src/Components/v1/Table/TablePagination/index.jsx b/client/src/Components/v1/Table/TablePagination/index.jsx
index 8a5af0cd5..77ee5ded1 100644
--- a/client/src/Components/v1/Table/TablePagination/index.jsx
+++ b/client/src/Components/v1/Table/TablePagination/index.jsx
@@ -2,7 +2,7 @@ import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { Stack, TablePagination, Typography } from "@mui/material";
import { TablePaginationActions } from "./Actions/index.jsx";
-import SelectorVertical from "../../../../assets/icons/selector-vertical.svg?react";
+import Icon from "../../Icon";
Pagination.propTypes = {
paginationLabel: PropTypes.string, // Label for the pagination.
@@ -100,7 +100,12 @@ function Pagination({
},
},
inputProps: { id: "pagination-dropdown" },
- IconComponent: SelectorVertical,
+ IconComponent: () => (
+
+ ),
sx: {
ml: theme.spacing(4),
mr: theme.spacing(12),
diff --git a/client/src/Components/v1/Toast/index.jsx b/client/src/Components/v1/Toast/index.jsx
index 5d66fca3f..d409cec81 100644
--- a/client/src/Components/v1/Toast/index.jsx
+++ b/client/src/Components/v1/Toast/index.jsx
@@ -3,19 +3,31 @@ import IconButton from "@mui/material/IconButton";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import ToastBody from "./body.jsx";
-import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
-import ErrorOutlineOutlinedIcon from "@mui/icons-material/ErrorOutlineOutlined";
-import WarningAmberOutlinedIcon from "@mui/icons-material/WarningAmberOutlined";
-import CloseIcon from "@mui/icons-material/Close";
+import Icon from "../Icon";
// Utils
import { useTheme } from "@emotion/react";
import PropTypes from "prop-types";
const icons = {
- info: ,
- error: ,
- warning: ,
+ info: (
+
+ ),
+ error: (
+
+ ),
+ warning: (
+
+ ),
};
const Toast = ({ variant, title, body, onClick, hasDismiss, hasIcon }) => {
@@ -50,7 +62,10 @@ const Toast = ({ variant, title, body, onClick, hasDismiss, hasIcon }) => {
)}
{title && (
-
+
)}
@@ -63,7 +78,10 @@ const Toast = ({ variant, title, body, onClick, hasDismiss, hasIcon }) => {
{!title && (
-
+
)}
diff --git a/client/src/Components/v2/Arrows/ArrowLeft.tsx b/client/src/Components/v2/Arrows/ArrowLeft.tsx
deleted file mode 100644
index 01d1342c8..000000000
--- a/client/src/Components/v2/Arrows/ArrowLeft.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import LeftArrow from "@/assets/icons/left-arrow.svg?react";
-import LeftArrowDouble from "@/assets/icons/left-arrow-double.svg?react";
-import LeftArrowLong from "@/assets/icons/left-arrow-long.svg?react";
-
-export const ArrowLeft = ({
- type,
- color = "#667085",
- ...props
-}: {
- type?: string;
- color?: string | undefined;
- [key: string]: any;
-}) => {
- if (type === "double") {
- return (
-
- );
- } else if (type === "long") {
- return (
-
- );
- } else {
- return (
-
- );
- }
-};
diff --git a/client/src/Components/v2/Arrows/ArrowRight.tsx b/client/src/Components/v2/Arrows/ArrowRight.tsx
deleted file mode 100644
index c73165f16..000000000
--- a/client/src/Components/v2/Arrows/ArrowRight.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import RightArrow from "@/assets/icons/right-arrow.svg?react";
-import RightArrowDouble from "@/assets/icons/right-arrow-double.svg?react";
-
-export const ArrowRight = ({
- type,
- color = "#667085",
- ...props
-}: {
- type?: string;
- color?: string | undefined;
- [key: string]: any;
-}) => {
- if (type === "double") {
- return (
-
- );
- } else {
- return (
-
- );
- }
-};
diff --git a/client/src/Components/v2/Auth/AuthBasePage.tsx b/client/src/Components/v2/Auth/AuthBasePage.tsx
deleted file mode 100644
index ba4230a6d..000000000
--- a/client/src/Components/v2/Auth/AuthBasePage.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import Stack from "@mui/material/Stack";
-import Box from "@mui/material/Box";
-import { HeaderAuth } from "@/Components/v2/Auth";
-import Logo from "@/assets/icons/checkmate-icon.svg?react";
-
-import type { StackProps } from "@mui/material/Stack";
-import { useTheme } from "@mui/material/styles";
-import { Typography } from "@mui/material";
-
-interface AuthBasePageProps extends StackProps {
- title?: string;
- subtitle?: string;
- children: React.ReactNode;
-}
-
-export const AuthBasePage: React.FC = ({
- children,
- title,
- subtitle,
- ...props
-}) => {
- const theme = useTheme();
- return (
-
-
-
-
-
-
- {title}
- {subtitle}
- {children}
-
-
- );
-};
diff --git a/client/src/Components/v2/Auth/HeaderAuth.jsx b/client/src/Components/v2/Auth/HeaderAuth.jsx
deleted file mode 100644
index 4e437cd7e..000000000
--- a/client/src/Components/v2/Auth/HeaderAuth.jsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import Stack from "@mui/material/Stack";
-import Typography from "@mui/material/Typography";
-import Logo from "@/assets/icons/checkmate-icon.svg?react";
-
-import { useTheme } from "@mui/material/styles";
-import { useTranslation } from "react-i18next";
-import { LanguageSelector, ThemeSwitch } from "@/Components/v2/Inputs";
-
-export const HeaderAuth = () => {
- const theme = useTheme();
- const { t } = useTranslation();
- return (
-
-
-
-
- );
-};
diff --git a/client/src/Components/v2/Auth/index.jsx b/client/src/Components/v2/Auth/index.jsx
deleted file mode 100644
index 84b742113..000000000
--- a/client/src/Components/v2/Auth/index.jsx
+++ /dev/null
@@ -1,2 +0,0 @@
-export { HeaderAuth } from "./HeaderAuth";
-export { AuthBasePage } from "./AuthBasePage";
diff --git a/client/src/Components/v2/DesignElements/BaseBox.tsx b/client/src/Components/v2/DesignElements/BaseBox.tsx
deleted file mode 100644
index 14c4af1c4..000000000
--- a/client/src/Components/v2/DesignElements/BaseBox.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import Box from "@mui/material/Box";
-import { useTheme } from "@mui/material/styles";
-import type { SxProps } from "@mui/material/styles";
-
-type BaseBoxProps = React.PropsWithChildren<{ sx?: SxProps }>;
-
-export const BaseBox: React.FC = ({ children, sx }) => {
- const theme = useTheme();
- return (
-
- {children}
-
- );
-};
diff --git a/client/src/Components/v2/DesignElements/BasePage.tsx b/client/src/Components/v2/DesignElements/BasePage.tsx
deleted file mode 100644
index f458cbb0a..000000000
--- a/client/src/Components/v2/DesignElements/BasePage.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import Stack from "@mui/material/Stack";
-import { ErrorFallback, EmptyFallback } from "./Fallback";
-
-import type { StackProps } from "@mui/material/Stack";
-import { useTheme } from "@mui/material/styles";
-import { useTranslation } from "react-i18next";
-interface BasePageProps extends StackProps {
- children: React.ReactNode;
-}
-
-export const BasePage: React.FC = ({
- children,
- ...props
-}: {
- children: React.ReactNode;
-}) => {
- const theme = useTheme();
- return (
-
- {children}
-
- );
-};
-
-interface BasePageWithStatesProps extends StackProps {
- loading: boolean;
- error: any;
- items: any[];
- page: string;
- actionLink?: string;
- children: React.ReactNode;
-}
-
-const isEmpty = (items: any[]) => {
- if (!items) return true;
- if (Array.isArray(items) && items.length === 0) return true;
- return false;
-};
-
-export const BasePageWithStates: React.FC = ({
- loading,
- error,
- items,
- page,
- actionLink,
- children,
- ...props
-}: BasePageWithStatesProps) => {
- const { t } = useTranslation();
-
- if (loading) {
- return null;
- }
-
- if (error) {
- return (
-
- );
- }
-
- if (isEmpty(items)) {
- return (
-
- );
- }
-
- return {children} ;
-};
diff --git a/client/src/Components/v2/DesignElements/Fallback.tsx b/client/src/Components/v2/DesignElements/Fallback.tsx
deleted file mode 100644
index f7b48a113..000000000
--- a/client/src/Components/v2/DesignElements/Fallback.tsx
+++ /dev/null
@@ -1,158 +0,0 @@
-import Box from "@mui/material/Box";
-import Stack from "@mui/material/Stack";
-import OutputAnimation from "@/assets/Animations/output.gif";
-import DarkmodeOutput from "@/assets/Animations/darkmodeOutput.gif";
-import Typography from "@mui/material/Typography";
-import { BulletPointCheck } from "@/Components/v2/DesignElements";
-import { Button } from "@/Components/v2/Inputs";
-
-import { useNavigate } from "react-router";
-import { useMediaQuery } from "@mui/material";
-import { useTheme } from "@mui/material/styles";
-import { useSelector } from "react-redux";
-
-import type { BoxProps } from "@mui/material";
-
-interface BaseFallbackProps extends BoxProps {
- children: React.ReactNode;
-}
-
-export const BaseFallback: React.FC = ({ children, ...props }) => {
- const theme = useTheme();
- const mode = useSelector((state: any) => state.ui.mode);
- const isSmall = useMediaQuery(theme.breakpoints.down("md"));
-
- return (
-
-
-
-
-
- {children}
-
-
-
- );
-};
-
-export const ErrorFallback = ({
- title,
- subtitle,
-}: {
- title: string;
- subtitle: string;
-}) => {
- const theme = useTheme();
- return (
-
-
- {title}
-
- {subtitle}
-
- );
-};
-
-export const EmptyFallback = ({
- page,
- title,
- bullets,
- actionButtonText,
- actionLink,
-}: {
- page: string;
- title: string;
- bullets: any;
- actionButtonText: string;
- actionLink: string;
-}) => {
- const theme = useTheme();
- const navigate = useNavigate();
- return (
-
-
-
- {title}
-
-
- {bullets?.map((bullet: string, index: number) => (
-
- ))}
-
-
- navigate(actionLink)}
- >
- {actionButtonText}
-
-
-
-
- );
-};
diff --git a/client/src/Components/v2/DesignElements/SplitBox.tsx b/client/src/Components/v2/DesignElements/SplitBox.tsx
deleted file mode 100644
index f5a09d8f5..000000000
--- a/client/src/Components/v2/DesignElements/SplitBox.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import { Fragment } from "react";
-import Stack from "@mui/material/Stack";
-import Box from "@mui/material/Box";
-import Typography from "@mui/material/Typography";
-import { useTheme } from "@mui/material/styles";
-import { useMediaQuery } from "@mui/material";
-export const SplitBox = ({
- left,
- right,
-}: {
- left: React.ReactNode;
- right: React.ReactNode;
-}) => {
- const theme = useTheme();
- const isSmall = useMediaQuery(theme.breakpoints.down("md"));
- return (
-
-
- {left}
-
-
- {right}
-
-
- );
-};
-
-export const ConfigBox = ({
- title,
- subtitle,
- rightContent,
-}: {
- title: string;
- subtitle: string;
- rightContent: React.ReactNode;
-}) => {
- return (
-
-
- {title}
-
- {subtitle}
-
- }
- right={rightContent}
- />
- );
-};
diff --git a/client/src/Components/v2/DesignElements/StatBox.tsx b/client/src/Components/v2/DesignElements/StatBox.tsx
deleted file mode 100644
index 3450444b2..000000000
--- a/client/src/Components/v2/DesignElements/StatBox.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import Stack from "@mui/material/Stack";
-import Typography from "@mui/material/Typography";
-import { useTheme } from "@mui/material/styles";
-import { useMediaQuery } from "@mui/material";
-import type { PaletteKey } from "@/Utils/Theme/v2/theme";
-import { BaseBox } from "@/Components/v2/DesignElements";
-
-type GradientBox = React.PropsWithChildren<{ palette?: PaletteKey }>;
-
-export const GradientBox: React.FC = ({ children, palette }) => {
- const theme = useTheme();
- const isSmall = useMediaQuery(theme.breakpoints.down("md"));
- const bg = palette
- ? `linear-gradient(to bottom right, ${theme.palette[palette].main} 30%, ${theme.palette[palette].lowContrast} 70%)`
- : `linear-gradient(340deg, ${theme.palette.tertiary.main} 10%, ${theme.palette.primary.main} 45%)`;
-
- return (
-
- {children}
-
- );
-};
-
-type StatBoxProps = React.PropsWithChildren<{
- title: string;
- subtitle: string;
- palette?: PaletteKey;
-}>;
-
-export const StatBox: React.FC = ({
- title,
- subtitle,
- palette,
- children,
-}) => {
- const theme = useTheme();
- const textColor = palette ? theme.palette[palette].contrastText : "inherit";
-
- return (
-
-
- {title}
- {subtitle}
- {children}
-
-
- );
-};
diff --git a/client/src/Components/v2/DesignElements/StatusLabel.tsx b/client/src/Components/v2/DesignElements/StatusLabel.tsx
deleted file mode 100644
index e4d288e63..000000000
--- a/client/src/Components/v2/DesignElements/StatusLabel.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import Box from "@mui/material/Box";
-import { BaseBox } from "@/Components/v2/DesignElements";
-import type { MonitorStatus } from "@/Types/Monitor";
-
-import { getStatusPalette } from "@/Utils/v2/MonitorUtils";
-import { useTheme } from "@mui/material/styles";
-
-export const StatusLabel = ({
- status,
- isActive,
-}: {
- status: MonitorStatus;
- isActive?: boolean;
-}) => {
- const theme = useTheme();
- const palette = getStatusPalette(status);
- const transformedText = status.charAt(0).toUpperCase() + status.slice(1).toLowerCase();
-
- return (
-
-
- {isActive === false ? "Paused" : transformedText}
-
- );
-};
diff --git a/client/src/Components/v2/DesignElements/Table.tsx b/client/src/Components/v2/DesignElements/Table.tsx
deleted file mode 100644
index bf75fd9bf..000000000
--- a/client/src/Components/v2/DesignElements/Table.tsx
+++ /dev/null
@@ -1,215 +0,0 @@
-import Paper from "@mui/material/Paper";
-import Table from "@mui/material/Table";
-import TableBody from "@mui/material/TableBody";
-import TableCell from "@mui/material/TableCell";
-import TableContainer from "@mui/material/TableContainer";
-import TableHead from "@mui/material/TableHead";
-import TableRow from "@mui/material/TableRow";
-
-import IconButton from "@mui/material/IconButton";
-import LastPageIcon from "@mui/icons-material/LastPage";
-import FirstPageIcon from "@mui/icons-material/FirstPage";
-import KeyboardArrowLeft from "@mui/icons-material/KeyboardArrowLeft";
-import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight";
-
-import Box from "@mui/material/Box";
-import TablePagination from "@mui/material/TablePagination";
-import type { TablePaginationProps } from "@mui/material/TablePagination";
-
-import { useTheme } from "@mui/material/styles";
-import { useMediaQuery } from "@mui/material";
-export type Header = {
- id: number | string;
- content: React.ReactNode;
- onClick?: (event: React.MouseEvent, row: T) => void;
- render: (row: T) => React.ReactNode;
-};
-
-type DataTableProps = {
- headers: Header[];
- data: T[];
- onRowClick?: (row: T) => void;
-};
-
-export function DataTable<
- T extends {
- id?: string | number;
- _id?: string | number;
- onRowClick?: (row: T) => void;
- },
->({ headers, data, onRowClick }: DataTableProps) {
- const theme = useTheme();
- if (data.length === 0 || headers.length === 0) return No data
;
- return (
-
-
-
-
- {headers.map((header, idx) => {
- return (
-
- {header.content}
-
- );
- })}
-
-
-
- {data.map((row) => {
- const key = row.id || row._id || Math.random();
-
- return (
- (onRowClick ? onRowClick(row) : null)}
- >
- {headers.map((header, index) => {
- return (
- header.onClick!(e, row) : undefined
- }
- >
- {header.render(row)}
-
- );
- })}
-
- );
- })}
-
-
-
- );
-}
-
-interface TablePaginationActionsProps {
- count: number;
- page: number;
- rowsPerPage: number;
- onPageChange: (event: React.MouseEvent, newPage: number) => void;
-}
-
-function TablePaginationActions(props: TablePaginationActionsProps) {
- const theme = useTheme();
- const { count, page, rowsPerPage, onPageChange } = props;
-
- const handleFirstPageButtonClick = (event: React.MouseEvent) => {
- onPageChange(event, 0);
- };
-
- const handleBackButtonClick = (event: React.MouseEvent) => {
- onPageChange(event, page - 1);
- };
-
- const handleNextButtonClick = (event: React.MouseEvent) => {
- onPageChange(event, page + 1);
- };
-
- const handleLastPageButtonClick = (event: React.MouseEvent) => {
- onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1));
- };
-
- return (
-
-
- {theme.direction === "rtl" ? : }
-
-
- {theme.direction === "rtl" ? : }
-
- = Math.ceil(count / rowsPerPage) - 1}
- aria-label="next page"
- >
- {theme.direction === "rtl" ? : }
-
- = Math.ceil(count / rowsPerPage) - 1}
- aria-label="last page"
- >
- {theme.direction === "rtl" ? : }
-
-
- );
-}
-
-export const Pagination: React.FC = ({ ...props }) => {
- const isSmall = useMediaQuery((theme: any) => theme.breakpoints.down("sm"));
- const theme = useTheme();
- return (
-
- );
-};
diff --git a/client/src/Components/v2/DesignElements/index.tsx b/client/src/Components/v2/DesignElements/index.tsx
deleted file mode 100644
index 3574daffb..000000000
--- a/client/src/Components/v2/DesignElements/index.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-export { SplitBox as HorizontalSplitBox, ConfigBox } from "./SplitBox";
-export { BasePage, BasePageWithStates } from "./BasePage";
-export { BGBox, UpStatusBox, DownStatusBox, PausedStatusBox } from "./StatusBox";
-export { DataTable as Table, Pagination } from "./Table";
-export { GradientBox, StatBox } from "./StatBox";
-export { BaseBox } from "./BaseBox";
-export { StatusLabel } from "./StatusLabel";
-export { BaseFallback, ErrorFallback, EmptyFallback } from "./Fallback";
-export { BulletPointCheck } from "./BulletPointCheck";
diff --git a/client/src/Components/v2/Inputs/AutoComplete.tsx b/client/src/Components/v2/Inputs/AutoComplete.tsx
deleted file mode 100644
index 73afa365a..000000000
--- a/client/src/Components/v2/Inputs/AutoComplete.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import Autocomplete from "@mui/material/Autocomplete";
-import type { AutocompleteProps } from "@mui/material/Autocomplete";
-import { TextInput } from "@/Components/v2/Inputs/TextInput";
-import { CheckboxInput } from "@/Components/v2/Inputs/Checkbox";
-import ListItem from "@mui/material/ListItem";
-import { useTheme } from "@mui/material/styles";
-
-type AutoCompleteInputProps = Omit<
- AutocompleteProps,
- "renderInput"
-> & {
- renderInput?: AutocompleteProps["renderInput"];
-};
-
-export const AutoCompleteInput: React.FC = ({ ...props }) => {
- const theme = useTheme();
- return (
- (
-
- )}
- getOptionKey={(option) => option._id}
- renderTags={() => null}
- renderOption={(props, option, { selected }) => {
- const { key, ...optionProps } = props;
- return (
-
-
- {option.name}
-
- );
- }}
- sx={{
- "&.MuiAutocomplete-root .MuiAutocomplete-input": {
- padding: `0 ${theme.spacing(5)}`,
- },
- }}
- />
- );
-};
diff --git a/client/src/Components/v2/Inputs/Button.tsx b/client/src/Components/v2/Inputs/Button.tsx
deleted file mode 100644
index 2f28e8eda..000000000
--- a/client/src/Components/v2/Inputs/Button.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import Button from "@mui/material/Button";
-import type { ButtonProps } from "@mui/material/Button";
-
-export const ButtonInput: React.FC = ({ sx, ...props }) => {
- return (
-
- );
-};
diff --git a/client/src/Components/v2/Inputs/ButtonGroup.tsx b/client/src/Components/v2/Inputs/ButtonGroup.tsx
deleted file mode 100644
index 3d8acd8cc..000000000
--- a/client/src/Components/v2/Inputs/ButtonGroup.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import ButtonGroup from "@mui/material/ButtonGroup";
-import type { ButtonGroupProps } from "@mui/material/ButtonGroup";
-export const ButtonGroupInput: React.FC = ({
- orientation,
- ...props
-}) => {
- return (
-
- );
-};
diff --git a/client/src/Components/v2/Inputs/Checkbox.tsx b/client/src/Components/v2/Inputs/Checkbox.tsx
deleted file mode 100644
index bc2cedd03..000000000
--- a/client/src/Components/v2/Inputs/Checkbox.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import Checkbox from "@mui/material/Checkbox";
-import type { CheckboxProps } from "@mui/material/Checkbox";
-import CheckboxOutline from "@/assets/icons/checkbox-outline.svg?react";
-import CheckboxFilled from "@/assets/icons/checkbox-filled.svg?react";
-import { useTheme } from "@mui/material/styles";
-type CheckboxInputProps = CheckboxProps & {
- label?: string;
-};
-
-export const CheckboxInput: React.FC = ({ label, ...props }) => {
- const theme = useTheme();
- return (
- }
- checkedIcon={ }
- sx={{
- "&:hover": { backgroundColor: "transparent" },
- "& svg": { width: theme.spacing(8), height: theme.spacing(8) },
- }}
- />
- );
-};
diff --git a/client/src/Components/v2/Inputs/LanguageSelector.tsx b/client/src/Components/v2/Inputs/LanguageSelector.tsx
deleted file mode 100644
index f7953c873..000000000
--- a/client/src/Components/v2/Inputs/LanguageSelector.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import "flag-icons/css/flag-icons.min.css";
-import { Select } from "@/Components/v2/Inputs";
-import MenuItem from "@mui/material/MenuItem";
-import Stack from "@mui/material/Stack";
-import Typography from "@mui/material/Typography";
-
-import { useTranslation } from "react-i18next";
-import { useTheme } from "@mui/material/styles";
-import { useDispatch } from "react-redux";
-import { useSelector } from "react-redux";
-import { setLanguage } from "@/Features/UI/uiSlice";
-import type { SelectChangeEvent } from "@mui/material/Select";
-
-export const LanguageSelector = () => {
- const { i18n } = useTranslation();
- const theme = useTheme();
- const dispatch = useDispatch();
- const language = useSelector((state: any) => state.ui.language);
- const languages = Object.keys(i18n.options.resources || {});
- const languageMap: Record = {
- cs: "cz",
- ja: "jp",
- uk: "ua",
- vi: "vn",
- };
-
- const handleChange = (event: SelectChangeEvent) => {
- const newLang = event.target.value;
- dispatch(setLanguage(newLang));
- };
-
- const languagesForDisplay = languages.map((l) => {
- let formattedLanguage = l === "en" ? "gb" : l;
- formattedLanguage = formattedLanguage.includes("-")
- ? formattedLanguage.split("-")[1].toLowerCase()
- : formattedLanguage;
- formattedLanguage = languageMap[formattedLanguage] || formattedLanguage;
- const flag = formattedLanguage ? `fi fi-${formattedLanguage}` : null;
-
- return (
-
-
- {flag && }
- {l}
-
-
- );
- });
-
- return (
-
- {languagesForDisplay}
-
- );
-};
diff --git a/client/src/Components/v2/Inputs/RadioInput.tsx b/client/src/Components/v2/Inputs/RadioInput.tsx
deleted file mode 100644
index d903a7b58..000000000
--- a/client/src/Components/v2/Inputs/RadioInput.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import Radio from "@mui/material/Radio";
-import type { RadioProps } from "@mui/material/Radio";
-import { useTheme } from "@mui/material/styles";
-import RadioChecked from "@/assets/icons/radio-checked.svg?react";
-import FormControlLabel from "@mui/material/FormControlLabel";
-import Typography from "@mui/material/Typography";
-
-interface RadioInputProps extends RadioProps {}
-
-export const RadioInput: React.FC = ({ ...props }) => {
- const theme = useTheme();
- return (
- }
- sx={{
- color: "transparent",
- boxShadow: `inset 0 0 0 1px ${theme.palette.secondary.main}`,
- "&:not(.Mui-checked)": {
- boxShadow: `inset 0 0 0 1px ${theme.palette.primary.contrastText}70`, // Use theme text color for the outline
- },
- mt: theme.spacing(0.5),
- padding: 0,
- "& .MuiSvgIcon-root": {
- fontSize: 16,
- },
- }}
- />
- );
-};
-
-export const RadioWithDescription: React.FC<
- RadioInputProps & { label: string; description: string }
-> = ({ label, description, ...props }) => {
- const theme = useTheme();
- return (
- }
- label={
- <>
- {label}
-
- {description}
-
- >
- }
- sx={{
- alignItems: "flex-start",
- p: theme.spacing(2.5),
- m: theme.spacing(-2.5),
- borderRadius: theme.shape.borderRadius,
-
- "&:hover": {
- backgroundColor: theme.palette.tertiary.main,
- },
- "& .MuiButtonBase-root": {
- p: 0,
- mr: theme.spacing(6),
- },
- }}
- />
- );
-};
diff --git a/client/src/Components/v2/Inputs/Select.tsx b/client/src/Components/v2/Inputs/Select.tsx
deleted file mode 100644
index 54aacf68a..000000000
--- a/client/src/Components/v2/Inputs/Select.tsx
+++ /dev/null
@@ -1,88 +0,0 @@
-import { Typography, Select } from "@mui/material";
-import MenuItem from "@mui/material/MenuItem";
-import type { SelectProps } from "@mui/material/Select";
-import { useTheme } from "@mui/material/styles";
-import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
-
-export const SelectInput: React.FC = ({ ...props }) => {
- const theme = useTheme();
- return (
-
- );
-};
-
-type ItemTypes = string | number;
-interface SelectItem {
- _id: ItemTypes;
- name: string;
-}
-export type CustomSelectProps = SelectProps & {
- items: SelectItem[];
- placeholder?: string;
- isHidden?: boolean;
- hasError?: boolean;
-};
-
-export const SelectFromItems: React.FC = ({
- items,
- placeholder,
- isHidden = false,
- hasError = false,
- ...props
-}) => {
- return (
- {
- if (!selected) {
- return (
-
- {placeholder ?? ""}
-
- );
- }
- const selectedItem = items.find((item) => item._id === selected);
- const displayName = selectedItem ? selectedItem.name : placeholder;
- return (
-
- {displayName}
-
- );
- }}
- {...props}
- >
- {items.map((item) => (
-
- {item.name}
-
- ))}
-
- );
-};
-
-SelectInput.displayName = "SelectInput";
-SelectFromItems.displayName = "SelectFromItems";
diff --git a/client/src/Components/v2/Inputs/TextInput.tsx b/client/src/Components/v2/Inputs/TextInput.tsx
deleted file mode 100644
index 4cd792059..000000000
--- a/client/src/Components/v2/Inputs/TextInput.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { forwardRef } from "react";
-import TextField from "@mui/material/TextField";
-import type { TextFieldProps } from "@mui/material";
-import { typographyLevels } from "@/Utils/Theme/v2/palette";
-
-export const TextInput = forwardRef(
- function TextInput(props, ref) {
- return (
-
- );
- }
-);
-TextInput.displayName = "TextInput";
diff --git a/client/src/Components/v2/Inputs/TextLink.tsx b/client/src/Components/v2/Inputs/TextLink.tsx
deleted file mode 100644
index 78525f837..000000000
--- a/client/src/Components/v2/Inputs/TextLink.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import Typography from "@mui/material/Typography";
-import Stack from "@mui/material/Stack";
-import Link from "@mui/material/Link";
-import { Link as RouterLink } from "react-router-dom";
-
-import { useTheme } from "@mui/material/styles";
-
-export const TextLink = ({
- text,
- linkText,
- href,
- target = "_self",
-}: {
- text: string;
- linkText: string;
- href: string;
- target?: string;
-}) => {
- const theme = useTheme();
-
- return (
-
- {text}
-
- {linkText}
-
-
- );
-};
diff --git a/client/src/Components/v2/Inputs/ThemeSwitch.tsx b/client/src/Components/v2/Inputs/ThemeSwitch.tsx
deleted file mode 100644
index fb4bb6d81..000000000
--- a/client/src/Components/v2/Inputs/ThemeSwitch.tsx
+++ /dev/null
@@ -1,135 +0,0 @@
-import { useTheme } from "@mui/material/styles";
-import { useDispatch, useSelector } from "react-redux";
-import { setMode } from "@/Features/UI/uiSlice.js";
-import { useTranslation } from "react-i18next";
-import IconButton from "@mui/material/IconButton";
-
-const SunAndMoonIcon = () => {
- const theme = useTheme();
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-export const ThemeSwitch = ({
- width = 48,
- height = 48,
-}: {
- width?: number;
- height?: number;
-}) => {
- const mode = useSelector((state: any) => state.ui.mode);
- const dispatch = useDispatch();
- const { t } = useTranslation();
-
- const handleChange = () => {
- dispatch(setMode(mode === "light" ? "dark" : "light"));
- };
-
- return (
-
-
-
- );
-};
diff --git a/client/src/Components/v2/Inputs/index.tsx b/client/src/Components/v2/Inputs/index.tsx
deleted file mode 100644
index ccf17f630..000000000
--- a/client/src/Components/v2/Inputs/index.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-export { ButtonInput as Button } from "./Button";
-export { ButtonGroupInput as ButtonGroup } from "./ButtonGroup";
-export { TextInput } from "./TextInput";
-export { SelectInput as Select } from "./Select";
-export { LanguageSelector } from "./LanguageSelector";
-export { ThemeSwitch } from "./ThemeSwitch";
-export { TextLink } from "./TextLink";
diff --git a/client/src/Components/v2/Layouts/RootLayout.tsx b/client/src/Components/v2/Layouts/RootLayout.tsx
deleted file mode 100644
index 4237494b1..000000000
--- a/client/src/Components/v2/Layouts/RootLayout.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { Outlet } from "react-router";
-import Stack from "@mui/material/Stack";
-import { SideBar } from "@/Components/v2/Layouts/Sidebar";
-import { useTheme } from "@mui/material/styles";
-const RootLayout = () => {
- const theme = useTheme();
- return (
-
-
-
-
-
-
- );
-};
-
-export default RootLayout;
diff --git a/client/src/Components/v2/Layouts/Sidebar/BottomControls.tsx b/client/src/Components/v2/Layouts/Sidebar/BottomControls.tsx
deleted file mode 100644
index a07ccde6c..000000000
--- a/client/src/Components/v2/Layouts/Sidebar/BottomControls.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { useTheme } from "@mui/material/styles";
-
-import Stack from "@mui/material/Stack";
-
-export const BottomControls = ({}) => {
- const theme = useTheme();
- return (
-
- );
-};
diff --git a/client/src/Components/v2/Layouts/Sidebar/CollapseButton.tsx b/client/src/Components/v2/Layouts/Sidebar/CollapseButton.tsx
deleted file mode 100644
index 564c41591..000000000
--- a/client/src/Components/v2/Layouts/Sidebar/CollapseButton.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import IconButton from "@mui/material/IconButton";
-import { ArrowRight } from "@/Components/v2/Arrows/ArrowRight";
-import { ArrowLeft } from "@/Components/v2/Arrows/ArrowLeft";
-import { useTheme } from "@mui/material/styles";
-import { useDispatch } from "react-redux";
-import { toggleSidebar } from "../../../../Features/UI/uiSlice.js";
-
-export const CollapseButton = ({ collapsed }: { collapsed: boolean }) => {
- const theme = useTheme();
- const dispatch = useDispatch();
- const arrowIcon = collapsed ? (
-
- ) : (
-
- );
-
- return (
- {
- dispatch(toggleSidebar());
- }}
- >
- {arrowIcon}
-
- );
-};
diff --git a/client/src/Components/v2/Layouts/Sidebar/Logo.tsx b/client/src/Components/v2/Layouts/Sidebar/Logo.tsx
deleted file mode 100644
index 6d190b99d..000000000
--- a/client/src/Components/v2/Layouts/Sidebar/Logo.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import Stack from "@mui/material/Stack";
-import Box from "@mui/material/Box";
-import Typography from "@mui/material/Typography";
-import { useTheme } from "@mui/material/styles";
-import { useNavigate } from "react-router";
-import { useTranslation } from "react-i18next";
-
-export const Logo = ({ collapsed }: { collapsed: boolean }) => {
- const { t } = useTranslation();
- const theme = useTheme();
- const navigate = useNavigate();
-
- return (
- navigate("/")}
- sx={{ cursor: "pointer" }}
- >
-
- C
-
-
- {" "}
-
- {t("common.appName")}
-
-
-
- );
-};
diff --git a/client/src/Components/v2/Layouts/Sidebar/Menu.tsx b/client/src/Components/v2/Layouts/Sidebar/Menu.tsx
deleted file mode 100644
index f0f99f33a..000000000
--- a/client/src/Components/v2/Layouts/Sidebar/Menu.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-// import Notifications from "@/assets/icons/notifications.svg?react";
-import Monitors from "@/assets/icons/monitors.svg?react";
-// import PageSpeed from "@/assets/icons/page-speed.svg?react";
-// import Integrations from "@/assets/icons/integrations.svg?react";
-// import Incidents from "@/assets/icons/incidents.svg?react";
-// import StatusPages from "@/assets/icons/status-pages.svg?react";
-// import Maintenance from "@/assets/icons/maintenance.svg?react";
-// import Logs from "@/assets/icons/logs.svg?react";
-// import Settings from "@/assets/icons/settings.svg?react";
-import Support from "@/assets/icons/support.svg?react";
-import Discussions from "@/assets/icons/discussions.svg?react";
-import Docs from "@/assets/icons/docs.svg?react";
-import ChangeLog from "@/assets/icons/changeLog.svg?react";
-
-export const getMenu = (t: Function) => [
- { name: t("menu.uptime"), path: "v2/uptime", icon: },
- // { name: t("menu.pagespeed"), path: "pagespeed", icon: },
-
- // { name: t("menu.infrastructure"), path: "infrastructure", icon: },
- // {
- // name: t("menu.notifications"),
- // path: "notifications",
- // icon: ,
- // },
- // { name: t("menu.incidents"), path: "incidents", icon: },
-
- // { name: t("menu.statusPages"), path: "status", icon: },
- // { name: t("menu.maintenance"), path: "maintenance", icon: },
- // { name: t("menu.logs"), path: "logs", icon: },
-
- // {
- // name: t("menu.settings"),
- // icon: ,
- // path: "settings",
- // },
-];
-
-export const getBottomMenu = (t: Function) => [
- { name: t("menu.support"), path: "support", icon: , url: "invite" },
- {
- name: t("menu.discussions"),
- path: "discussions",
- icon: ,
- url: "https://github.com/bluewave-labs/checkmate/discussions",
- },
- {
- name: t("menu.docs"),
- path: "docs",
- icon: ,
- url: "https://bluewavelabs.gitbook.io/checkmate",
- },
- {
- name: t("menu.changelog"),
- path: "changelog",
- icon: ,
- url: "https://github.com/bluewave-labs/checkmate/releases",
- },
-];
diff --git a/client/src/Components/v2/Layouts/Sidebar/NavItem.tsx b/client/src/Components/v2/Layouts/Sidebar/NavItem.tsx
deleted file mode 100644
index 8f7f5d68c..000000000
--- a/client/src/Components/v2/Layouts/Sidebar/NavItem.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-import Tooltip from "@mui/material/Tooltip";
-import ListItemButton from "@mui/material/ListItemButton";
-import ListItemIcon from "@mui/material/ListItemIcon";
-import Box from "@mui/material/Box";
-import Typography from "@mui/material/Typography";
-import { useTheme } from "@mui/material/styles";
-
-export interface NavData {
- name: string;
- icon: JSX.Element;
-}
-
-export const NavItem = ({
- item,
- collapsed,
- selected,
- onClick,
-}: {
- item: NavData;
- collapsed: boolean;
- selected: boolean;
- onClick: (event: React.MouseEvent) => void;
-}) => {
- const theme = useTheme();
- const iconStroke = selected
- ? theme.palette.primary.contrastText
- : theme.palette.primary.contrastTextTertiary;
-
- const buttonBgColor = selected ? theme.palette.secondary.main : "transparent";
- const buttonBgHoverColor = selected
- ? theme.palette.secondary.main
- : theme.palette.tertiary.main;
- const fontWeight = selected ? 600 : 400;
- return (
-
-
-
- {item.icon}
-
-
-
- {item.name}
-
-
-
-
- );
-};
diff --git a/client/src/Components/v2/Layouts/Sidebar/index.tsx b/client/src/Components/v2/Layouts/Sidebar/index.tsx
deleted file mode 100644
index a17b9fa13..000000000
--- a/client/src/Components/v2/Layouts/Sidebar/index.tsx
+++ /dev/null
@@ -1,106 +0,0 @@
-import { useEffect } from "react";
-import { useSelector, useDispatch } from "react-redux";
-import { setCollapsed } from "@/Features/UI/uiSlice";
-import useMediaQuery from "@mui/material/useMediaQuery";
-import { useTheme } from "@mui/material/styles";
-import { useTranslation } from "react-i18next";
-import { useNavigate, useLocation } from "react-router-dom";
-
-import { CollapseButton } from "@/Components/v2/Layouts/Sidebar/CollapseButton";
-import Stack from "@mui/material/Stack";
-import List from "@mui/material/List";
-import Divider from "@mui/material/Divider";
-import { Logo } from "@/Components/v2/Layouts/Sidebar/Logo";
-import { getMenu, getBottomMenu } from "@/Components/v2/Layouts/Sidebar/Menu";
-import { NavItem } from "@/Components/v2/Layouts/Sidebar/NavItem";
-import { BottomControls } from "@/Components/v2/Layouts/Sidebar/BottomControls";
-
-export const COLLAPSED_WIDTH = 64;
-export const EXPANDED_WIDTH = 250;
-
-export const SideBar = () => {
- const theme = useTheme();
- const isSmall = useMediaQuery(theme.breakpoints.down("md"));
- const dispatch = useDispatch();
- const collapsed = useSelector((state: any) => state.ui.sidebar.collapsed);
- const { t } = useTranslation();
- const navigate = useNavigate();
- const location = useLocation();
- const menu = getMenu(t);
- const bottomMenu = getBottomMenu(t);
-
- useEffect(() => {
- dispatch(setCollapsed({ collapsed: isSmall }));
- }, [isSmall]);
-
- return (
-
-
-
-
- {menu.map((item) => {
- const selected = location.pathname.startsWith(`/${item.path}`);
- return (
- navigate(`/${item.path}`)}
- />
- );
- })}
-
-
- {bottomMenu.map((item) => {
- const selected = location.pathname.startsWith(`/${item.path}`);
-
- return (
- {
- if (item.url) {
- window.open(item.url, "_blank", "noreferrer");
- } else {
- navigate(`/${item.path}`);
- }
- }}
- />
- );
- })}
-
-
-
-
- );
-};
diff --git a/client/src/Components/v2/Monitors/HeaderControls.tsx b/client/src/Components/v2/Monitors/HeaderControls.tsx
deleted file mode 100644
index c6bfa7f81..000000000
--- a/client/src/Components/v2/Monitors/HeaderControls.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import Stack from "@mui/material/Stack";
-import { MonitorStatus } from "@/Components/v2/Monitors/MonitorStatus";
-import { ButtonGroup, Button } from "@/Components/v2/Inputs";
-import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined";
-import PauseOutlinedIcon from "@mui/icons-material/PauseOutlined";
-import PlayArrowOutlinedIcon from "@mui/icons-material/PlayArrowOutlined";
-import EmailIcon from "@mui/icons-material/Email";
-import BugReportOutlinedIcon from "@mui/icons-material/BugReportOutlined";
-
-import { useMediaQuery } from "@mui/material";
-import { useTranslation } from "react-i18next";
-import { useTheme } from "@mui/material/styles";
-
-import type { IMonitor } from "@/Types/Monitor";
-
-export const HeaderControls = ({
- monitor,
- patch,
- isPatching,
- refetch,
-}: {
- monitor: IMonitor;
- patch: Function;
- isPatching: boolean;
- refetch: Function;
-}) => {
- const { t } = useTranslation();
- const theme = useTheme();
- const isSmall = useMediaQuery(theme.breakpoints.down("md"));
-
- return (
-
-
-
-
- }>{t("sendTestNotifications")}
- }>{t("menu.incidents")}
- {
- await patch(`/monitors/${monitor._id}/active`);
- refetch();
- }}
- startIcon={
- monitor?.isActive ? :
- }
- >
- {monitor?.isActive ? t("pause") : t("resume")}
-
- }>{t("configure")}
-
-
-
- );
-};
diff --git a/client/src/Components/v2/Monitors/HeaderCreate.tsx b/client/src/Components/v2/Monitors/HeaderCreate.tsx
deleted file mode 100644
index 46b17c8a5..000000000
--- a/client/src/Components/v2/Monitors/HeaderCreate.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import Stack from "@mui/material/Stack";
-import { Button } from "@/Components/v2/Inputs";
-import { useTheme } from "@mui/material/styles";
-import { useTranslation } from "react-i18next";
-import { useNavigate } from "react-router";
-export const HeaderCreate = ({
- label,
- isLoading,
- path,
-}: {
- label?: string;
- isLoading: boolean;
- path: string;
-}) => {
- const theme = useTheme();
- const { t } = useTranslation();
- const navigate = useNavigate();
- return (
-
- navigate(path)}
- >
- {label || t("createNew")}
-
-
- );
-};
diff --git a/client/src/Components/v2/Monitors/HeaderRange.tsx b/client/src/Components/v2/Monitors/HeaderRange.tsx
deleted file mode 100644
index 21ce01fe2..000000000
--- a/client/src/Components/v2/Monitors/HeaderRange.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import Stack from "@mui/material/Stack";
-import { ButtonGroup, Button } from "@/Components/v2/Inputs";
-import { useTheme } from "@mui/material/styles";
-import Typography from "@mui/material/Typography";
-import { useMediaQuery } from "@mui/material";
-export const HeaderRange = ({
- range,
- setRange,
- loading,
-}: {
- range: string;
- setRange: Function;
- loading: boolean;
-}) => {
- const theme = useTheme();
- const isSmall = useMediaQuery(theme.breakpoints.down("md"));
- return (
-
- {`Showing statistics for past ${range}`}
-
- setRange("2h")}
- loading={loading}
- >
- Recent
-
- setRange("24h")}
- loading={loading}
- >
- Day
-
- setRange("7d")}
- loading={loading}
- >
- 7 days
-
- setRange("30d")}
- loading={loading}
- >
- 30 days
-
-
-
- );
-};
diff --git a/client/src/Components/v2/Monitors/HistogramResponseTime.tsx b/client/src/Components/v2/Monitors/HistogramResponseTime.tsx
deleted file mode 100644
index aa1996e2a..000000000
--- a/client/src/Components/v2/Monitors/HistogramResponseTime.tsx
+++ /dev/null
@@ -1,87 +0,0 @@
-import Stack from "@mui/material/Stack";
-import Box from "@mui/material/Box";
-import { useTheme } from "@mui/material/styles";
-import type { Check } from "@/Types/Check";
-import { HistogramResponseTimeTooltip } from "@/Components/v2/Monitors/HistogramResponseTimeTooltip";
-import { normalizeResponseTimes } from "@/Utils/v2/DataUtils";
-
-export const HistogramResponseTime = ({ checks }: { checks: Check[] }) => {
- const normalChecks = normalizeResponseTimes(checks, "responseTime");
- let data = Array();
-
- if (!normalChecks || normalChecks.length === 0) {
- return null;
- }
- if (normalChecks.length !== 25) {
- const placeholders = Array(25 - normalChecks.length).fill("placeholder");
- data = [...normalChecks, ...placeholders];
- } else {
- data = normalChecks;
- }
-
- const theme = useTheme();
-
- return (
- event.stopPropagation()}
- sx={{
- cursor: "default",
- }}
- >
- {data.map((check, index) => {
- if (check === "placeholder") {
- return (
-
- );
- } else {
- return (
-
-
-
-
-
- );
- }
- })}
-
- );
-};
diff --git a/client/src/Components/v2/Monitors/HistogramResponseTimeTooltip.tsx b/client/src/Components/v2/Monitors/HistogramResponseTimeTooltip.tsx
deleted file mode 100644
index d8c0263f0..000000000
--- a/client/src/Components/v2/Monitors/HistogramResponseTimeTooltip.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import Stack from "@mui/material/Stack";
-import Tooltip from "@mui/material/Tooltip";
-import Typography from "@mui/material/Typography";
-import { formatDateWithTz } from "@/Utils/v2/TimeUtils";
-import { useSelector } from "react-redux";
-import type { LatestCheck } from "@/Types/Check";
-
-export const HistogramResponseTimeTooltip: React.FC<{
- children: React.ReactElement;
- check: LatestCheck;
-}> = ({ children, check }) => {
- const uiTimezone = useSelector((state: any) => state.ui.timezone);
- return (
-
-
- {formatDateWithTz(check?.checkedAt, "ddd, MMMM D, YYYY, HH:mm A", uiTimezone)}
-
- {check?.responseTime && (
- Response Time: {check.responseTime} ms
- )}
-
- }
- >
- {children}
-
- );
-};
diff --git a/client/src/Components/v2/Monitors/HistogramStatus.tsx b/client/src/Components/v2/Monitors/HistogramStatus.tsx
deleted file mode 100644
index bc6675bc3..000000000
--- a/client/src/Components/v2/Monitors/HistogramStatus.tsx
+++ /dev/null
@@ -1,218 +0,0 @@
-import Stack from "@mui/material/Stack";
-import Box from "@mui/material/Box";
-import Typography from "@mui/material/Typography";
-import { BaseBox } from "@/Components/v2/DesignElements";
-import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts";
-import UptimeIcon from "@/assets/icons/uptime-icon.svg?react";
-import IncidentsIcon from "@/assets/icons/incidents.svg?react";
-
-import type { GroupedCheck } from "@/Types/Check";
-import type { MonitorStatus } from "@/Types/Monitor";
-
-import { normalizeResponseTimes } from "@/Utils/v2/DataUtils";
-import { useState } from "react";
-import { formatDateWithTz } from "@/Utils/v2/TimeUtils";
-import { useSelector } from "react-redux";
-import { useTheme } from "@mui/material/styles";
-import { getResponseTimeColor } from "@/Utils/v2/MonitorUtils";
-
-const XLabel = ({
- p1,
- p2,
- range,
-}: {
- p1: GroupedCheck;
- p2: GroupedCheck;
- range: string;
-}) => {
- const theme = useTheme();
- const uiTimezone = useSelector((state: any) => state.ui.timezone);
- const dateFormat = range === "day" ? "MMM D, h:mm A" : "MMM D";
- return (
- <>
-
- {formatDateWithTz(p1._id, dateFormat, uiTimezone)}
-
-
- {formatDateWithTz(p2._id, dateFormat, uiTimezone)}
-
- >
- );
-};
-
-type BaseChartProps = React.PropsWithChildren<{
- icon: React.ReactNode;
- title: string;
-}>;
-
-export const BaseChart: React.FC = ({ children, icon, title }) => {
- const theme = useTheme();
-
- return (
-
-
-
-
- {icon}
-
- {title}
-
- {children}
-
-
- );
-};
-
-export const HistogramStatus = ({
- checks,
- status,
- range,
- title,
-}: {
- checks: GroupedCheck[];
- status: MonitorStatus;
- range: string;
- title: string;
-}) => {
- const uiTimezone = useSelector((state: any) => state.ui.timezone);
-
- const icon = status === "up" ? : ;
- const theme = useTheme();
- const [idx, setIdx] = useState(null);
- const dateFormat = range === "1d" || range === "2h" ? "MMM D, h A" : "MMM D";
- const normalChecks = normalizeResponseTimes(checks, "avgResponseTime");
-
- if (normalChecks.length === 0) {
- return (
-
-
-
- {status === "up" ? "No checks yet" : "Great, no downtime yet!"}
-
-
-
- );
- }
-
- const totalChecks = normalChecks.reduce((count, check) => {
- return count + check.count;
- }, 0);
-
- return (
-
-
-
-
- Total checks
- {idx ? (
-
- {normalChecks[idx].count}
-
- {formatDateWithTz(normalChecks[idx]._id, dateFormat, uiTimezone)}
-
-
- ) : (
- {totalChecks}
- )}
-
-
-
-
-
- }
- />
-
- {normalChecks?.map((groupedCheck, idx) => {
- const fillColor = getResponseTimeColor(groupedCheck.normalResponseTime);
- return (
- | setIdx(idx)}
- onMouseLeave={() => setIdx(null)}
- key={groupedCheck._id}
- fill={theme.palette[fillColor].main}
- />
- );
- })}
- |
-
-
-
-
- );
-};
diff --git a/client/src/Components/v2/Monitors/index.tsx b/client/src/Components/v2/Monitors/index.tsx
deleted file mode 100644
index 3b0acbd20..000000000
--- a/client/src/Components/v2/Monitors/index.tsx
+++ /dev/null
@@ -1 +0,0 @@
-export { HeaderCreate } from "./HeaderCreate";
diff --git a/client/src/Components/v2/ActionsMenu/index.tsx b/client/src/Components/v2/actions-menu/index.tsx
similarity index 91%
rename from client/src/Components/v2/ActionsMenu/index.tsx
rename to client/src/Components/v2/actions-menu/index.tsx
index a143be3f5..3b2f3688a 100644
--- a/client/src/Components/v2/ActionsMenu/index.tsx
+++ b/client/src/Components/v2/actions-menu/index.tsx
@@ -2,7 +2,7 @@ import React, { useState } from "react";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import IconButton from "@mui/material/IconButton";
-import Settings from "@/assets/icons/settings-bold.svg?react";
+import { Settings } from "lucide-react";
export type ActionMenuItem = {
id: number | string;
@@ -28,7 +28,10 @@ export const ActionsMenu = ({ items }: { items: ActionMenuItem[] }) => {
return (
-
+
;
+ availabilityCellSx?: SxProps;
+ responseCellSx?: SxProps;
+}
+
+export const HeatmapResponseTime = ({
+ checks,
+ gap,
+ availabilityCellSx,
+ responseCellSx,
+}: HeatmapResponseTimeProps) => {
+ const theme = useTheme();
+ if (!gap) {
+ gap = theme.spacing(0.5);
+ }
+
+ if (!checks || checks.length === 0) return null;
+
+ const latestChecks = checks.slice(-25).reverse();
+
+ let data: Array;
+ if (latestChecks.length !== 25) {
+ const placeholders = Array(25 - latestChecks.length).fill({
+ status: "placeholder" as const,
+ });
+ data = [...latestChecks, ...placeholders];
+ } else {
+ data = latestChecks;
+ }
+ return (
+
+
+ {data.map((check, index) => {
+ const statusBg =
+ check.status === "placeholder"
+ ? theme.palette.action.hover
+ : check.status === true
+ ? theme.palette.success.light
+ : theme.palette.error.light;
+ const respBg =
+ check.status === "placeholder"
+ ? theme.palette.action.hover
+ : getResponseColor(check.responseTime || 0, {
+ start: theme.palette.success.main,
+ mid: theme.palette.warning.main,
+ end: theme.palette.error.main,
+ });
+ const statusBorder =
+ check.status === "placeholder"
+ ? theme.palette.divider
+ : check.status === true
+ ? theme.palette.success.main
+ : theme.palette.error.main;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+ })}
+
+
+ );
+};
diff --git a/client/src/Components/v2/common/charts/HeatmapResponseTimeTooltip.tsx b/client/src/Components/v2/common/charts/HeatmapResponseTimeTooltip.tsx
new file mode 100644
index 000000000..ec9ad4d26
--- /dev/null
+++ b/client/src/Components/v2/common/charts/HeatmapResponseTimeTooltip.tsx
@@ -0,0 +1,78 @@
+import Stack from "@mui/material/Stack";
+import Tooltip from "@mui/material/Tooltip";
+import Typography from "@mui/material/Typography";
+import { formatDateWithTz } from "@/Utils/TimeUtils";
+import type { CheckSnapshot } from "@/Types/Check";
+import { useTheme } from "@mui/material/styles";
+import { useSelector } from "react-redux";
+import type { RootState } from "@/Types/state";
+import { useTranslation } from "react-i18next";
+type HeatmapCheck =
+ | CheckSnapshot
+ | { status: "placeholder"; responseTime: 0; createdAt: "" };
+
+export const HeatmapResponseTimeTooltip = ({
+ children,
+ check,
+}: {
+ children: React.ReactElement;
+ check: HeatmapCheck;
+}) => {
+ const { t } = useTranslation();
+ const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
+ const theme = useTheme();
+
+ const getColor = (status: boolean) => {
+ if (status === true) return theme.palette.success.light;
+ if (status === false) return theme.palette.error.light;
+ };
+
+ if (check.status === "placeholder") {
+ return children;
+ }
+
+ return (
+
+
+ {formatDateWithTz(check?.createdAt, "ddd, MMMM D, YYYY, HH:mm A", uiTimezone)}
+
+ {check?.responseTime && (
+
+ {t("common.labels.responseTime")}: {check.responseTime.toFixed()} ms
+
+ )}
+
+
+ Status:{" "}
+
+ {check?.status === true
+ ? t("pages.common.monitors.status.up")
+ : t("pages.common.monitors.status.down")}
+
+
+
+ }
+ >
+ {children}
+
+ );
+};
diff --git a/client/src/Components/v2/common/charts/HistogramResponseTime.tsx b/client/src/Components/v2/common/charts/HistogramResponseTime.tsx
new file mode 100644
index 000000000..e3b82cd50
--- /dev/null
+++ b/client/src/Components/v2/common/charts/HistogramResponseTime.tsx
@@ -0,0 +1,102 @@
+import Box from "@mui/material/Box";
+import { useTheme } from "@mui/material/styles";
+import type { ResponsiveStyleValue } from "@mui/system";
+import type { CheckSnapshot } from "@/Types/Check";
+import { HeatmapResponseTimeTooltip } from "./HeatmapResponseTimeTooltip";
+
+interface HistogramResponseTimeProps {
+ checks: CheckSnapshot[];
+ height?: ResponsiveStyleValue;
+ gap?: ResponsiveStyleValue;
+}
+
+const DEFAULT_HEIGHT = 50;
+
+export const HistogramResponseTime = ({
+ checks,
+ height = DEFAULT_HEIGHT,
+ gap,
+}: HistogramResponseTimeProps) => {
+ const theme = useTheme();
+
+ if (!Array.isArray(checks) || checks.length === 0) return null;
+
+ let data = Array();
+
+ if (!checks || checks.length === 0) {
+ data = [];
+ }
+ if (checks.length !== 25) {
+ const placeholders = Array(25 - checks.length).fill({
+ status: "placeholder",
+ });
+ data = [...checks, ...placeholders];
+ } else {
+ data = checks;
+ }
+ const chartHeight = typeof height === "number" ? `${height}px` : height;
+ const gridGap = gap ?? theme.spacing(0.5);
+ return (
+
+
+ {data.map((check, index) => {
+ const isPlaceholder = (check as any).status === "placeholder";
+ const heightPct = `${Math.max(0, Math.min(100, (check as any).responseTime ?? 0))}%`;
+ const barColor =
+ check.status === true
+ ? theme.palette.success.light
+ : theme.palette.error.light;
+ const bar = (
+
+
+
+ );
+
+ return isPlaceholder ? (
+
+ {bar}
+
+ ) : (
+
+ {bar}
+
+ );
+ })}
+
+
+ );
+};
diff --git a/client/src/Components/v2/common/controls/HeaderCreate.tsx b/client/src/Components/v2/common/controls/HeaderCreate.tsx
new file mode 100644
index 000000000..8234cc8ed
--- /dev/null
+++ b/client/src/Components/v2/common/controls/HeaderCreate.tsx
@@ -0,0 +1,47 @@
+import { Stack } from "@mui/material";
+import { useNavigate } from "react-router-dom";
+import { ButtonInput } from "../../inputs/Button";
+import { useTranslation } from "react-i18next";
+
+interface HeaderCreateProps {
+ label?: string;
+ path: string;
+ isLoading?: boolean;
+ isAdmin?: boolean;
+}
+
+export const HeaderCreate = ({
+ label,
+ path,
+ isLoading = false,
+ isAdmin = false,
+}: HeaderCreateProps) => {
+ const navigate = useNavigate();
+ const { t } = useTranslation();
+
+ if (!isAdmin) {
+ return null;
+ }
+
+ const handleClick = () => {
+ navigate(path);
+ };
+
+ return (
+
+
+ {label || t("common.buttons.create")}
+
+
+ );
+};
diff --git a/client/src/Components/v2/common/controls/HeaderMonitorControls.tsx b/client/src/Components/v2/common/controls/HeaderMonitorControls.tsx
new file mode 100644
index 000000000..17576a8b2
--- /dev/null
+++ b/client/src/Components/v2/common/controls/HeaderMonitorControls.tsx
@@ -0,0 +1,102 @@
+import Stack from "@mui/material/Stack";
+import { Icon, MonitorStatus } from "@/Components/v2/design-elements";
+import { Button } from "@/Components/v2/inputs";
+import { Settings, Pause, Play, Mail, Bug } from "lucide-react";
+
+import { useTranslation } from "react-i18next";
+import { useNavigate } from "react-router-dom";
+import { useTheme } from "@mui/material/styles";
+import { usePost } from "@/Hooks/UseApi";
+
+import type { Monitor } from "@/Types/Monitor.js";
+
+interface HeaderMonitorControlsProps {
+ path: string;
+ monitor?: Monitor;
+ isAdmin: boolean;
+ refetch: Function;
+}
+
+export const HeaderMonitorControls = ({
+ path,
+ monitor,
+ isAdmin,
+ refetch,
+}: HeaderMonitorControlsProps) => {
+ const { t } = useTranslation();
+ const navigate = useNavigate();
+ const theme = useTheme();
+ const {
+ post,
+ loading: isPosting,
+ // error: postError,
+ } = usePost();
+
+ if (!monitor) {
+ return null;
+ }
+ return (
+
+
+
+ }
+ onClick={async () => {
+ await post(`/notifications/test/all`, { monitorId: monitor.id });
+ }}
+ sx={{
+ whiteSpace: "nowrap",
+ }}
+ >
+ {t("common.buttons.testNotifications")}
+
+ }
+ onClick={() => {
+ navigate(`/incidents/${monitor?.id}`);
+ }}
+ >
+ {t("common.buttons.incidents")}
+
+ {isAdmin && (
+ : }
+ onClick={async () => {
+ await post(`/monitors/pause/${monitor.id}`, {});
+ await refetch();
+ }}
+ >
+ {monitor?.isActive ? t("pause") : t("resume")}
+
+ )}
+ {isAdmin && (
+ }
+ onClick={() => navigate(`/${path}/configure/${monitor.id}`)}
+ >
+ Configure
+
+ )}
+
+
+ );
+};
diff --git a/client/src/Components/v2/common/controls/HeaderTimeRange.tsx b/client/src/Components/v2/common/controls/HeaderTimeRange.tsx
new file mode 100644
index 000000000..57b61473a
--- /dev/null
+++ b/client/src/Components/v2/common/controls/HeaderTimeRange.tsx
@@ -0,0 +1,89 @@
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import { ToggleButtonGroup, ToggleButton } from "@/Components/v2/inputs";
+import { useTheme } from "@mui/material/styles";
+
+interface MonitorTimeFrameHeaderProps {
+ isLoading?: boolean;
+ hasDateRange?: boolean;
+ dateRange: string;
+ setDateRange: (dateRange: string) => void;
+}
+
+export const HeaderTimeRange = ({
+ isLoading = false,
+ hasDateRange = true,
+ dateRange,
+ setDateRange,
+}: MonitorTimeFrameHeaderProps) => {
+ const theme = useTheme();
+
+ const handleChange = (
+ _event: React.MouseEvent,
+ newValue: string | null
+ ) => {
+ if (newValue !== null) {
+ setDateRange(newValue);
+ }
+ };
+
+ let timeFramePicker = null;
+
+ if (hasDateRange) {
+ timeFramePicker = (
+
+
+ Recent
+
+
+ Day
+
+
+ Week
+
+
+ Month
+
+
+ );
+ }
+
+ return (
+
+
+ Showing statistics for past{" "}
+ {dateRange === "recent"
+ ? "2 hours"
+ : dateRange === "day"
+ ? "24 hours"
+ : dateRange === "week"
+ ? "7 days"
+ : "30 days"}
+ .
+
+ {timeFramePicker}
+
+ );
+};
diff --git a/client/src/Components/v2/common/index.ts b/client/src/Components/v2/common/index.ts
new file mode 100644
index 000000000..d0d1262b7
--- /dev/null
+++ b/client/src/Components/v2/common/index.ts
@@ -0,0 +1,6 @@
+export * from "../common/charts/HistogramResponseTime";
+export * from "../common/charts/HeatmapResponseTime";
+export * from "../common/charts/HeatmapResponseTimeTooltip";
+export * from "../common/controls/HeaderCreate";
+export * from "../common/controls/HeaderMonitorControls";
+export * from "../common/controls/HeaderTimeRange";
diff --git a/client/src/Components/v2/design-elements/BaseBox.tsx b/client/src/Components/v2/design-elements/BaseBox.tsx
new file mode 100644
index 000000000..580d15dcf
--- /dev/null
+++ b/client/src/Components/v2/design-elements/BaseBox.tsx
@@ -0,0 +1,19 @@
+import Box, { type BoxProps } from "@mui/material/Box";
+import { useTheme } from "@mui/material/styles";
+
+export const BaseBox = ({ sx, ...rest }: BoxProps) => {
+ const theme = useTheme();
+ return (
+
+ );
+};
diff --git a/client/src/Components/v2/design-elements/BaseChart.tsx b/client/src/Components/v2/design-elements/BaseChart.tsx
new file mode 100644
index 000000000..fd9251915
--- /dev/null
+++ b/client/src/Components/v2/design-elements/BaseChart.tsx
@@ -0,0 +1,72 @@
+import { BaseBox } from "../design-elements";
+import Stack from "@mui/material/Stack";
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import type { ResponsiveStyleValue } from "@mui/system";
+
+import { useTheme } from "@mui/material/styles";
+
+type BaseChartProps = React.PropsWithChildren<{
+ icon: React.ReactNode;
+ title: string;
+ width?: number | string;
+ maxWidth?: number | string;
+ padding?: number | string | ResponsiveStyleValue;
+}>;
+
+export const BaseChart = ({
+ children,
+ icon,
+ title,
+ width = "100%",
+ maxWidth = "100%",
+ padding,
+}: BaseChartProps) => {
+ const theme = useTheme();
+ return (
+
+
+
+ {icon && (
+
+ {icon}
+
+ )}
+ {title}
+
+ {children}
+
+
+ );
+};
diff --git a/client/src/Components/v2/design-elements/BasePage.tsx b/client/src/Components/v2/design-elements/BasePage.tsx
new file mode 100644
index 000000000..de20fc005
--- /dev/null
+++ b/client/src/Components/v2/design-elements/BasePage.tsx
@@ -0,0 +1,209 @@
+import Stack from "@mui/material/Stack";
+import Alert from "@mui/material/Alert";
+import Link from "@mui/material/Link";
+import {
+ ErrorFallback,
+ EmptyFallback,
+ EmptyMonitorFallback,
+ BaseFallback,
+} from "@/Components/v2/design-elements/Fallback";
+import { Breadcrumb } from "@/Components/v2/design-elements/Breadcrumb";
+import CircularProgress from "@mui/material/CircularProgress";
+
+import type { StackProps } from "@mui/material/Stack";
+import { useTheme } from "@mui/material/styles";
+import { Trans, useTranslation } from "react-i18next";
+import { Link as RouterLink } from "react-router-dom";
+import { Typography } from "@mui/material";
+
+export const PageSpeedKeyPriorityFallback = () => {
+ return (
+
+
+
+
+ ),
+ }}
+ />
+
+
+
+ );
+};
+
+interface BasePageProps extends StackProps {
+ loading?: boolean;
+ error?: boolean;
+ children: React.ReactNode;
+ breadcrumbOverride?: string[];
+}
+
+export const BasePage = ({
+ loading,
+ error,
+ children,
+ breadcrumbOverride,
+ ...props
+}: BasePageProps) => {
+ const theme = useTheme();
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ if (error) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+ {children}
+
+ );
+};
+
+interface BasePageWithStatesProps extends StackProps {
+ loading: boolean;
+ error: any;
+ items: any[];
+ bullets: string[] | unknown;
+ page: string;
+ actionButtonText: string;
+ actionLink: string;
+ children: React.ReactNode;
+}
+
+export const BasePageWithStates = ({
+ loading,
+ error,
+ items,
+ page,
+ bullets,
+ actionButtonText,
+ actionLink,
+ children,
+ ...props
+}: BasePageWithStatesProps) => {
+ const showLoading = loading && (!items || items.length === 0);
+
+ if (isEmpty(items)) {
+ return (
+
+ );
+ }
+
+ return (
+
+ {children}
+
+ );
+};
+
+interface MonitorBasePageWithStatesProps extends StackProps {
+ loading: boolean;
+ error: any;
+ items: any[];
+ page: string;
+ actionLink?: string;
+ children: React.ReactNode;
+ priorityFallback?: React.ReactNode;
+}
+
+const isEmpty = (items: any[]) => {
+ if (!items) return true;
+ if (Array.isArray(items) && items.length === 0) return true;
+ return false;
+};
+
+export const MonitorBasePageWithStates = ({
+ loading,
+ error,
+ items,
+ page,
+ actionLink,
+ children,
+ priorityFallback,
+ ...props
+}: MonitorBasePageWithStatesProps) => {
+ const { t } = useTranslation();
+
+ const showLoading = loading && (!items || items.length === 0);
+
+ if (priorityFallback) {
+ return (
+
+ {priorityFallback}
+
+ );
+ }
+
+ if (!loading && isEmpty(items)) {
+ return (
+
+ );
+ }
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/client/src/Components/v2/design-elements/Breadcrumb.tsx b/client/src/Components/v2/design-elements/Breadcrumb.tsx
new file mode 100644
index 000000000..492ed0ec9
--- /dev/null
+++ b/client/src/Components/v2/design-elements/Breadcrumb.tsx
@@ -0,0 +1,78 @@
+import MuiBreadcrumbs from "@mui/material/Breadcrumbs";
+import Typography from "@mui/material/Typography";
+import { Link, useLocation } from "react-router-dom";
+import { useTheme } from "@mui/material/styles";
+import { ChevronRight } from "lucide-react";
+
+const isId = (segment: string): boolean => {
+ return segment.length === 24 || /^[a-f0-9-]{36}$/.test(segment);
+};
+
+export const Breadcrumb = () => {
+ const theme = useTheme();
+ const location = useLocation();
+
+ const segments = location.pathname.split("/").filter((x) => x);
+
+ // Build simplified breadcrumb: "uptime" or "uptime / details"
+ const basePage = segments[0] || "home";
+ const hasDetailsPage = segments.length > 1 && isId(segments[1]);
+
+ return (
+
+ }
+ sx={{
+ fontSize: "14px",
+ marginBottom: theme.spacing(6),
+ "& .MuiBreadcrumbs-separator": {
+ color: theme.palette.text.secondary,
+ },
+ }}
+ >
+ {hasDetailsPage ? (
+
+
+ {basePage.charAt(0).toUpperCase() + basePage.slice(1)}
+
+
+ ) : (
+
+ {basePage.charAt(0).toUpperCase() + basePage.slice(1)}
+
+ )}
+ {hasDetailsPage && (
+
+ Details
+
+ )}
+
+ );
+};
diff --git a/client/src/Components/v2/DesignElements/BulletPointCheck.tsx b/client/src/Components/v2/design-elements/BulletPointCheck.tsx
similarity index 71%
rename from client/src/Components/v2/DesignElements/BulletPointCheck.tsx
rename to client/src/Components/v2/design-elements/BulletPointCheck.tsx
index 03147e011..2893ee4c2 100644
--- a/client/src/Components/v2/DesignElements/BulletPointCheck.tsx
+++ b/client/src/Components/v2/design-elements/BulletPointCheck.tsx
@@ -1,6 +1,6 @@
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
-import CheckOutlined from "@/assets/icons/check-outlined.svg?react";
+import { Check } from "lucide-react";
import { useTheme } from "@mui/material/styles";
export const BulletPointCheck = ({
@@ -15,24 +15,25 @@ export const BulletPointCheck = ({
const colors: Record = {
success: theme.palette.success.main,
error: theme.palette.error.main,
- info: theme.palette.primary.contrastTextSecondary,
+ info: theme.palette.text.secondary,
};
return (
-
+
{
+ style?: CSSProperties;
+}
+
+export const Dot = ({ color = "gray", size = "4px", style }: DotProps) => {
return (
{
+ const theme = useTheme();
+ const isSmall = useMediaQuery(theme.breakpoints.down("md"));
+
+ return (
+
+
+ {children}
+
+ );
+};
+
+export const ErrorFallback = ({
+ title,
+ subtitle,
+}: {
+ title: string;
+ subtitle: string;
+}) => {
+ const theme = useTheme();
+ return (
+
+
+ {title}
+
+ {subtitle}
+
+ );
+};
+
+export const EmptyFallback = ({
+ title,
+ bullets,
+ actionButtonText,
+ actionLink,
+}: {
+ title: string;
+ bullets: any;
+ actionButtonText: string;
+ actionLink: string;
+}) => {
+ const theme = useTheme();
+ const navigate = useNavigate();
+ return (
+
+
+
+ {title}
+
+
+ {Array.isArray(bullets) &&
+ bullets?.map((bullet: string) => (
+
+ ))}
+
+
+ navigate(actionLink)}
+ >
+ {actionButtonText}
+
+
+
+
+ );
+};
+
+export const EmptyMonitorFallback = ({
+ page,
+ title,
+ bullets,
+ actionButtonText,
+ actionLink,
+}: {
+ page: string;
+ title: string;
+ bullets: any;
+ actionButtonText: string;
+ actionLink: string;
+}) => {
+ const theme = useTheme();
+ const navigate = useNavigate();
+ return (
+
+
+
+ {title}
+
+
+ {Array.isArray(bullets) &&
+ bullets?.map((bullet: string, index: number) => (
+
+ ))}
+
+
+ navigate(actionLink)}
+ >
+ {actionButtonText}
+
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/design-elements/Icon.tsx b/client/src/Components/v2/design-elements/Icon.tsx
new file mode 100644
index 000000000..b32cd84b2
--- /dev/null
+++ b/client/src/Components/v2/design-elements/Icon.tsx
@@ -0,0 +1,18 @@
+import type { LucideIcon } from "lucide-react";
+
+interface IconProps {
+ icon: LucideIcon;
+ size?: number;
+ strokeWidth?: number;
+}
+
+const Icon = ({ icon: Icon, size = 20, strokeWidth = 1.5 }: IconProps) => {
+ return (
+
+ );
+};
+
+export default Icon;
diff --git a/client/src/Components/v2/Monitors/MonitorStatus.tsx b/client/src/Components/v2/design-elements/MonitorStatus.tsx
similarity index 73%
rename from client/src/Components/v2/Monitors/MonitorStatus.tsx
rename to client/src/Components/v2/design-elements/MonitorStatus.tsx
index f329e6bb8..3d5e81845 100644
--- a/client/src/Components/v2/Monitors/MonitorStatus.tsx
+++ b/client/src/Components/v2/design-elements/MonitorStatus.tsx
@@ -1,14 +1,13 @@
-import type { IMonitor } from "@/Types/Monitor";
+import type { Monitor } from "@/Types/Monitor";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
-import { PulseDot } from "@/Components/v2/DesignElements/PulseDot";
-import { Dot } from "@/Components/v2/DesignElements/Dot";
-import { getStatusColor, formatUrl } from "@/Utils/v2/MonitorUtils";
+import { PulseDot, Dot } from "@/Components/v2/design-elements";
+import { getStatusColor, formatUrl } from "@/Utils/MonitorUtils";
import { useTheme } from "@mui/material/styles";
import prettyMilliseconds from "pretty-ms";
-import { typographyLevels } from "@/Utils/Theme/v2/palette";
-import { useMediaQuery } from "@mui/material";
-export const MonitorStatus = ({ monitor }: { monitor: IMonitor }) => {
+import { typographyLevels } from "@/Utils/Theme/v2Palette";
+import useMediaQuery from "@mui/material/useMediaQuery";
+export const MonitorStatus = ({ monitor }: { monitor: Monitor }) => {
const theme = useTheme();
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
@@ -35,7 +34,6 @@ export const MonitorStatus = ({ monitor }: { monitor: IMonitor }) => {
>
{
+import { Box, Stack, useTheme } from "@mui/material";
+
+interface PulseDotProps {
+ color: string;
+}
+
+export const PulseDot = ({ color }: PulseDotProps) => {
const theme = useTheme();
+
return (
{
width: "7px",
height: "7px",
borderRadius: "50%",
- backgroundColor: theme.palette.accent.contrastText,
+ backgroundColor: theme.palette.background.paper,
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
diff --git a/client/src/Components/v2/design-elements/SkeletonCard.tsx b/client/src/Components/v2/design-elements/SkeletonCard.tsx
new file mode 100644
index 000000000..05f0c462a
--- /dev/null
+++ b/client/src/Components/v2/design-elements/SkeletonCard.tsx
@@ -0,0 +1,147 @@
+import { Box, Stack, useTheme } from "@mui/material";
+
+interface SkeletonCardProps {
+ width?: number | string | { xs?: number | string; md?: number | string };
+ showHalo?: boolean;
+}
+
+export const SkeletonCard = ({
+ width = { xs: 130, md: 216 },
+ showHalo = true,
+}: SkeletonCardProps) => {
+ const theme = useTheme();
+ const pulseAnimation = {
+ "@keyframes pulse": {
+ "0%, 100%": { opacity: 1 },
+ "50%": { opacity: 0.72 },
+ },
+ };
+
+ const floatAnimation = {
+ "@keyframes float": {
+ "0%, 100%": { transform: "rotate(5deg) translate(12px, -20px)" },
+ "50%": { transform: "rotate(5deg) translate(12px, -24px)" },
+ },
+ };
+
+ const cardStyle = {
+ width: width,
+ height: 57,
+ borderRadius: theme.shape.borderRadius,
+ background:
+ theme.palette.mode === "dark"
+ ? theme.palette.secondary.dark
+ : theme.palette.background.paper,
+ boxShadow:
+ theme.palette.mode === "dark"
+ ? "0 8px 30px rgba(0,0,0,.3), 0 0 0 1px rgba(255,255,255,.05) inset"
+ : "0 8px 30px rgba(0,0,0,.08), 0 0 0 1px rgba(0,0,0,.05) inset",
+ display: "flex",
+ gap: theme.spacing(5),
+ alignItems: "center",
+ padding: theme.spacing(5),
+ };
+
+ const blockStyle = {
+ width: 55,
+ height: 34,
+ borderRadius: theme.shape.borderRadius,
+ background: theme.palette.secondary.main,
+ border: `1px solid ${theme.palette.divider}`,
+ animation: "pulse 1.6s ease-in-out infinite",
+ ...pulseAnimation,
+ };
+
+ const lineStyle = {
+ height: 7,
+ borderRadius: theme.shape.borderRadius,
+ background: theme.palette.secondary.main,
+ border: `1px solid ${theme.palette.divider}`,
+ animation: "pulse 1.6s ease-in-out infinite",
+ ...pulseAnimation,
+ };
+
+ return (
+
+ {showHalo && (
+
+ )}
+
+ {/* Card stack */}
+
+ {/* Front card (now at bottom) */}
+
+
+
+ {/* Empty space to maintain card height */}
+
+
+
+ {/* Back card A */}
+
+
+
+
+
+
+
+
+
+ {/* Back card B */}
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/design-elements/StatBox.tsx b/client/src/Components/v2/design-elements/StatBox.tsx
new file mode 100644
index 000000000..b78a003dc
--- /dev/null
+++ b/client/src/Components/v2/design-elements/StatBox.tsx
@@ -0,0 +1,86 @@
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import Box from "@mui/material/Box";
+import { useTheme } from "@mui/material/styles";
+import { lighten } from "@mui/material/styles";
+import useMediaQuery from "@mui/material/useMediaQuery";
+import type { PaletteKey } from "@/Utils/Theme/v2Theme";
+import { BaseBox, TooltipWithInfo } from "@/Components/v2/design-elements";
+import type { SxProps } from "@mui/material";
+
+type GradientBox = React.PropsWithChildren<{
+ palette?: PaletteKey;
+ sx?: SxProps;
+}>;
+
+export const GradientBox = ({ children, palette, sx }: GradientBox) => {
+ const theme = useTheme();
+ const isSmall = useMediaQuery(theme.breakpoints.down("md"));
+ const isLight = theme.palette.mode === "light";
+ const paper = theme.palette.background.paper;
+ const paperStart = lighten(paper, isLight ? 0.06 : 0.08);
+ const paperEnd = paper;
+ const bg = palette
+ ? `linear-gradient(135deg, ${theme.palette[palette].light} 0%, ${theme.palette[palette].main} 100%)`
+ : `linear-gradient(135deg, ${paperStart} 0%, ${paperEnd} 100%)`;
+
+ return (
+
+ {children}
+
+ );
+};
+
+type StatBoxProps = React.PropsWithChildren<{
+ title: string;
+ subtitle: string;
+ palette?: PaletteKey;
+ sx?: SxProps;
+ tooltip?: string;
+ onClick?: () => void;
+}>;
+
+export const StatBox = ({
+ title,
+ subtitle,
+ palette,
+ children,
+ sx,
+ tooltip,
+ onClick,
+}: StatBoxProps) => {
+ const theme = useTheme();
+ const textColor = palette ? theme.palette[palette].contrastText : "inherit";
+
+ return (
+
+
+
+ {title}
+ {tooltip && (
+
+ )}
+
+ {subtitle}
+ {children}
+
+
+ );
+};
diff --git a/client/src/Components/v2/DesignElements/StatusBox.tsx b/client/src/Components/v2/design-elements/StatusBox.tsx
similarity index 62%
rename from client/src/Components/v2/DesignElements/StatusBox.tsx
rename to client/src/Components/v2/design-elements/StatusBox.tsx
index 8cd5cfbc1..6b1c1920c 100644
--- a/client/src/Components/v2/DesignElements/StatusBox.tsx
+++ b/client/src/Components/v2/design-elements/StatusBox.tsx
@@ -1,15 +1,15 @@
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import Box from "@mui/material/Box";
-import { BaseBox } from "@/Components/v2/DesignElements";
+import { BaseBox } from "@/Components/v2/design-elements";
import Background from "@/assets/Images/background-grid.svg?react";
import { useTranslation } from "react-i18next";
import { useTheme } from "@mui/material/styles";
-type StatusBoxProps = React.PropsWithChildren<{}>;
+type StatusBoxProps = React.PropsWithChildren<{ children: React.ReactNode }>;
-export const BGBox: React.FC = ({ children }) => {
+export const BGBox = ({ children }: StatusBoxProps) => {
const theme = useTheme();
return (
= ({ children }) => {
overflow: "hidden",
position: "relative",
flex: 1,
- padding: theme.spacing(8),
+ padding: theme.spacing(4),
}}
>
-
+
{label}
@@ -68,9 +68,9 @@ export const UpStatusBox = ({ n }: { n: number }) => {
const { t } = useTranslation();
return (
);
};
@@ -80,9 +80,9 @@ export const DownStatusBox = ({ n }: { n: number }) => {
const { t } = useTranslation();
return (
);
};
@@ -92,9 +92,21 @@ export const PausedStatusBox = ({ n }: { n: number }) => {
const { t } = useTranslation();
return (
+ );
+};
+
+export const InitializingStatusBox = ({ n }: { n: number }) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+ return (
+
);
};
diff --git a/client/src/Components/v2/design-elements/StatusLabel.tsx b/client/src/Components/v2/design-elements/StatusLabel.tsx
new file mode 100644
index 000000000..c9991f0d4
--- /dev/null
+++ b/client/src/Components/v2/design-elements/StatusLabel.tsx
@@ -0,0 +1,104 @@
+import Box from "@mui/material/Box";
+import { BaseBox } from "@/Components/v2/design-elements";
+import Typography from "@mui/material/Typography";
+
+import type { MonitorStatus } from "@/Types/Monitor";
+import type { SxProps } from "@mui/material/styles";
+import { getStatusPalette, getValuePalette } from "@/Utils/MonitorUtils";
+import { useTheme } from "@mui/material/styles";
+import { useTranslation } from "react-i18next";
+
+export const ValueTypes = ["positive", "negative", "neutral"] as const;
+export type ValueType = (typeof ValueTypes)[number];
+
+export const StatusLabel = ({
+ status,
+ isActive,
+ sx,
+}: {
+ status: MonitorStatus;
+ isActive?: boolean;
+ sx?: SxProps;
+}) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const palette = getStatusPalette(status);
+
+ const determineStatus = (
+ isActive: boolean | undefined,
+ status: MonitorStatus
+ ): string => {
+ if (isActive === false) {
+ return t("pages.common.monitors.status.paused");
+ }
+
+ if (status === true) {
+ return t("pages.common.monitors.status.up");
+ }
+ if (status === false) {
+ return t("pages.common.monitors.status.down");
+ }
+ return t("pages.common.monitors.status.initializing");
+ };
+
+ return (
+
+
+
+ {determineStatus(isActive, status)}
+
+
+ );
+};
+
+export const ValueLabel = ({ value, text }: { value: ValueType; text: string }) => {
+ const theme = useTheme();
+ const palette = getValuePalette(value);
+ const transformedText = text.charAt(0).toUpperCase() + text.slice(1).toLowerCase();
+
+ return (
+
+
+ {transformedText}
+
+ );
+};
diff --git a/client/src/Components/v2/design-elements/Table.tsx b/client/src/Components/v2/design-elements/Table.tsx
new file mode 100644
index 000000000..0516f1ddd
--- /dev/null
+++ b/client/src/Components/v2/design-elements/Table.tsx
@@ -0,0 +1,577 @@
+import Stack from "@mui/material/Stack";
+import Grid2 from "@mui/material/Grid2";
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import Paper from "@mui/material/Paper";
+import Table from "@mui/material/Table";
+import TableBody from "@mui/material/TableBody";
+import TableCell from "@mui/material/TableCell";
+import TableContainer from "@mui/material/TableContainer";
+import TableHead from "@mui/material/TableHead";
+import TableRow from "@mui/material/TableRow";
+import Collapse from "@mui/material/Collapse";
+import Tooltip from "@mui/material/Tooltip";
+
+import IconButton from "@mui/material/IconButton";
+import {
+ ChevronsLeft,
+ ChevronsRight,
+ ChevronLeft,
+ ChevronRight,
+ Coffee,
+ Ellipsis,
+ PartyPopper,
+} from "lucide-react";
+
+import TablePagination from "@mui/material/TablePagination";
+import type { TablePaginationOwnProps } from "@mui/material/TablePagination";
+
+import { useTranslation } from "react-i18next";
+import { useState, Fragment } from "react";
+import { useTheme } from "@mui/material/styles";
+import useMediaQuery from "@mui/material/useMediaQuery";
+
+export type Header = {
+ id: number | string;
+ content: React.ReactNode;
+ onClick?: (event: React.MouseEvent, row: T) => void;
+ render: (row: T) => React.ReactNode;
+};
+
+type DataTableProps = {
+ headers: Header[];
+ data: T[];
+ onRowClick?: (row: T) => void;
+ cardsOnSmallScreens?: boolean;
+ expandableRows?: boolean;
+ renderExpandedContent?: (row: T) => React.ReactNode;
+ emptyViewText?: string;
+ emptyViewPositive?: boolean;
+};
+
+export function DataTable<
+ T extends {
+ id?: string | number;
+ _id?: string | number;
+ onRowClick?: (row: T) => void;
+ },
+>({
+ headers,
+ data,
+ onRowClick,
+ cardsOnSmallScreens = true,
+ expandableRows = false,
+ renderExpandedContent,
+ emptyViewText,
+ emptyViewPositive,
+}: DataTableProps) {
+ const theme = useTheme();
+ const [expanded, setExpanded] = useState<(string | number) | null>(null);
+ const handleExpand = (row: T) => {
+ const key = row.id || row._id || null;
+ setExpanded(expanded === key ? null : key);
+ };
+
+ const isSmall = useMediaQuery(theme.breakpoints.down("md"));
+
+ if (data.length === 0 || headers.length === 0) {
+ return (
+
+ );
+ }
+
+ const keys = [];
+ // Return stack of cards for small screens
+ if (isSmall && cardsOnSmallScreens) {
+ return (
+
+ {data.map((row) => {
+ const key = row.id || row._id || Math.random();
+ keys.push(key);
+ return (
+ (onRowClick ? onRowClick(row) : null)}
+ spacing={theme.spacing(4)}
+ sx={{
+ borderStyle: "solid",
+ borderWidth: 1,
+ borderColor: theme.palette.divider,
+ borderRadius: theme.shape.borderRadius,
+ padding: theme.spacing(4),
+ cursor: onRowClick ? "pointer" : "default",
+ }}
+ key={key}
+ >
+ {headers.map((header) => {
+ return (
+
+
+
+ {header.content}
+
+
+
+ {header.render(row)}{" "}
+
+
+ );
+ })}
+ {expandableRows && renderExpandedContent && renderExpandedContent(row) && (
+ {renderExpandedContent(row)}
+ )}
+
+ );
+ })}
+
+ );
+ }
+
+ return (
+
+
+
+
+ {headers.map((header, idx) => {
+ return (
+
+ {header.content}
+
+ );
+ })}
+
+
+
+ {data.map((row) => {
+ const key = row.id || row._id || Math.random();
+ const isExpanded = expanded === key;
+
+ return (
+
+ {
+ if (expandableRows) handleExpand(row);
+ else if (onRowClick) onRowClick(row);
+ }}
+ >
+ {headers.map((header, index) => {
+ return (
+ header.onClick!(e, row) : undefined
+ }
+ >
+ {header.render(row)}
+
+ );
+ })}
+
+ {expandableRows &&
+ renderExpandedContent &&
+ renderExpandedContent(row) && (
+
+
+
+ {renderExpandedContent(row)}
+
+
+
+ )}
+
+ );
+ })}
+
+
+
+ );
+}
+
+interface TablePaginationActionsProps {
+ count: number;
+ page: number;
+ rowsPerPage: number;
+ onPageChange: (event: React.MouseEvent, newPage: number) => void;
+}
+
+function TablePaginationActions(props: TablePaginationActionsProps) {
+ const theme = useTheme();
+ const { count, page, rowsPerPage, onPageChange } = props;
+
+ const handleFirstPageButtonClick = (event: React.MouseEvent) => {
+ onPageChange(event, 0);
+ };
+
+ const handleBackButtonClick = (event: React.MouseEvent) => {
+ onPageChange(event, page - 1);
+ };
+
+ const handleNextButtonClick = (event: React.MouseEvent) => {
+ onPageChange(event, page + 1);
+ };
+
+ const handleLastPageButtonClick = (event: React.MouseEvent) => {
+ onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1));
+ };
+
+ return (
+
+
+ {theme.direction === "rtl" ? (
+
+ ) : (
+
+ )}
+
+
+ {theme.direction === "rtl" ? (
+
+ ) : (
+
+ )}
+
+ = Math.ceil(count / rowsPerPage) - 1}
+ aria-label="next page"
+ >
+ {theme.direction === "rtl" ? (
+
+ ) : (
+
+ )}
+
+ = Math.ceil(count / rowsPerPage) - 1}
+ aria-label="last page"
+ >
+ {theme.direction === "rtl" ? (
+
+ ) : (
+
+ )}
+
+
+ );
+}
+
+interface HasMoreTablePaginationActionsProps {
+ hasMore?: boolean;
+ page: number;
+ onPageChange: (event: React.MouseEvent, newPage: number) => void;
+}
+function HasMoreTablePaginationActions(props: HasMoreTablePaginationActionsProps) {
+ const theme = useTheme();
+ const { hasMore, page, onPageChange } = props;
+
+ const handleFirstPageButtonClick = (event: React.MouseEvent) => {
+ onPageChange(event, 0);
+ };
+
+ const handleBackButtonClick = (event: React.MouseEvent) => {
+ onPageChange(event, page - 1);
+ };
+
+ const handleNextButtonClick = (event: React.MouseEvent) => {
+ onPageChange(event, page + 1);
+ };
+
+ return (
+
+
+ {theme.direction === "rtl" ? (
+
+ ) : (
+
+ )}
+
+
+ {theme.direction === "rtl" ? (
+
+ ) : (
+
+ )}
+
+
+ {theme.direction === "rtl" ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+ );
+}
+
+interface PaginationProps extends TablePaginationOwnProps {
+ component?: React.ElementType;
+ hasMore?: boolean;
+}
+
+export const Pagination = ({ ...props }: PaginationProps) => {
+ const { hasMore, ...rest } = props;
+ const isSmall = useMediaQuery((theme: any) => theme.breakpoints.down("sm"));
+ const theme = useTheme();
+ const labelDisplayedRows = ({
+ from,
+ to,
+ count,
+ }: {
+ from: number;
+ to: number;
+ count: number;
+ }) => {
+ if (hasMore) {
+ return to === 0 ? "" : `${from}–${to}`;
+ }
+ return `${from}–${to} of ${count}`;
+ };
+
+ return (
+
+ typeof hasMore === "boolean" ? (
+
+ ) : (
+
+ )
+ }
+ rowsPerPageOptions={[5, 10, 25]}
+ labelDisplayedRows={labelDisplayedRows}
+ {...rest}
+ sx={{
+ "& .MuiTablePagination-toolbar": isSmall
+ ? {
+ display: "grid",
+ gridTemplateColumns: "1fr 1fr",
+ gridAutoRows: "auto",
+ rowGap: theme.spacing(1),
+ alignItems: "center",
+ justifyItems: "center",
+ paddingLeft: 0,
+ paddingRight: 0,
+ }
+ : { display: "flex", alignItems: "center" },
+ "& .MuiTablePagination-selectLabel": {
+ gridColumn: "1",
+ gridRow: "1",
+ justifySelf: "center",
+ },
+ "& .MuiTablePagination-select": {
+ gridColumn: "2",
+ gridRow: "1",
+ justifySelf: "center",
+ },
+ "& .MuiTablePagination-displayedRows": isSmall
+ ? { gridColumn: "1 / span 2", gridRow: "2", justifySelf: "center" }
+ : {},
+ "& .table-pagination-actions": isSmall
+ ? { gridColumn: "1 / span 2", gridRow: "3", justifySelf: "center" }
+ : {},
+ "& .MuiSelect-select": {
+ border: 1,
+ borderColor: theme.palette.divider,
+ borderRadius: theme.shape.borderRadius,
+ },
+ }}
+ />
+ );
+};
+
+const EmptyView = ({ text, positive }: { text?: string; positive?: boolean }) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+ const Icon = positive ? PartyPopper : Coffee;
+ return (
+
+
+
+
+
+ {text ?? t("common.table.empty")}
+
+
+ );
+};
diff --git a/client/src/Components/v2/design-elements/Tooltip.tsx b/client/src/Components/v2/design-elements/Tooltip.tsx
new file mode 100644
index 000000000..f83b1331c
--- /dev/null
+++ b/client/src/Components/v2/design-elements/Tooltip.tsx
@@ -0,0 +1,79 @@
+import MuiTooltip from "@mui/material/Tooltip";
+import { useTheme } from "@mui/material/styles";
+import { Info } from "lucide-react";
+
+import type { TooltipProps } from "@mui/material/Tooltip";
+
+type StyledTooltipProps = TooltipProps;
+
+export const Tooltip = ({ placement = "top", ...props }: StyledTooltipProps) => {
+ const theme = useTheme();
+
+ return (
+
+ {props.children}
+
+ );
+};
+
+export interface TooltipWithInfoProps extends Omit {
+ iconSize?: number;
+ iconColor?: string;
+}
+
+export const TooltipWithInfo = ({
+ iconSize = 14,
+ iconColor,
+ ...props
+}: TooltipWithInfoProps) => {
+ const theme = useTheme();
+
+ const defaultColor = theme.palette.text.secondary;
+
+ return (
+
+
+ {
+ e.currentTarget.style.opacity = "1";
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.opacity = "0.7";
+ }}
+ />
+
+
+ );
+};
diff --git a/client/src/Components/v2/design-elements/index.tsx b/client/src/Components/v2/design-elements/index.tsx
new file mode 100644
index 000000000..6ebffc624
--- /dev/null
+++ b/client/src/Components/v2/design-elements/index.tsx
@@ -0,0 +1,17 @@
+export * from "./Breadcrumb";
+export * from "./BasePage";
+export * from "./Fallback";
+export * from "./SkeletonCard";
+export * from "./BulletPointCheck";
+export * from "./StatusBox";
+export * from "./BaseBox";
+export * from "./Table";
+export { DataTable as Table } from "./Table";
+export * from "./StatusLabel";
+export * from "./PulseDot";
+export * from "./Dot";
+export * from "./MonitorStatus";
+export { default as Icon } from "./Icon";
+export * from "./Tooltip";
+export * from "./StatBox";
+export * from "./BaseChart";
diff --git a/client/src/Components/v2/inputs/AutoComplete.tsx b/client/src/Components/v2/inputs/AutoComplete.tsx
new file mode 100644
index 000000000..af0e8cbfd
--- /dev/null
+++ b/client/src/Components/v2/inputs/AutoComplete.tsx
@@ -0,0 +1,88 @@
+import Autocomplete from "@mui/material/Autocomplete";
+import type { AutocompleteProps } from "@mui/material/Autocomplete";
+import { TextField, Checkbox } from "@/Components/v2/inputs";
+import ListItem from "@mui/material/ListItem";
+import Stack from "@mui/material/Stack";
+import { useTheme } from "@mui/material/styles";
+import { FieldLabel } from "./FieldLabel";
+import { ChevronDown } from "lucide-react";
+
+type AutoCompleteInputProps = Omit<
+ AutocompleteProps,
+ "renderInput"
+> & {
+ renderInput?: AutocompleteProps["renderInput"];
+ fieldLabel?: string;
+ required?: boolean;
+};
+
+export const AutoCompleteInput = ({
+ fieldLabel,
+ required,
+ renderInput,
+ ...props
+}: AutoCompleteInputProps) => {
+ const theme = useTheme();
+ const multiple = props.multiple;
+
+ const defaultRenderInput = (params: any) => (
+
+ );
+
+ const autocomplete = (
+
+ }
+ renderInput={renderInput || defaultRenderInput}
+ getOptionKey={(option) => option.id}
+ renderTags={() => null}
+ renderOption={(props, option, { selected }) => {
+ const { key, ...optionProps } = props;
+ return (
+
+
+ {multiple && }
+ {option.name}
+
+
+ );
+ }}
+ sx={{
+ "&.MuiAutocomplete-root .MuiAutocomplete-input": {
+ padding: `0 ${theme.spacing(5)}`,
+ },
+ "& .MuiInputBase-root .MuiAutocomplete-endAdornment": {
+ right: theme.spacing(3),
+ },
+ }}
+ />
+ );
+
+ if (fieldLabel) {
+ return (
+
+ {fieldLabel}
+ {autocomplete}
+
+ );
+ }
+
+ return autocomplete;
+};
diff --git a/client/src/Components/v2/inputs/Button.tsx b/client/src/Components/v2/inputs/Button.tsx
new file mode 100644
index 000000000..54f776075
--- /dev/null
+++ b/client/src/Components/v2/inputs/Button.tsx
@@ -0,0 +1,37 @@
+import Button from "@mui/material/Button";
+import type { ButtonProps } from "@mui/material/Button";
+import { useTheme } from "@mui/material/styles";
+
+export const ButtonInput = ({ sx, ...props }: ButtonProps) => {
+ const theme = useTheme();
+ const outlinedSx =
+ props.variant === "outlined"
+ ? {
+ color: theme.palette.text.primary,
+ borderColor: theme.palette.divider,
+ "&:hover": {
+ borderColor: theme.palette.text.secondary,
+ },
+ }
+ : {};
+ return (
+
+ );
+};
diff --git a/client/src/Components/v2/inputs/Checkbox.tsx b/client/src/Components/v2/inputs/Checkbox.tsx
new file mode 100644
index 000000000..195a796dd
--- /dev/null
+++ b/client/src/Components/v2/inputs/Checkbox.tsx
@@ -0,0 +1,56 @@
+import { forwardRef } from "react";
+import Checkbox from "@mui/material/Checkbox";
+import type { CheckboxProps } from "@mui/material/Checkbox";
+import { Square, SquareCheck } from "lucide-react";
+import { useTheme } from "@mui/material/styles";
+
+type CheckboxInputProps = CheckboxProps & {
+ label?: string;
+};
+export const CheckboxInput = forwardRef(
+ function CheckboxInput(
+ { sx, ...props }: CheckboxInputProps,
+ ref: React.Ref
+ ) {
+ const theme = useTheme();
+ return (
+
+ }
+ checkedIcon={
+
+ }
+ sx={{
+ color: theme.palette.text.secondary,
+ "&.Mui-checked": {
+ color: theme.palette.primary.main,
+ },
+ "&:hover": { backgroundColor: "transparent" },
+ "& svg": {
+ stroke: "currentColor",
+ },
+ "& svg path, & svg line, & svg polyline, & svg rect": {
+ stroke: "currentColor",
+ fill: "none",
+ },
+
+ ...sx,
+ }}
+ />
+ );
+ }
+);
diff --git a/client/src/Components/v2/inputs/Dialog.tsx b/client/src/Components/v2/inputs/Dialog.tsx
new file mode 100644
index 000000000..373d95023
--- /dev/null
+++ b/client/src/Components/v2/inputs/Dialog.tsx
@@ -0,0 +1,56 @@
+import Dialog from "@mui/material/Dialog";
+import DialogActions from "@mui/material/DialogActions";
+import DialogContent from "@mui/material/DialogContent";
+import DialogContentText from "@mui/material/DialogContentText";
+import DialogTitle from "@mui/material/DialogTitle";
+import { Button } from "@/Components/v2/inputs";
+import { typographyLevels } from "@/Utils/Theme/v2Palette";
+import { useTranslation } from "react-i18next";
+
+export const DialogInput = ({
+ open,
+ title,
+ content,
+ onConfirm,
+ onCancel,
+ confirmColor = "primary",
+ cancelColor = "error",
+ loading = false,
+}: {
+ open: boolean;
+ title?: string;
+ content?: string;
+ onConfirm?(item: any): any;
+ onCancel?(item: any): any;
+ confirmColor?: "error" | "primary";
+ cancelColor?: "error" | "primary";
+ loading?: boolean;
+}) => {
+ const { t } = useTranslation();
+ return (
+
+ {title}
+
+ {content}
+
+
+
+ {t("common.buttons.cancel")}
+
+
+ {t("common.buttons.confirm")}
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/inputs/FieldLabel.tsx b/client/src/Components/v2/inputs/FieldLabel.tsx
new file mode 100644
index 000000000..552991538
--- /dev/null
+++ b/client/src/Components/v2/inputs/FieldLabel.tsx
@@ -0,0 +1,38 @@
+import Typography from "@mui/material/Typography";
+import { useTheme } from "@mui/material/styles";
+
+interface FieldLabelProps {
+ children: React.ReactNode;
+ required?: boolean;
+ htmlFor?: string;
+}
+
+export const FieldLabel = ({ children, required = false, htmlFor }: FieldLabelProps) => {
+ const theme = useTheme();
+
+ return (
+
+ {children}
+ {required && (
+
+ *
+
+ )}
+
+ );
+};
diff --git a/client/src/Components/v2/inputs/Select.tsx b/client/src/Components/v2/inputs/Select.tsx
new file mode 100644
index 000000000..057b0e72c
--- /dev/null
+++ b/client/src/Components/v2/inputs/Select.tsx
@@ -0,0 +1,102 @@
+import Select from "@mui/material/Select";
+import React from "react";
+import { useTheme } from "@mui/material/styles";
+import type { SelectProps } from "@mui/material/Select";
+import Stack from "@mui/material/Stack";
+import { FieldLabel } from "./FieldLabel";
+import { ChevronDown } from "lucide-react";
+import Typography from "@mui/material/Typography";
+
+interface SelectInputProps extends Omit, "label"> {
+ fieldLabel?: string;
+ required?: boolean;
+ placeholder?: string;
+ placeholderColor?: string;
+}
+
+export const SelectInput = ({
+ fieldLabel,
+ required,
+ placeholder,
+ placeholderColor,
+ ...props
+}: SelectInputProps) => {
+ const theme = useTheme();
+ const emptyPlaceholderColor = placeholderColor || theme.palette.text.disabled;
+
+ const renderValue = (selected: any) => {
+ const isMultiple = Boolean((props as any).multiple);
+ const isEmpty = isMultiple
+ ? !Array.isArray(selected) || selected.length === 0
+ : selected === undefined || selected === null || selected === "";
+
+ if (isEmpty && placeholder) {
+ return {placeholder} ;
+ }
+
+ if (isMultiple) {
+ const items: string[] = Array.isArray(selected) ? selected : [];
+ const capitalized = items.map(
+ (item) => item.charAt(0).toUpperCase() + item.slice(1)
+ );
+ return ({capitalized.join(" | ")} ) as any;
+ }
+
+ const nodes = React.Children.toArray(props.children as React.ReactNode);
+ for (const node of nodes) {
+ if (!React.isValidElement(node)) continue;
+ const el = node as React.ReactElement<{
+ value?: unknown;
+ children?: React.ReactNode;
+ }>;
+ if (el.props?.value === selected) {
+ return (el.props.children ?? selected) as React.ReactNode;
+ }
+ }
+ return selected as React.ReactNode;
+ };
+
+ const select = (
+
+ {...props}
+ displayEmpty
+ renderValue={renderValue}
+ inputProps={{
+ ...(props.inputProps || {}),
+ "aria-placeholder": placeholder,
+ }}
+ IconComponent={() => (
+
+ )}
+ sx={{
+ height: "34px",
+ "& .MuiSelect-icon": {
+ right: theme.spacing(3),
+ },
+ "& .MuiOutlinedInput-notchedOutline": {
+ borderRadius: theme.shape.borderRadius,
+ borderColor: theme.palette.divider,
+ },
+ "&:hover .MuiOutlinedInput-notchedOutline": {
+ borderColor: theme.palette.divider,
+ },
+ ...props.sx,
+ }}
+ />
+ );
+
+ if (fieldLabel) {
+ return (
+
+ {fieldLabel}
+ {select}
+
+ );
+ }
+
+ return select;
+};
diff --git a/client/src/Components/v2/inputs/TextInput.tsx b/client/src/Components/v2/inputs/TextInput.tsx
new file mode 100644
index 000000000..9a16a43c7
--- /dev/null
+++ b/client/src/Components/v2/inputs/TextInput.tsx
@@ -0,0 +1,56 @@
+import { forwardRef } from "react";
+import TextField from "@mui/material/TextField";
+import type { TextFieldProps } from "@mui/material";
+import { typographyLevels } from "@/Utils/Theme/v2Palette";
+import { useTheme } from "@mui/material/styles";
+import Stack from "@mui/material/Stack";
+import { FieldLabel } from "./FieldLabel";
+
+interface TextInputProps extends Omit {
+ fieldLabel?: string;
+ required?: boolean;
+}
+
+export const TextInput = forwardRef(function TextInput(
+ { fieldLabel, required, ...props },
+ ref
+) {
+ const theme = useTheme();
+
+ const input = (
+
+ );
+
+ if (fieldLabel) {
+ return (
+
+ {fieldLabel}
+ {input}
+
+ );
+ }
+
+ return input;
+});
+TextInput.displayName = "TextInput";
diff --git a/client/src/Components/v2/inputs/ToggleButton.tsx b/client/src/Components/v2/inputs/ToggleButton.tsx
new file mode 100644
index 000000000..40b4309b8
--- /dev/null
+++ b/client/src/Components/v2/inputs/ToggleButton.tsx
@@ -0,0 +1,52 @@
+import ToggleButton from "@mui/material/ToggleButton";
+import ToggleButtonGroup from "@mui/material/ToggleButtonGroup";
+import type { ToggleButtonProps } from "@mui/material/ToggleButton";
+import type { ToggleButtonGroupProps } from "@mui/material/ToggleButtonGroup";
+import { useTheme } from "@mui/material/styles";
+
+export const ToggleButtonInput = ({ sx, ...props }: ToggleButtonProps) => {
+ return (
+
+ );
+};
+
+export const ToggleButtonGroupInput = ({ sx, ...props }: ToggleButtonGroupProps) => {
+ const theme = useTheme();
+ return (
+
+ );
+};
diff --git a/client/src/Components/v2/inputs/index.tsx b/client/src/Components/v2/inputs/index.tsx
new file mode 100644
index 000000000..70e740dbd
--- /dev/null
+++ b/client/src/Components/v2/inputs/index.tsx
@@ -0,0 +1,11 @@
+export { ButtonInput as Button } from "./Button";
+export * from "./FieldLabel";
+export { SelectInput as Select } from "./Select";
+export { CheckboxInput as Checkbox } from "./Checkbox";
+export { AutoCompleteInput as Autocomplete } from "./AutoComplete";
+export { TextInput as TextField } from "./TextInput";
+export {
+ ToggleButtonInput as ToggleButton,
+ ToggleButtonGroupInput as ToggleButtonGroup,
+} from "./ToggleButton";
+export { DialogInput as Dialog } from "./Dialog";
diff --git a/client/src/Components/v2/monitors/ControlsFilter.tsx b/client/src/Components/v2/monitors/ControlsFilter.tsx
new file mode 100644
index 000000000..d7eecefb5
--- /dev/null
+++ b/client/src/Components/v2/monitors/ControlsFilter.tsx
@@ -0,0 +1,92 @@
+import Stack from "@mui/material/Stack";
+import { Select, Button } from "@/Components/v2/inputs";
+import MenuItem from "@mui/material/MenuItem";
+import useMediaQuery from "@mui/material/useMediaQuery";
+
+import type { MonitorType } from "@/Types/Monitor";
+import { Typography, useTheme } from "@mui/material";
+
+const types = ["http", "ping", "port", "docker"];
+const statuses = ["up", "down"];
+const states = ["active", "paused"];
+
+export const ControlsFilter = ({
+ selectedTypes,
+ setSelectedTypes,
+ selectedStatus,
+ setSelectedStatus,
+ selectedState,
+ setSelectedState,
+ onClearFilters,
+}: {
+ selectedTypes: MonitorType[];
+ setSelectedTypes: React.Dispatch>;
+ selectedStatus: string;
+ setSelectedStatus: React.Dispatch>;
+ selectedState: string;
+ setSelectedState: React.Dispatch>;
+ onClearFilters: () => void;
+}) => {
+ const theme = useTheme();
+ const isSmall = useMediaQuery(theme.breakpoints.down("md"));
+ const isFilterActive =
+ selectedTypes.length > 0 || selectedStatus !== "" || selectedState !== "";
+ return (
+
+ setSelectedTypes(e.target.value as MonitorType[])}
+ >
+ {types.map((type) => (
+
+ {type}
+
+ ))}
+
+ setSelectedStatus(e.target.value)}
+ >
+ {statuses.map((status) => (
+
+ {status}
+
+ ))}
+
+ setSelectedState(e.target.value)}
+ >
+ {states.map((state) => (
+
+ {state}
+
+ ))}
+
+ {isFilterActive && (
+
+ Clear Filters
+
+ )}
+
+ );
+};
diff --git a/client/src/Components/v2/monitors/MonitorStatBoxes.tsx b/client/src/Components/v2/monitors/MonitorStatBoxes.tsx
new file mode 100644
index 000000000..65be06f3b
--- /dev/null
+++ b/client/src/Components/v2/monitors/MonitorStatBoxes.tsx
@@ -0,0 +1,69 @@
+import Stack from "@mui/material/Stack";
+import { StatBox } from "@/Components/v2/design-elements";
+
+import { useTheme } from "@mui/material/styles";
+import type { MonitorStats, Monitor } from "@/Types/Monitor";
+import { getHumanReadableDuration } from "@/Utils/timeUtilsLegacy.js";
+import { getStatusPalette } from "@/Utils/MonitorUtils";
+import { useTranslation } from "react-i18next";
+
+interface MonitorStatBoxesProps {
+ monitor?: Monitor;
+ monitorStats: MonitorStats | null;
+ certificateExpiry?: string;
+}
+
+export const MonitorStatBoxes = ({
+ monitor,
+ monitorStats,
+ certificateExpiry,
+}: MonitorStatBoxesProps) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+ if (!monitorStats || !monitor) {
+ return null;
+ }
+
+ const timeOfLastFailure = monitorStats?.timeOfLastFailure || 0;
+ const timeSinceLastFailure =
+ timeOfLastFailure > 0
+ ? Date.now() - timeOfLastFailure
+ : Date.now() - new Date(monitorStats?.createdAt || 0).getTime();
+
+ // Determine time since last check
+ const timeOfLastCheck = monitorStats?.lastCheckTimestamp || 0;
+ const timeSinceLastCheck = Date.now() - timeOfLastCheck;
+
+ const streakTime = getHumanReadableDuration(timeSinceLastFailure);
+
+ const lastCheckTime = getHumanReadableDuration(timeSinceLastCheck);
+ const palette = getStatusPalette(monitor?.status);
+
+ return (
+
+
+
+
+
+ {monitor?.type === "http" && (
+
+ )}
+
+ );
+};
diff --git a/client/src/Components/v2/Monitors/ChartResponseTime.tsx b/client/src/Components/v2/monitors/charts/HistogramDetails.tsx
similarity index 67%
rename from client/src/Components/v2/Monitors/ChartResponseTime.tsx
rename to client/src/Components/v2/monitors/charts/HistogramDetails.tsx
index 3c1c93f01..8858a9784 100644
--- a/client/src/Components/v2/Monitors/ChartResponseTime.tsx
+++ b/client/src/Components/v2/monitors/charts/HistogramDetails.tsx
@@ -1,7 +1,5 @@
-import { BaseChart } from "./HistogramStatus";
-import { BaseBox } from "../DesignElements";
-import ResponseTimeIcon from "@/assets/icons/response-time-icon.svg?react";
-import { normalizeResponseTimes } from "@/Utils/v2/DataUtils";
+import { BaseChart, BaseBox } from "@/Components/v2/design-elements";
+import { Clock } from "lucide-react";
import {
AreaChart,
Area,
@@ -13,14 +11,15 @@ import {
} from "recharts";
import Typography from "@mui/material/Typography";
+import { useSelector } from "react-redux";
import {
formatDateWithTz,
tickDateFormatLookup,
tooltipDateFormatLookup,
-} from "@/Utils/v2/TimeUtils";
+} from "@/Utils/TimeUtils";
import { useTheme } from "@mui/material/styles";
import type { GroupedCheck } from "@/Types/Check";
-import { useSelector } from "react-redux";
+import type { RootState } from "@/Types/state";
type XTickProps = {
x: number;
@@ -29,16 +28,16 @@ type XTickProps = {
range: string;
};
-const XTick: React.FC = ({ x, y, payload, range }) => {
+export const XTick = ({ x, y, payload, range }: XTickProps) => {
const format = tickDateFormatLookup(range);
const theme = useTheme();
- const uiTimezone = useSelector((state: any) => state.ui.timezone);
+ const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
return (
@@ -50,20 +49,20 @@ const XTick: React.FC = ({ x, y, payload, range }) => {
type ResponseTimeToolTipProps = {
active?: boolean | undefined;
payload?: any[];
- label?: string;
+ label?: string | number;
range: string;
theme: any;
uiTimezone: string;
};
-const ResponseTimeToolTip: React.FC = ({
+const ResponseTimeToolTip = ({
active,
payload,
label,
range,
theme,
uiTimezone,
-}) => {
+}: ResponseTimeToolTipProps) => {
if (!label) return null;
if (!payload) return null;
if (!active) return null;
@@ -72,13 +71,13 @@ const ResponseTimeToolTip: React.FC = ({
const responseTime = Math.floor(payload?.[0]?.payload?.avgResponseTime || 0);
return (
- {formatDateWithTz(label, format, uiTimezone)}
+ {formatDateWithTz(String(label), format, uiTimezone)}
Response time: {responseTime} ms
);
};
-export const ChartResponseTime = ({
+export const HistogramDetails = ({
checks,
range,
}: {
@@ -86,23 +85,24 @@ export const ChartResponseTime = ({
range: string;
}) => {
const theme = useTheme();
- const uiTimezone = useSelector((state: any) => state.ui.timezone);
- const normalized = normalizeResponseTimes(
- checks,
- "avgResponseTime"
- );
+ const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
return (
}
+ icon={
+
+ }
title="Response times"
>
-
+
@@ -131,7 +131,7 @@ export const ChartResponseTime = ({
(
diff --git a/client/src/Components/v2/monitors/charts/HistogramPageSpeed.tsx b/client/src/Components/v2/monitors/charts/HistogramPageSpeed.tsx
new file mode 100644
index 000000000..f19e0323c
--- /dev/null
+++ b/client/src/Components/v2/monitors/charts/HistogramPageSpeed.tsx
@@ -0,0 +1,106 @@
+import Stack from "@mui/material/Stack";
+import { Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip } from "recharts";
+import { HistogramPageSpeedTooltip } from "@/Components/v2/monitors/charts/HistogramPageSpeedTooltip";
+import { useTheme } from "@mui/material/styles";
+import type { CheckSnapshot } from "@/Types/Check";
+import type { MonitorStatus } from "@/Types/Monitor";
+
+import { getStatusColor } from "@/Utils/MonitorUtils";
+
+const processData = (checks: CheckSnapshot[]) => {
+ if (checks.length === 0) return [];
+ const formattedData: { score: number; date: string }[] = [];
+
+ const calculateScore = (entry: CheckSnapshot) => {
+ const accessibility = entry?.accessibility || 0;
+ const bestPractices = entry?.bestPractices || 0;
+ const performance = entry?.performance || 0;
+ const seo = entry?.seo || 0;
+ return Math.floor((accessibility + bestPractices + performance + seo) / 4);
+ };
+
+ checks.forEach((entry) => {
+ const score = calculateScore(entry);
+ const date = entry.createdAt;
+ formattedData.push({ score, date });
+ });
+
+ return formattedData;
+};
+
+export const HistogramPageSpeed = ({
+ checks,
+ status,
+}: {
+ checks: CheckSnapshot[];
+ status: MonitorStatus;
+}) => {
+ const theme = useTheme();
+ const scores = processData(checks ?? []);
+ const color = getStatusColor(status, theme);
+ return (
+ event.stopPropagation()}
+ overflow={"hidden"}
+ sx={{
+ cursor: "default",
+ position: "relative",
+ }}
+ >
+
+
+
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/monitors/charts/HistogramPageSpeedDetails.tsx b/client/src/Components/v2/monitors/charts/HistogramPageSpeedDetails.tsx
new file mode 100644
index 000000000..05bee8bc8
--- /dev/null
+++ b/client/src/Components/v2/monitors/charts/HistogramPageSpeedDetails.tsx
@@ -0,0 +1,144 @@
+import { BaseChart } from "@/Components/v2/design-elements";
+import { TrendingUp } from "lucide-react";
+import { XTick } from "@/Components/v2/monitors/";
+import {
+ XAxis,
+ AreaChart,
+ Area,
+ Tooltip,
+ CartesianGrid,
+ ResponsiveContainer,
+} from "recharts";
+import { HistogramPageSpeedScoresTooltip } from "@/Components/v2/monitors";
+
+import { useTheme } from "@mui/material/styles";
+import type { CheckSnapshot } from "@/Types/Check";
+import type { Palette } from "@mui/material/styles";
+type PaletteColorKey = Extract<
+ keyof Palette,
+ "primary" | "error" | "success" | "warning"
+>;
+
+export interface ConfigItem {
+ id: string;
+ text: string;
+ palette: PaletteColorKey;
+}
+
+const config: Record = {
+ seo: {
+ id: "seo",
+ text: "SEO",
+ palette: "primary",
+ },
+ performance: {
+ id: "performance",
+ text: "performance",
+ palette: "success",
+ },
+ bestPractices: {
+ id: "bestPractices",
+ text: "best practices",
+ palette: "warning",
+ },
+ accessibility: {
+ id: "accessibility",
+ text: "accessibility",
+ palette: "error",
+ },
+};
+
+export const HistogramPageSpeedDetails = ({
+ checks,
+ range,
+}: {
+ checks: CheckSnapshot[];
+ range: string;
+}) => {
+ const theme = useTheme();
+ return (
+
+ }
+ title="Score history"
+ >
+
+
+ (
+
+ )}
+ />
+
+ }
+ />
+
+ {Object.values(config).map(({ id, palette }) => {
+ const startColor = theme.palette[palette].main;
+ const endColor = theme.palette[palette].light;
+
+ return (
+
+
+
+
+ );
+ })}
+
+ {Object.keys(config).map((key) => {
+ const { palette } = config[key];
+ const strokeColor = theme.palette[palette].main;
+ const bgColor = theme.palette.primary.main;
+
+ return (
+
+ );
+ })}
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/monitors/charts/HistogramPageSpeedDetailsTooltip.tsx b/client/src/Components/v2/monitors/charts/HistogramPageSpeedDetailsTooltip.tsx
new file mode 100644
index 000000000..7219ba821
--- /dev/null
+++ b/client/src/Components/v2/monitors/charts/HistogramPageSpeedDetailsTooltip.tsx
@@ -0,0 +1,78 @@
+import Box from "@mui/material/Box";
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import { useTheme } from "@mui/material/styles";
+import { formatDateWithTz } from "@/Utils/TimeUtils";
+import type { ConfigItem } from "@/Components/v2/monitors";
+import type { TooltipProps } from "recharts";
+import { useSelector } from "react-redux";
+import type { RootState } from "@/Types/state";
+
+interface HistogramPageSpeedScoresTooltipProps
+ extends Partial> {
+ config: Record;
+}
+export const HistogramPageSpeedScoresTooltip = ({
+ active,
+ payload,
+ label,
+ config,
+}: HistogramPageSpeedScoresTooltipProps) => {
+ const theme = useTheme();
+ const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
+
+ if (active && payload && payload.length) {
+ return (
+
+
+ {formatDateWithTz(label, "ddd, MMMM D, YYYY, h:mm A", uiTimezone)}
+
+ {Object.keys(config)
+ .reverse()
+ .map((key) => {
+ const { palette } = config[key];
+ const dotColor = theme.palette[palette].main;
+
+ return (
+
+
+
+ {config[key].text}
+
+ {Math.floor(payload[0].payload[key])}
+
+ );
+ })}
+
+ );
+ }
+ return null;
+};
diff --git a/client/src/Components/v2/monitors/charts/HistogramPageSpeedTooltip.tsx b/client/src/Components/v2/monitors/charts/HistogramPageSpeedTooltip.tsx
new file mode 100644
index 000000000..28d43a66e
--- /dev/null
+++ b/client/src/Components/v2/monitors/charts/HistogramPageSpeedTooltip.tsx
@@ -0,0 +1,39 @@
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import { formatDateWithTz } from "@/Utils/TimeUtils";
+import type { TooltipProps } from "recharts";
+import { useTheme } from "@mui/material/styles";
+import { useSelector } from "react-redux";
+import type { RootState } from "@/Types/state";
+
+export const HistogramPageSpeedTooltip = ({
+ active,
+ payload,
+}: TooltipProps) => {
+ const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
+ const theme = useTheme();
+
+ if (!active || !payload || payload.length === 0) {
+ return null;
+ }
+
+ const data = payload[0]?.payload;
+
+ return (
+
+
+ {formatDateWithTz(data?.date, "ddd, MMMM D, YYYY, HH:mm A", uiTimezone)}
+
+ Score: {data?.score}
+
+ );
+};
diff --git a/client/src/Components/v2/monitors/charts/HistogramStatus.tsx b/client/src/Components/v2/monitors/charts/HistogramStatus.tsx
new file mode 100644
index 000000000..1c25c339e
--- /dev/null
+++ b/client/src/Components/v2/monitors/charts/HistogramStatus.tsx
@@ -0,0 +1,166 @@
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import { BaseChart } from "@/Components/v2/design-elements";
+import { useTheme } from "@mui/material/styles";
+import { useSelector } from "react-redux";
+import { formatDateWithTz } from "@/Utils/timeUtilsLegacy";
+import { useTranslation } from "react-i18next";
+import { ResponsiveContainer, BarChart, XAxis, Bar, Cell, Tooltip } from "recharts";
+import { getResponseTimeColor } from "@/Utils/MonitorUtils";
+import type { GroupedCheck } from "@/Types/Check";
+import type { RootState } from "@/Types/state";
+
+interface XLabelProps {
+ p1: GroupedCheck;
+ p2: GroupedCheck;
+ range: string;
+}
+
+const XLabel = ({ p1, p2, range }: XLabelProps) => {
+ const theme = useTheme();
+ const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
+ const dateFormat = range === "day" ? "MMM D, h:mm A" : "MMM D";
+ return (
+ <>
+
+ {formatDateWithTz(p1.bucketDate, dateFormat, uiTimezone)}
+
+
+ {formatDateWithTz(p2.bucketDate, dateFormat, uiTimezone)}
+
+ >
+ );
+};
+
+interface HistogramStatusProps {
+ title: string;
+ icon: React.ReactNode;
+ checks?: GroupedCheck[];
+ range: string;
+}
+
+export const HistogramStatus = ({
+ title,
+ icon,
+ checks = [],
+ range,
+}: HistogramStatusProps) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const CustomTooltip = ({
+ active,
+ payload,
+ }: {
+ active?: boolean;
+ payload?: Array<{ payload: GroupedCheck }>;
+ }) => {
+ const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
+ if (!active || !payload?.length) return null;
+ const d = payload[0]?.payload;
+ const avg = d?.avgResponseTime ?? 0;
+ const titleText = t("common.charts.labels.averageResponseTime");
+ const fmt = range === "30d" ? "MMM D, YYYY" : "ddd, MMM D, YYYY, h:mm A";
+ let dateLabel = "";
+ if (d?.bucketDate) {
+ const base = new Date(d.bucketDate);
+ const midBucket =
+ range === "30d" ? new Date(base.getTime() + 12 * 60 * 60 * 1000) : base;
+ dateLabel = formatDateWithTz(midBucket.toISOString(), fmt, uiTimezone);
+ }
+ return (
+
+ {dateLabel ? (
+
+ {dateLabel}
+
+ ) : null}
+ {titleText}
+ {Math.floor(avg)} ms
+
+ );
+ };
+
+ const totalChecks = checks.reduce((acc, check) => acc + (check.totalChecks || 0), 0);
+
+ return (
+
+
+
+
+ Total checks: {totalChecks}
+
+
+
+
+
+ }
+ />
+ }
+ />
+
+ {checks?.map((groupedCheck, index) => {
+ const fillColor = getResponseTimeColor(groupedCheck.avgResponseTime);
+ return (
+ |
+ );
+ })}
+
+
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/monitors/charts/PiePageSpeed.tsx b/client/src/Components/v2/monitors/charts/PiePageSpeed.tsx
new file mode 100644
index 000000000..205ec9a96
--- /dev/null
+++ b/client/src/Components/v2/monitors/charts/PiePageSpeed.tsx
@@ -0,0 +1,161 @@
+import { BaseChart } from "@/Components/v2/design-elements";
+import { FileText } from "lucide-react";
+import type { CheckAudits, CheckSnapshot } from "@/Types/Check";
+import { Pie, PieChart, ResponsiveContainer, Label } from "recharts";
+import Typography from "@mui/material/Typography";
+import Box from "@mui/material/Box";
+import { getPageSpeedPalette } from "@/Utils/MonitorUtils";
+import { useTheme } from "@mui/material/styles";
+import { alpha } from "@mui/material/styles";
+import { useState } from "react";
+import Tooltip from "@mui/material/Tooltip";
+import { useTranslation } from "react-i18next";
+
+const CenterLabel = ({ viewBox, value }: any) => {
+ const { cx, cy } = viewBox;
+ return (
+
+
+ {value}
+
+
+ );
+};
+
+export const PiePageSpeed = ({ latestCheck }: { latestCheck?: CheckSnapshot }) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const [hoverTitle, setHoverTitle] = useState(null);
+
+ if (!latestCheck) return null;
+ const LABELS: Record = {
+ FCP: t("pages.pageSpeed.charts.common.fcp"),
+ SI: t("pages.pageSpeed.charts.common.si"),
+ LCP: t("pages.pageSpeed.charts.common.lcp"),
+ TBT: t("pages.pageSpeed.charts.common.tbt"),
+ CLS: t("pages.pageSpeed.charts.common.cls"),
+ };
+ const metrics: { key: keyof CheckAudits; color: string; weight: number }[] = [
+ { key: "fcp", color: alpha("#1DE9B6", 0.6), weight: 0.1 },
+ { key: "si", color: alpha("#7C4DFF", 0.6), weight: 0.1 },
+ { key: "lcp", color: alpha("#FFB200", 0.6), weight: 0.25 },
+ { key: "tbt", color: alpha("#00AFFE", 0.6), weight: 0.3 },
+ { key: "cls", color: alpha("#FF4181", 0.6), weight: 0.25 },
+ ];
+
+ const scores = metrics.flatMap(({ key, color, weight }) => {
+ const audit = latestCheck?.audits?.[key];
+ const val = Math.floor((audit?.score ?? 0) * 100);
+ const inverse = 100 - val;
+ return [
+ {
+ name: `${key.toUpperCase()} Inverse`,
+ value: inverse * weight,
+ fill: "transparent",
+ stroke: color,
+ weight,
+ },
+ {
+ name: key.toUpperCase(),
+ value: val * weight,
+ fill: color,
+ stroke: color,
+ weight,
+ },
+ ];
+ });
+
+ const totalScore =
+ (latestCheck.audits?.fcp?.score || 0) * 0.1 +
+ (latestCheck.audits?.si?.score || 0) * 0.1 +
+ (latestCheck.audits?.lcp?.score || 0) * 0.25 +
+ (latestCheck.audits?.tbt?.score || 0) * 0.3 +
+ (latestCheck.audits?.cls?.score || 0) * 0.25;
+ const pageSpeedPalette = getPageSpeedPalette(Math.floor(totalScore * 100));
+
+ const score = [
+ {
+ value: 100,
+ fill: alpha(theme.palette[pageSpeedPalette].light || "#ffffff", 0.6),
+ stroke: "none",
+ },
+ ];
+
+ return (
+
+ }
+ title={t("pages.pageSpeed.charts.pie.title")}
+ >
+
+
+
+
+
+ }
+ />
+
+ `${name}: ${Math.round(value)}`}
+ dataKey="value"
+ stroke="none"
+ onMouseEnter={(_, index: number) => {
+ const d = scores[index];
+ if (!d) return;
+ const isInverse = String(d.name).includes("Inverse");
+ const pair = isInverse && scores[index + 1] ? scores[index + 1] : d;
+ const base = String(pair.name).replace(" Inverse", "");
+ const full = LABELS[base] ?? base;
+ const displayVal = Math.round(Number(pair.value) || 0);
+ setHoverTitle(`${full}: ${displayVal}`);
+ }}
+ onMouseLeave={() => setHoverTitle(null)}
+ />
+
+
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/monitors/charts/PiePageSpeedLegend.tsx b/client/src/Components/v2/monitors/charts/PiePageSpeedLegend.tsx
new file mode 100644
index 000000000..b98b09b33
--- /dev/null
+++ b/client/src/Components/v2/monitors/charts/PiePageSpeedLegend.tsx
@@ -0,0 +1,105 @@
+import Stack from "@mui/material/Stack";
+import Box from "@mui/material/Box";
+import { BarChart3 } from "lucide-react";
+import { BaseChart } from "@/Components/v2/design-elements";
+import Typography from "@mui/material/Typography";
+
+import type { CheckSnapshot } from "@/Types/Check";
+import { useTranslation } from "react-i18next";
+import { getPageSpeedPalette } from "@/Utils/MonitorUtils";
+import { useTheme } from "@mui/material/styles";
+
+const MetricBox = ({
+ label,
+ value,
+ weight,
+}: {
+ label: string;
+ value: number;
+ weight: number;
+}) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const palette = getPageSpeedPalette(value);
+ return (
+
+
+ {label}
+
+ {`${value}%`}
+ {`${t("pages.pageSpeed.charts.legend.weight")}: ${weight}%`}
+
+
+
+
+ );
+};
+
+export const PiePageSpeedLegend = ({ latestCheck }: { latestCheck?: CheckSnapshot }) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+
+ if (!latestCheck) {
+ return null;
+ }
+ return (
+
+ }
+ title={t("pages.pageSpeed.charts.legend.title")}
+ >
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/client/src/Components/v2/Monitors/ChartAvgResponse.tsx b/client/src/Components/v2/monitors/charts/RadialAvgResponse.tsx
similarity index 68%
rename from client/src/Components/v2/Monitors/ChartAvgResponse.tsx
rename to client/src/Components/v2/monitors/charts/RadialAvgResponse.tsx
index e9b85cb43..b54bfc764 100644
--- a/client/src/Components/v2/Monitors/ChartAvgResponse.tsx
+++ b/client/src/Components/v2/monitors/charts/RadialAvgResponse.tsx
@@ -1,13 +1,15 @@
-import { BaseChart } from "./HistogramStatus";
+import { BaseChart } from "@/Components/v2/design-elements";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
-import AverageResponseIcon from "@/assets/icons/average-response-icon.svg?react";
+import { Gauge } from "lucide-react";
import { Cell, RadialBarChart, RadialBar, ResponsiveContainer } from "recharts";
-import { getResponseTimeColor } from "@/Utils/v2/MonitorUtils";
+import { useTranslation } from "react-i18next";
+import { getResponseTimeColor } from "@/Utils/MonitorUtils";
import { useTheme } from "@mui/material/styles";
-export const ChartAvgResponse = ({ avg, max }: { avg: number; max: number }) => {
+export const RadialAvgResponse = ({ avg, max }: { avg: number; max: number }) => {
+ const { t } = useTranslation();
const theme = useTheme();
const chartData = [
{ name: "max", value: max - avg, color: "transparent" },
@@ -23,20 +25,25 @@ export const ChartAvgResponse = ({ avg, max }: { avg: number; max: number }) =>
return (
}
- title={"Average response time"}
+ icon={
+
+ }
+ title={t("common.charts.labels.averageResponseTime")}
>
>
|
|
@@ -56,8 +63,8 @@ export const ChartAvgResponse = ({ avg, max }: { avg: number; max: number }) =>
direction={"row"}
justifyContent={"space-between"}
>
- Low
- High
+ {t("common.charts.labels.low")}
+ {t("common.charts.labels.high")}
{
- try {
- const res = await networkService.registerUser(form);
- return res.data;
- } catch (error) {
- if (error.response.data) {
- return thunkApi.rejectWithValue(error.response.data);
+export const register = createAsyncThunk(
+ "auth/register",
+ async ({ user, token }, thunkApi) => {
+ try {
+ const res = await networkService.registerUser(user, token);
+ return res.data;
+ } catch (error) {
+ if (error.response.data) {
+ return thunkApi.rejectWithValue(error.response.data);
+ }
+ const payload = {
+ status: false,
+ msg: error.message ? error.message : "Unknown error",
+ };
+ return thunkApi.rejectWithValue(payload);
}
- const payload = {
- status: false,
- msg: error.message ? error.message : "Unknown error",
- };
- return thunkApi.rejectWithValue(payload);
}
-});
+);
export const login = createAsyncThunk("auth/login", async (form, thunkApi) => {
try {
diff --git a/client/src/Features/Auth/v2AuthSlice.ts b/client/src/Features/Auth/v2AuthSlice.ts
deleted file mode 100644
index 28a18b659..000000000
--- a/client/src/Features/Auth/v2AuthSlice.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { createSlice } from "@reduxjs/toolkit";
-
-const initialState = {
- isAuthenticated: false,
-};
-
-const v2AuthSlice = createSlice({
- name: "v2Auth",
- initialState,
- reducers: {
- setIsAuthenticated: (state, action) => {
- const { authenticated } = action.payload;
- state.isAuthenticated = authenticated;
- },
- },
-});
-
-export default v2AuthSlice.reducer;
-export const { setIsAuthenticated } = v2AuthSlice.actions;
diff --git a/client/src/Features/UI/uiSlice.js b/client/src/Features/UI/uiSlice.js
index 63f7ae90e..65aff99a8 100644
--- a/client/src/Features/UI/uiSlice.js
+++ b/client/src/Features/UI/uiSlice.js
@@ -29,6 +29,7 @@ const initialState = {
distributedUptimeEnabled: false,
language: "en",
starPromptOpen: true,
+ chartType: "histogram",
};
const uiSlice = createSlice({
@@ -58,6 +59,9 @@ const uiSlice = createSlice({
state.showURL = action.payload;
},
setGreeting(state, action) {
+ if (!state.greeting) {
+ state.greeting = { index: 0, lastUpdate: null };
+ }
state.greeting.index = action.payload.index;
state.greeting.lastUpdate = action.payload.lastUpdate;
},
@@ -70,6 +74,9 @@ const uiSlice = createSlice({
setStarPromptOpen: (state, action) => {
state.starPromptOpen = action.payload;
},
+ setChartType: (state, action) => {
+ state.chartType = action.payload;
+ },
},
});
@@ -85,4 +92,5 @@ export const {
setDistributedUptimeEnabled,
setLanguage,
setStarPromptOpen,
+ setChartType,
} = uiSlice.actions;
diff --git a/client/src/Hooks/UseApi.ts b/client/src/Hooks/UseApi.ts
new file mode 100644
index 000000000..aa71f01af
--- /dev/null
+++ b/client/src/Hooks/UseApi.ts
@@ -0,0 +1,144 @@
+import useSWR from "swr";
+import type { SWRConfiguration } from "swr";
+import type { AxiosRequestConfig } from "axios";
+import { get, patch, post, deleteOp } from "@/Utils/ApiClient";
+import { useState } from "react";
+import { useToast } from "@/Hooks/UseToast";
+
+interface ApiResponse {
+ success: boolean;
+ msg: string;
+ data: T;
+}
+
+const fetcher = async (url: string, config?: AxiosRequestConfig) => {
+ const res = await get>(url, config);
+ return res.data;
+};
+
+export const useGet = (
+ url: string | null,
+ axiosConfig?: AxiosRequestConfig,
+ swrConfig?: SWRConfiguration
+) => {
+ const { data, error, isLoading, mutate } = useSWR>(
+ url,
+ (url: string) => fetcher(url, axiosConfig),
+ swrConfig
+ );
+
+ return {
+ data: data?.data ?? null,
+ isLoading,
+ error,
+ refetch: mutate,
+ };
+};
+
+export const usePost = () => {
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+ const { toastError, toastSuccess } = useToast();
+
+ const postFn = async (
+ endpoint: string,
+ body: B,
+ config?: AxiosRequestConfig
+ ): Promise | null> => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ const res = await post>(endpoint, body, {
+ ...config,
+ headers: {
+ ...config?.headers,
+ },
+ });
+ toastSuccess(res.data?.msg || "Operation successful");
+
+ return res.data;
+ } catch (err: any) {
+ const errMsg = err?.response?.data?.msg || err.message || "An error occurred";
+ console.error(err);
+ toastError(errMsg);
+ setError(errMsg);
+ return null;
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return { post: postFn, loading, error };
+};
+
+export const usePatch = () => {
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+ const { toastError, toastSuccess } = useToast();
+
+ const patchFn = async (
+ endpoint: string,
+ body?: B,
+ config?: AxiosRequestConfig
+ ): Promise | null> => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ const res = await patch>(endpoint, body, {
+ ...config,
+ headers: {
+ ...config?.headers,
+ },
+ });
+ toastSuccess(res.data?.msg || "Operation successful");
+ return res.data;
+ } catch (err: any) {
+ console.error(err);
+ const errMsg = err?.response?.data?.msg || err.message || "An error occurred";
+ toastError(errMsg);
+ setError(errMsg);
+ return null;
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return { patch: patchFn, loading, error };
+};
+
+export const useDelete = () => {
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+ const { toastError, toastSuccess } = useToast();
+
+ const deleteFn = async (
+ endpoint: string,
+ config?: AxiosRequestConfig
+ ): Promise | null> => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ const res = await deleteOp>(endpoint, {
+ ...config,
+ headers: {
+ ...config?.headers,
+ },
+ });
+ toastSuccess(res.data?.msg || "Operation successful");
+ return res.data;
+ } catch (err: any) {
+ console.error(err);
+ const errMsg = err?.response?.data?.message || err.message || "An error occurred";
+ toastError(errMsg);
+ setError(errMsg);
+ return null;
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return { deleteFn, loading, error };
+};
diff --git a/client/src/Hooks/UseToast.ts b/client/src/Hooks/UseToast.ts
new file mode 100644
index 000000000..8823aa675
--- /dev/null
+++ b/client/src/Hooks/UseToast.ts
@@ -0,0 +1,24 @@
+import { toast, type ToastOptions } from "react-toastify";
+
+export const useToast = () => {
+ const showToast = (message: string, options?: ToastOptions) => {
+ toast.dismiss();
+ const baseStyle: React.CSSProperties = {
+ whiteSpace: "pre-line",
+ wordBreak: "break-word",
+ };
+ toast(message, {
+ ...options,
+ style: { ...baseStyle, ...(options?.style || {}) },
+ });
+ };
+
+ const toastSuccess = (msg: string, opts?: ToastOptions) =>
+ showToast(msg, { ...opts, type: "success" });
+ const toastError = (msg: string, opts?: ToastOptions) =>
+ showToast(msg, { ...opts, type: "error" });
+ const toastInfo = (msg: string, opts?: ToastOptions) =>
+ showToast(msg, { ...opts, type: "info" });
+
+ return { showToast, toastSuccess, toastError, toastInfo };
+};
diff --git a/client/src/Hooks/v1/checkHooks.js b/client/src/Hooks/checkHooks.js
similarity index 79%
rename from client/src/Hooks/v1/checkHooks.js
rename to client/src/Hooks/checkHooks.js
index 96fd1fe1c..2d7edd677 100644
--- a/client/src/Hooks/v1/checkHooks.js
+++ b/client/src/Hooks/checkHooks.js
@@ -1,6 +1,6 @@
import { useState, useEffect } from "react";
-import { networkService } from "../../main.jsx";
-import { createToast } from "../../Utils/toastUtils.jsx";
+import { networkService } from "../main.jsx";
+import { createToast } from "../Utils/toastUtils.jsx";
import { useTranslation } from "react-i18next";
const useFetchChecksTeam = ({
@@ -137,7 +137,7 @@ const useFetchChecksByMonitor = ({
return [checks, checksCount, isLoading, networkError];
};
-const useFetchChecksSummaryByTeamId = ({ updateTrigger } = {}) => {
+const useFetchChecksSummaryByTeamId = ({ dateRange, updateTrigger } = {}) => {
const [summary, setSummary] = useState(undefined);
const [isLoading, setIsLoading] = useState(false);
const [networkError, setNetworkError] = useState(false);
@@ -147,7 +147,7 @@ const useFetchChecksSummaryByTeamId = ({ updateTrigger } = {}) => {
try {
setIsLoading(true);
- const res = await networkService.getChecksAndSummaryByTeamId();
+ const res = await networkService.getChecksAndSummaryByTeamId({ dateRange });
setSummary(res.data.data);
} catch (error) {
setNetworkError(true);
@@ -158,7 +158,7 @@ const useFetchChecksSummaryByTeamId = ({ updateTrigger } = {}) => {
};
fetchSummary();
- }, [updateTrigger]);
+ }, [dateRange, updateTrigger]);
return [summary, isLoading, networkError];
};
@@ -185,36 +185,9 @@ const useResolveIncident = () => {
return [resolveIncident, isLoading];
};
-const useAcknowledgeChecks = () => {
- const [isLoading, setIsLoading] = useState(false);
- const { t } = useTranslation();
-
- const acknowledge = async (setUpdateTrigger, monitorId = null) => {
- setIsLoading(true);
- try {
- if (monitorId) {
- await networkService.updateMonitorChecksStatus({ monitorId, ack: true });
- } else {
- await networkService.updateAllChecksStatus({ ack: true });
- }
- setUpdateTrigger((prev) => !prev);
- } catch (error) {
- const toastMessage = monitorId
- ? t("checkHooks.failureResolveMonitor")
- : t("checkHooks.failureResolveAll");
- createToast({ body: toastMessage });
- } finally {
- setIsLoading(false);
- }
- };
-
- return { acknowledge, isLoading };
-};
-
export {
useFetchChecksByMonitor,
useFetchChecksTeam,
useFetchChecksSummaryByTeamId,
useResolveIncident,
- useAcknowledgeChecks,
};
diff --git a/client/src/Hooks/v1/inviteHooks.js b/client/src/Hooks/inviteHooks.js
similarity index 97%
rename from client/src/Hooks/v1/inviteHooks.js
rename to client/src/Hooks/inviteHooks.js
index b6a0851af..c862eec31 100644
--- a/client/src/Hooks/v1/inviteHooks.js
+++ b/client/src/Hooks/inviteHooks.js
@@ -1,5 +1,5 @@
import { useState } from "react";
-import { networkService } from "../../main.jsx";
+import { networkService } from "../main.jsx";
import { useTranslation } from "react-i18next";
const CLIENT_HOST = import.meta.env.VITE_APP_CLIENT_HOST;
diff --git a/client/src/Hooks/v1/logHooks.js b/client/src/Hooks/logHooks.js
similarity index 96%
rename from client/src/Hooks/v1/logHooks.js
rename to client/src/Hooks/logHooks.js
index ff4d13872..d5162de5a 100644
--- a/client/src/Hooks/v1/logHooks.js
+++ b/client/src/Hooks/logHooks.js
@@ -1,6 +1,6 @@
import { useState, useEffect } from "react";
-import { networkService } from "../../main.jsx";
-import { createToast } from "../../Utils/toastUtils.jsx";
+import { networkService } from "../main.jsx";
+import { createToast } from "../Utils/toastUtils.jsx";
import { useTranslation } from "react-i18next";
const useFetchLogs = () => {
diff --git a/client/src/Hooks/v1/monitorHooks.js b/client/src/Hooks/monitorHooks.js
similarity index 80%
rename from client/src/Hooks/v1/monitorHooks.js
rename to client/src/Hooks/monitorHooks.js
index e6b4b5261..9d46e9365 100644
--- a/client/src/Hooks/v1/monitorHooks.js
+++ b/client/src/Hooks/monitorHooks.js
@@ -1,12 +1,12 @@
import { useEffect, useState } from "react";
-import { networkService } from "../../main.jsx";
-import { createToast } from "../../Utils/toastUtils.jsx";
+import { networkService } from "../main.jsx";
+import { createToast } from "../Utils/toastUtils.jsx";
import { useTheme } from "@emotion/react";
import { useMonitorUtils } from "./useMonitorUtils.js";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
-const useFetchMonitorsWithSummary = ({ types, monitorUpdateTrigger }) => {
+export const useFetchMonitorsWithSummary = ({ types, monitorUpdateTrigger }) => {
const [isLoading, setIsLoading] = useState(false);
const [monitors, setMonitors] = useState(undefined);
const [monitorsSummary, setMonitorsSummary] = useState(undefined);
@@ -37,7 +37,7 @@ const useFetchMonitorsWithSummary = ({ types, monitorUpdateTrigger }) => {
return [monitors, monitorsSummary, isLoading, networkError];
};
-const useFetchMonitorsWithChecks = ({
+export const useFetchMonitorsWithChecks = ({
types,
limit,
page,
@@ -50,6 +50,7 @@ const useFetchMonitorsWithChecks = ({
const [isLoading, setIsLoading] = useState(false);
const [count, setCount] = useState(undefined);
const [monitors, setMonitors] = useState(undefined);
+ const [summary, setSummary] = useState(undefined);
const [networkError, setNetworkError] = useState(false);
const theme = useTheme();
@@ -67,12 +68,14 @@ const useFetchMonitorsWithChecks = ({
field,
order,
});
- const { count, monitors } = res?.data?.data ?? {};
+
+ const { count, monitors, summary } = res?.data?.data ?? {};
const mappedMonitors = monitors.map((monitor) =>
getMonitorWithPercentage(monitor, theme)
);
+ setSummary(summary);
setMonitors(mappedMonitors);
- setCount(count?.monitorsCount ?? 0);
+ setCount(count || 0);
} catch (error) {
console.error(error);
setNetworkError(true);
@@ -96,25 +99,12 @@ const useFetchMonitorsWithChecks = ({
types,
monitorUpdateTrigger,
]);
- return [monitors, count, isLoading, networkError];
+ return [summary, monitors, count, isLoading, networkError];
};
-const useFetchMonitorsByTeamId = ({
- types,
- limit,
- page,
- rowsPerPage,
- filter,
- field,
- order,
- checkOrder,
- normalize,
- status,
- updateTrigger,
-}) => {
+export const useFetchMonitorsByTeamId = ({ types, filter, updateTrigger }) => {
const [isLoading, setIsLoading] = useState(false);
const [monitors, setMonitors] = useState(undefined);
- const [summary, setSummary] = useState(undefined);
const [networkError, setNetworkError] = useState(false);
useEffect(() => {
@@ -122,20 +112,11 @@ const useFetchMonitorsByTeamId = ({
try {
setIsLoading(true);
const res = await networkService.getMonitorsByTeamId({
- limit,
types,
- page,
- rowsPerPage,
filter,
- field,
- order,
- checkOrder,
- status,
- normalize,
});
- if (res?.data?.data?.filteredMonitors) {
- setMonitors(res.data.data.filteredMonitors);
- setSummary(res.data.data.summary);
+ if (res?.data?.data) {
+ setMonitors(res.data.data);
}
} catch (error) {
setNetworkError(true);
@@ -147,23 +128,11 @@ const useFetchMonitorsByTeamId = ({
}
};
fetchMonitors();
- }, [
- types,
- limit,
- page,
- rowsPerPage,
- filter,
- field,
- order,
- updateTrigger,
- checkOrder,
- normalize,
- status,
- ]);
- return [monitors, summary, isLoading, networkError];
+ }, [types, filter, updateTrigger]);
+ return [monitors, isLoading, networkError];
};
-const useFetchStatsByMonitorId = ({
+export const useFetchStatsByMonitorId = ({
monitorId,
sortOrder,
limit,
@@ -202,7 +171,7 @@ const useFetchStatsByMonitorId = ({
return [monitor, audits, isLoading, networkError];
};
-const useFetchMonitorGames = ({ setGames, updateTrigger }) => {
+export const useFetchMonitorGames = ({ setGames, updateTrigger }) => {
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const fetchGames = async () => {
@@ -221,7 +190,7 @@ const useFetchMonitorGames = ({ setGames, updateTrigger }) => {
return [isLoading];
};
-const useFetchMonitorById = ({ monitorId, setMonitor, updateTrigger }) => {
+export const useFetchMonitorById = ({ monitorId, setMonitor, updateTrigger }) => {
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
if (typeof monitorId === "undefined") {
@@ -244,7 +213,7 @@ const useFetchMonitorById = ({ monitorId, setMonitor, updateTrigger }) => {
return [isLoading];
};
-const useFetchHardwareMonitorById = ({ monitorId, dateRange, updateTrigger }) => {
+export const useFetchHardwareMonitorById = ({ monitorId, dateRange, updateTrigger }) => {
const [isLoading, setIsLoading] = useState(true);
const [networkError, setNetworkError] = useState(false);
const [monitor, setMonitor] = useState(undefined);
@@ -270,8 +239,36 @@ const useFetchHardwareMonitorById = ({ monitorId, dateRange, updateTrigger }) =>
}, [monitorId, dateRange, updateTrigger]);
return [monitor, isLoading, networkError];
};
+export const useFetchPageSpeedMonitorById = ({ monitorId, dateRange, updateTrigger }) => {
+ const [isLoading, setIsLoading] = useState(true);
+ const [networkError, setNetworkError] = useState(false);
+ const [monitor, setMonitor] = useState(undefined);
+ const [monitorStats, setMonitorStats] = useState(undefined);
-const useFetchUptimeMonitorById = ({ monitorId, dateRange, trigger }) => {
+ useEffect(() => {
+ const fetchMonitor = async () => {
+ try {
+ if (!monitorId) {
+ return { monitor: undefined, isLoading: false, networkError: undefined };
+ }
+ const response = await networkService.getPageSpeedDetailsByMonitorId({
+ monitorId: monitorId,
+ dateRange: dateRange,
+ });
+ setMonitor(response.data.data.monitor);
+ setMonitorStats(response.data.data.monitorStats);
+ } catch (error) {
+ setNetworkError(true);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ fetchMonitor();
+ }, [monitorId, dateRange, updateTrigger]);
+ return [monitor, monitorStats, isLoading, networkError];
+};
+
+export const useFetchUptimeMonitorById = ({ monitorId, dateRange, trigger }) => {
const [networkError, setNetworkError] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [monitor, setMonitor] = useState(undefined);
@@ -299,7 +296,7 @@ const useFetchUptimeMonitorById = ({ monitorId, dateRange, trigger }) => {
return [monitor, monitorStats, isLoading, networkError];
};
-const useCreateMonitor = () => {
+export const useCreateMonitor = () => {
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate();
const createMonitor = async ({ monitor, redirect }) => {
@@ -319,7 +316,7 @@ const useCreateMonitor = () => {
return [createMonitor, isLoading];
};
-const useFetchGlobalSettings = () => {
+export const useFetchGlobalSettings = () => {
const [isLoading, setIsLoading] = useState(true);
const [globalSettings, setGlobalSettings] = useState(undefined);
useEffect(() => {
@@ -341,13 +338,13 @@ const useFetchGlobalSettings = () => {
return [globalSettings, isLoading];
};
-const useDeleteMonitor = () => {
+export const useDeleteMonitor = () => {
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate();
const deleteMonitor = async ({ monitor, redirect }) => {
try {
setIsLoading(true);
- await networkService.deleteMonitorById({ monitorId: monitor._id });
+ await networkService.deleteMonitorById({ monitorId: monitor.id });
createToast({ body: "Monitor deleted successfully!" });
if (redirect) {
navigate(redirect);
@@ -361,7 +358,7 @@ const useDeleteMonitor = () => {
return [deleteMonitor, isLoading];
};
-const useUpdateMonitor = () => {
+export const useUpdateMonitor = () => {
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate();
const updateMonitor = async ({ monitor, redirect }) => {
@@ -387,10 +384,11 @@ const useUpdateMonitor = () => {
...(monitor.type === "hardware" && {
thresholds: monitor.thresholds,
secret: monitor.secret,
+ selectedDisks: monitor.selectedDisks,
}),
};
await networkService.updateMonitor({
- monitorId: monitor._id,
+ monitorId: monitor.id,
updatedFields,
});
@@ -407,7 +405,7 @@ const useUpdateMonitor = () => {
return [updateMonitor, isLoading];
};
-const usePauseMonitor = () => {
+export const usePauseMonitor = () => {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(undefined);
const pauseMonitor = async ({ monitorId, triggerUpdate }) => {
@@ -430,7 +428,7 @@ const usePauseMonitor = () => {
return [pauseMonitor, isLoading, error];
};
-const useAddDemoMonitors = () => {
+export const useAddDemoMonitors = () => {
const [isLoading, setIsLoading] = useState(false);
const { t } = useTranslation();
const addDemoMonitors = async () => {
@@ -447,7 +445,7 @@ const useAddDemoMonitors = () => {
return [addDemoMonitors, isLoading];
};
-const useDeleteAllMonitors = () => {
+export const useDeleteAllMonitors = () => {
const [isLoading, setIsLoading] = useState(false);
const { t } = useTranslation();
const deleteAllMonitors = async () => {
@@ -464,7 +462,7 @@ const useDeleteAllMonitors = () => {
return [deleteAllMonitors, isLoading];
};
-const useDeleteMonitorStats = () => {
+export const useDeleteMonitorStats = () => {
const { t } = useTranslation();
const [isLoading, setIsLoading] = useState(false);
const deleteMonitorStats = async () => {
@@ -482,7 +480,7 @@ const useDeleteMonitorStats = () => {
return [deleteMonitorStats, isLoading];
};
-const useCreateBulkMonitors = () => {
+export const useCreateBulkMonitors = () => {
const [isLoading, setIsLoading] = useState(false);
const createBulkMonitors = async (file, user) => {
@@ -505,7 +503,7 @@ const useCreateBulkMonitors = () => {
return [createBulkMonitors, isLoading];
};
-const useExportMonitors = () => {
+export const useExportMonitors = () => {
const [isLoading, setIsLoading] = useState(false);
const { t } = useTranslation();
@@ -538,23 +536,19 @@ const useExportMonitors = () => {
return [exportMonitors, isLoading];
};
-export {
- useFetchMonitorsWithSummary,
- useFetchMonitorsWithChecks,
- useFetchMonitorsByTeamId,
- useFetchStatsByMonitorId,
- useFetchMonitorById,
- useFetchUptimeMonitorById,
- useFetchHardwareMonitorById,
- useCreateMonitor,
- useFetchGlobalSettings,
- useDeleteMonitor,
- useUpdateMonitor,
- usePauseMonitor,
- useAddDemoMonitors,
- useDeleteAllMonitors,
- useDeleteMonitorStats,
- useCreateBulkMonitors,
- useExportMonitors,
- useFetchMonitorGames,
+export const useFetchJson = () => {
+ const [isLoading, setIsLoading] = useState(false);
+ const fetchJson = async () => {
+ try {
+ setIsLoading(true);
+ const res = await networkService.fetchJson();
+ createToast({ body: "JSON fetched successfully" });
+ return res?.data?.data ?? [];
+ } catch (error) {
+ createToast({ body: "Failed to create monitor." });
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ return [fetchJson, isLoading];
};
diff --git a/client/src/Hooks/v1/settingsHooks.js b/client/src/Hooks/settingsHooks.js
similarity index 89%
rename from client/src/Hooks/v1/settingsHooks.js
rename to client/src/Hooks/settingsHooks.js
index 787e46ff9..4693d8daa 100644
--- a/client/src/Hooks/v1/settingsHooks.js
+++ b/client/src/Hooks/settingsHooks.js
@@ -1,6 +1,6 @@
import { useState, useEffect } from "react";
-import { networkService } from "../../main.jsx";
-import { createToast } from "../../Utils/toastUtils.jsx";
+import { networkService } from "../main.jsx";
+import { createToast } from "../Utils/toastUtils.jsx";
import { useTranslation } from "react-i18next";
const useFetchSettings = ({ setSettingsData, setIsApiKeySet, setIsEmailPasswordSet }) => {
@@ -42,11 +42,11 @@ const useSaveSettings = ({
setIsLoading(true);
try {
const settingsResponse = await networkService.updateAppSettings({ settings });
- if (settings.checkTTL) {
- await networkService.updateChecksTTL({
- ttl: settings.checkTTL,
- });
- }
+ // if (settings.checkTTL) {
+ // await networkService.updateChecksTTL({
+ // ttl: settings.checkTTL,
+ // });
+ // }
setIsApiKeySet(settingsResponse.data.data.pagespeedKeySet);
setIsEmailPasswordSet(settingsResponse.data.data.emailPasswordSet);
if (settingsResponse.data.data.pagespeedKeySet === true) {
@@ -56,6 +56,7 @@ const useSaveSettings = ({
setEmailPasswordHasBeenReset(false);
}
setSettingsData(settingsResponse.data.data);
+
createToast({ body: t("settingsSuccessSaved") });
} catch (error) {
createToast({ body: t("settingsFailedToSave") });
diff --git a/client/src/Hooks/v1/useIsAdmin.js b/client/src/Hooks/useIsAdmin.js
similarity index 100%
rename from client/src/Hooks/v1/useIsAdmin.js
rename to client/src/Hooks/useIsAdmin.js
diff --git a/client/src/Hooks/v1/useMonitorUtils.js b/client/src/Hooks/useMonitorUtils.js
similarity index 100%
rename from client/src/Hooks/v1/useMonitorUtils.js
rename to client/src/Hooks/useMonitorUtils.js
diff --git a/client/src/Hooks/v1/useNotifications.js b/client/src/Hooks/useNotifications.js
similarity index 96%
rename from client/src/Hooks/v1/useNotifications.js
rename to client/src/Hooks/useNotifications.js
index d1b4a6652..6c5919fac 100644
--- a/client/src/Hooks/v1/useNotifications.js
+++ b/client/src/Hooks/useNotifications.js
@@ -1,9 +1,9 @@
import { useState, useEffect, useCallback } from "react";
-import { createToast } from "../../Utils/toastUtils.jsx";
-import { networkService } from "../../main.jsx";
+import { createToast } from "../Utils/toastUtils.jsx";
+import { networkService } from "../main.jsx";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
-import { NOTIFICATION_TYPES } from "../../Pages/v1/Notifications/utils.js";
+import { NOTIFICATION_TYPES } from "../Pages/Notifications/utils.js";
const useCreateNotification = () => {
const navigate = useNavigate();
@@ -102,7 +102,7 @@ const useGetNotificationById = (id, setNotification) => {
const notificationData = {
address: notification?.address,
notificationName: notification?.notificationName,
- type: NOTIFICATION_TYPES.find((type) => type.value === notification?.type)?._id,
+ type: NOTIFICATION_TYPES.find((type) => type.value === notification?.type)?.id,
};
setNotification(notificationData);
diff --git a/client/src/Hooks/v1/useSendTestEmail.js b/client/src/Hooks/useSendTestEmail.js
similarity index 93%
rename from client/src/Hooks/v1/useSendTestEmail.js
rename to client/src/Hooks/useSendTestEmail.js
index 0be77954a..7223a489a 100644
--- a/client/src/Hooks/v1/useSendTestEmail.js
+++ b/client/src/Hooks/useSendTestEmail.js
@@ -1,7 +1,7 @@
import { useState } from "react";
-import { networkService } from "../../main.jsx";
+import { networkService } from "../main.jsx";
import { useSelector } from "react-redux";
-import { createToast } from "../../Utils/toastUtils.jsx";
+import { createToast } from "../Utils/toastUtils.jsx";
import { useTranslation } from "react-i18next";
const useSendTestEmail = () => {
diff --git a/client/src/Hooks/useSidebar.js b/client/src/Hooks/useSidebar.js
new file mode 100644
index 000000000..97724b417
--- /dev/null
+++ b/client/src/Hooks/useSidebar.js
@@ -0,0 +1,29 @@
+import { useSelector } from "react-redux";
+
+// CSS variable names for sidebar widths
+const SIDEBAR_WIDTH_VAR = "var(--env-var-side-bar-width)";
+const SIDEBAR_COLLAPSED_WIDTH_VAR = "var(--env-var-side-bar-collapsed-width)";
+
+// Transition timing for sidebar width changes
+const SIDEBAR_TRANSITION = "width 650ms cubic-bezier(0.36, -0.01, 0, 0.77)";
+
+/**
+ * Hook to get sidebar state and computed width
+ * Centralizes sidebar width logic to avoid duplication between Sidebar and HomeLayout
+ *
+ * @returns {Object} Sidebar state and styles
+ * @returns {boolean} collapsed - Whether the sidebar is collapsed
+ * @returns {string} width - CSS width value based on collapsed state
+ * @returns {string} transition - CSS transition for width changes
+ */
+export const useSidebar = () => {
+ const collapsed = useSelector((state) => state.ui.sidebar?.collapsed ?? false);
+
+ return {
+ collapsed,
+ width: collapsed ? SIDEBAR_COLLAPSED_WIDTH_VAR : SIDEBAR_WIDTH_VAR,
+ transition: SIDEBAR_TRANSITION,
+ };
+};
+
+export default useSidebar;
diff --git a/client/src/Hooks/v1/userHooks.js b/client/src/Hooks/userHooks.js
similarity index 94%
rename from client/src/Hooks/v1/userHooks.js
rename to client/src/Hooks/userHooks.js
index f9c454450..3d822f979 100644
--- a/client/src/Hooks/v1/userHooks.js
+++ b/client/src/Hooks/userHooks.js
@@ -1,6 +1,6 @@
import { useEffect, useState, useCallback } from "react";
-import { networkService } from "../../main.jsx";
-import { createToast } from "../../Utils/toastUtils.jsx";
+import { networkService } from "../main.jsx";
+import { createToast } from "../Utils/toastUtils.jsx";
import { useTranslation } from "react-i18next";
export const useGetUser = (userId) => {
diff --git a/client/src/Hooks/v2/UseApi.tsx b/client/src/Hooks/v2/UseApi.tsx
deleted file mode 100644
index 2ee37c0da..000000000
--- a/client/src/Hooks/v2/UseApi.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import { useState } from "react";
-import useSWR from "swr";
-import type { SWRConfiguration } from "swr";
-import type { AxiosRequestConfig } from "axios";
-import { get, post, patch } from "@/Utils/v2/ApiClient"; // your axios wrapper
-
-export type ApiResponse = {
- message: string;
- data: any;
-};
-
-// Generic fetcher for GET requests
-const fetcher = async (url: string, config?: AxiosRequestConfig) => {
- const res = await get(url, config);
- return res.data;
-};
-
-export const useGet = (
- url: string,
- axiosConfig?: AxiosRequestConfig,
- swrConfig?: SWRConfiguration
-) => {
- const { data, error, isLoading, isValidating, mutate } = useSWR(
- url,
- (url) => fetcher(url, axiosConfig),
- swrConfig
- );
-
- return {
- response: data ?? null,
- loading: isLoading,
- isValidating,
- error: error?.message ?? null,
- refetch: mutate,
- };
-};
-
-export const usePost = () => {
- const [loading, setLoading] = useState(false);
- const [error, setError] = useState(null);
-
- const postFn = async (
- endpoint: string,
- body: B,
- config?: AxiosRequestConfig
- ): Promise => {
- setLoading(true);
- setError(null);
-
- try {
- const res = await post(endpoint, body, config);
- return res.data;
- } catch (err: any) {
- const errMsg = err?.response?.data?.msg || err.message || "An error occurred";
- setError(errMsg);
- return null;
- } finally {
- setLoading(false);
- }
- };
-
- return { post: postFn, loading, error };
-};
-
-export const usePatch = () => {
- const [loading, setLoading] = useState(false);
- const [error, setError] = useState(null);
-
- const patchFn = async (
- endpoint: string,
- body?: B,
- config?: AxiosRequestConfig
- ): Promise => {
- setLoading(true);
- setError(null);
-
- try {
- const res = await patch(endpoint, body, config);
- return res.data;
- } catch (err: any) {
- const errMsg = err?.response?.data?.msg || err.message || "An error occurred";
- setError(errMsg);
- return null;
- } finally {
- setLoading(false);
- }
- };
-
- return { patch: patchFn, loading, error };
-};
diff --git a/client/src/Hooks/v2/useInitMonitorForm.tsx b/client/src/Hooks/v2/useInitMonitorForm.tsx
deleted file mode 100644
index 2e98b3753..000000000
--- a/client/src/Hooks/v2/useInitMonitorForm.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { monitorSchema } from "@/Validation/v2/zod";
-import { z } from "zod";
-export const useInitForm = ({
- initialData,
-}: {
- initialData: Partial> | undefined;
-}) => {
- const defaults: z.infer = {
- type: initialData?.type || "http",
- url: initialData?.url || "",
- n: initialData?.n || 3,
- notificationChannels: initialData?.notificationChannels || [],
- name: initialData?.name || "",
- interval: initialData?.interval || "1 minute",
- };
- return { defaults };
-};
diff --git a/client/src/Pages/v1/About/index.jsx b/client/src/Pages/About/index.jsx
similarity index 100%
rename from client/src/Pages/v1/About/index.jsx
rename to client/src/Pages/About/index.jsx
diff --git a/client/src/Pages/v1/Account/EditUser/hooks/editUser.js b/client/src/Pages/Account/EditUser/hooks/editUser.js
similarity index 91%
rename from client/src/Pages/v1/Account/EditUser/hooks/editUser.js
rename to client/src/Pages/Account/EditUser/hooks/editUser.js
index 00660aead..aeb50ffff 100644
--- a/client/src/Pages/v1/Account/EditUser/hooks/editUser.js
+++ b/client/src/Pages/Account/EditUser/hooks/editUser.js
@@ -1,8 +1,8 @@
import { useState, useEffect } from "react";
-import { ROLES } from "../../../../../Utils/roleUtils.js";
-import { editUserValidation } from "../../../../../Validation/validation.js";
+import { ROLES } from "../../../../Utils/roleUtils.js";
+import { editUserValidation } from "../../../../Validation/validation.js";
import Joi from "joi";
-import { createToast } from "../../../../../Utils/toastUtils.jsx";
+import { createToast } from "../../../../Utils/toastUtils.jsx";
import { useTranslation } from "react-i18next";
export const useEditUserForm = (user) => {
const [searchInput, setSearchInput] = useState("");
diff --git a/client/src/Pages/v1/Account/EditUser/index.jsx b/client/src/Pages/Account/EditUser/index.jsx
similarity index 96%
rename from client/src/Pages/v1/Account/EditUser/index.jsx
rename to client/src/Pages/Account/EditUser/index.jsx
index 674a522b8..2b2ac5128 100644
--- a/client/src/Pages/v1/Account/EditUser/index.jsx
+++ b/client/src/Pages/Account/EditUser/index.jsx
@@ -11,8 +11,8 @@ import ChangePasswordModal from "@/Pages/Account/components/ChangePasswordModal/
import { useParams } from "react-router-dom";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
-import { useGetUser, useEditUser } from "../../../../Hooks/v1/userHooks.js";
-import { EDITABLE_ROLES, ROLES } from "../../../../Utils/roleUtils.js";
+import { useGetUser, useEditUser } from "../../../Hooks/userHooks.js";
+import { EDITABLE_ROLES, ROLES } from "../../../Utils/roleUtils.js";
import { useEditUserForm, useValidateEditUserForm } from "./hooks/editUser.js";
import { useSelector } from "react-redux";
diff --git a/client/src/Pages/v1/Account/components/AddMemberMenu/index.jsx b/client/src/Pages/Account/components/AddMemberMenu/index.jsx
similarity index 90%
rename from client/src/Pages/v1/Account/components/AddMemberMenu/index.jsx
rename to client/src/Pages/Account/components/AddMemberMenu/index.jsx
index ab1ba599d..96bea3522 100644
--- a/client/src/Pages/v1/Account/components/AddMemberMenu/index.jsx
+++ b/client/src/Pages/Account/components/AddMemberMenu/index.jsx
@@ -3,7 +3,7 @@ import Button from "@mui/material/Button";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import { useTheme } from "@emotion/react";
-import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
+import Icon from "@/Components/v1/Icon";
import { useTranslation } from "react-i18next";
import Proptypes from "prop-types";
@@ -24,7 +24,13 @@ const AddMemberMenu = ({ handleInviteOpen, handleIsRegisterOpen }) => {
}
+ endIcon={
+
+ }
onClick={handleClick}
>
{t("teamPanel.addTeamMember.addMemberMenu")}
diff --git a/client/src/Pages/v1/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx b/client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx
similarity index 93%
rename from client/src/Pages/v1/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx
rename to client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx
index 58db019a3..1237777ff 100644
--- a/client/src/Pages/v1/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx
+++ b/client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx
@@ -1,5 +1,5 @@
import { useState } from "react";
-import { newOrChangedCredentials } from "../../../../../../Validation/validation.js";
+import { newOrChangedCredentials } from "../../../../../Validation/validation.js";
import { useTranslation } from "react-i18next";
const useAddTeamMember = () => {
const { t } = useTranslation();
diff --git a/client/src/Pages/v1/Account/components/AddTeamMember/index.jsx b/client/src/Pages/Account/components/AddTeamMember/index.jsx
similarity index 97%
rename from client/src/Pages/v1/Account/components/AddTeamMember/index.jsx
rename to client/src/Pages/Account/components/AddTeamMember/index.jsx
index 219658b63..e5280af16 100644
--- a/client/src/Pages/v1/Account/components/AddTeamMember/index.jsx
+++ b/client/src/Pages/Account/components/AddTeamMember/index.jsx
@@ -2,10 +2,10 @@ import { Button, Stack } from "@mui/material";
import { GenericDialog } from "@/Components/v1/Dialog/genericDialog.jsx";
import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
import Select from "@/Components/v1/Inputs/Select/index.jsx";
-import { useGetInviteToken } from "../../../../../Hooks/v1/inviteHooks.js";
+import { useGetInviteToken } from "../../../../Hooks/inviteHooks.js";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
-import { createToast } from "../../../../../Utils/toastUtils.jsx";
+import { createToast } from "../../../../Utils/toastUtils.jsx";
import { useState } from "react";
import PasswordTooltip from "../../../Auth/components/PasswordTooltip.jsx";
import useAddTeamMember from "./hooks/useAddTeamMember.jsx";
diff --git a/client/src/Pages/Account/components/ChangePasswordModal/index.jsx b/client/src/Pages/Account/components/ChangePasswordModal/index.jsx
index cc284a675..725a9b00e 100644
--- a/client/src/Pages/Account/components/ChangePasswordModal/index.jsx
+++ b/client/src/Pages/Account/components/ChangePasswordModal/index.jsx
@@ -2,12 +2,12 @@ import { useState } from "react";
import { Button, Stack } from "@mui/material";
import { GenericDialog } from "@/Components/v1/Dialog/genericDialog";
import TextInput from "@/Components/v1/Inputs/TextInput";
-import PasswordTooltip from "@/Pages/v1/Auth/components/PasswordTooltip";
+import PasswordTooltip from "@/Pages/Auth/components/PasswordTooltip";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
import { createToast } from "../../../../Utils/toastUtils";
import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments";
-import usePasswordFeedback from "@/Pages/v1/Auth/hooks/usePasswordFeedback";
+import usePasswordFeedback from "@/Pages/Auth/hooks/usePasswordFeedback";
import PropTypes from "prop-types";
const ChangePasswordModal = ({ isSaving, isLoading, changePassword }) => {
diff --git a/client/src/Pages/v1/Account/components/PasswordPanel.jsx b/client/src/Pages/Account/components/PasswordPanel.jsx
similarity index 90%
rename from client/src/Pages/v1/Account/components/PasswordPanel.jsx
rename to client/src/Pages/Account/components/PasswordPanel.jsx
index ca2af9205..e889db96c 100644
--- a/client/src/Pages/v1/Account/components/PasswordPanel.jsx
+++ b/client/src/Pages/Account/components/PasswordPanel.jsx
@@ -4,12 +4,12 @@ import { useTheme } from "@emotion/react";
import { Box, Stack, Typography, Button } from "@mui/material";
import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx";
import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
-import { newOrChangedCredentials } from "../../../../Validation/validation.js";
+import { newOrChangedCredentials } from "../../../Validation/validation.js";
import Alert from "@/Components/v1/Alert/index.jsx";
-import { update } from "../../../../Features/Auth/authSlice.js";
+import { update } from "../../../Features/Auth/authSlice.js";
import { useDispatch, useSelector } from "react-redux";
-import { createToast } from "../../../../Utils/toastUtils.jsx";
-import { getTouchedFieldErrors } from "../../../../Validation/error.js";
+import { createToast } from "../../../Utils/toastUtils.jsx";
+import { getTouchedFieldErrors } from "../../../Validation/error.js";
import { useTranslation } from "react-i18next";
const defaultPasswordsState = {
@@ -167,7 +167,11 @@ const PasswordPanel = () => {
value={localData.password}
onChange={handleChange}
error={errors[idToName["edit-current-password"]] ? true : false}
- helperText={errors[idToName["edit-current-password"]]}
+ helperText={
+ errors[idToName["edit-current-password"]]
+ ? t(errors[idToName["edit-current-password"]])
+ : ""
+ }
endAdornment={ }
flex={1}
/>
@@ -193,7 +197,11 @@ const PasswordPanel = () => {
value={localData.newPassword}
onChange={handleChange}
error={errors[idToName["edit-new-password"]] ? true : false}
- helperText={errors[idToName["edit-new-password"]]}
+ helperText={
+ errors[idToName["edit-new-password"]]
+ ? t(errors[idToName["edit-new-password"]])
+ : ""
+ }
endAdornment={ }
flex={1}
/>
@@ -219,7 +227,11 @@ const PasswordPanel = () => {
value={localData.confirm}
onChange={handleChange}
error={errors[idToName["edit-confirm-password"]] ? true : false}
- helperText={errors[idToName["edit-confirm-password"]]}
+ helperText={
+ errors[idToName["edit-confirm-password"]]
+ ? t(errors[idToName["edit-confirm-password"]])
+ : ""
+ }
endAdornment={ }
flex={1}
/>
diff --git a/client/src/Pages/v1/Account/components/ProfilePanel.jsx b/client/src/Pages/Account/components/ProfilePanel.jsx
similarity index 95%
rename from client/src/Pages/v1/Account/components/ProfilePanel.jsx
rename to client/src/Pages/Account/components/ProfilePanel.jsx
index 2035861a2..dfe508879 100644
--- a/client/src/Pages/v1/Account/components/ProfilePanel.jsx
+++ b/client/src/Pages/Account/components/ProfilePanel.jsx
@@ -5,15 +5,11 @@ import { Box, Button, Divider, Stack, Typography } from "@mui/material";
import Avatar from "@/Components/v1/Avatar/index.jsx";
import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
import ImageUpload from "@/Components/v1/Inputs/ImageUpload/index.jsx";
-import { newOrChangedCredentials } from "../../../../Validation/validation.js";
+import { newOrChangedCredentials } from "../../../Validation/validation.js";
import { useDispatch, useSelector } from "react-redux";
-import {
- clearAuthState,
- deleteUser,
- update,
-} from "../../../../Features/Auth/authSlice.js";
-import { createToast } from "../../../../Utils/toastUtils.jsx";
-import { logger } from "../../../../Utils/Logger.js";
+import { clearAuthState, deleteUser, update } from "../../../Features/Auth/authSlice.js";
+import { createToast } from "../../../Utils/toastUtils.jsx";
+import { logger } from "../../../Utils/Logger.js";
import { GenericDialog } from "@/Components/v1/Dialog/genericDialog.jsx";
import Dialog from "@/Components/v1/Dialog/index.jsx";
import { useTranslation } from "react-i18next";
@@ -214,7 +210,11 @@ const ProfilePanel = () => {
autoComplete="given-name"
onChange={handleChange}
error={errors[idToName["edit-first-name"]] ? true : false}
- helperText={errors[idToName["edit-first-name"]]}
+ helperText={
+ errors[idToName["edit-first-name"]]
+ ? t(errors[idToName["edit-first-name"]])
+ : ""
+ }
flex={1}
/>
@@ -232,7 +232,11 @@ const ProfilePanel = () => {
value={localData.lastName}
onChange={handleChange}
error={errors[idToName["edit-last-name"]] ? true : false}
- helperText={errors[idToName["edit-last-name"]]}
+ helperText={
+ errors[idToName["edit-last-name"]]
+ ? t(errors[idToName["edit-last-name"]])
+ : ""
+ }
flex={1}
/>
diff --git a/client/src/Pages/v1/Account/components/RoleTable/index.jsx b/client/src/Pages/Account/components/RoleTable/index.jsx
similarity index 79%
rename from client/src/Pages/v1/Account/components/RoleTable/index.jsx
rename to client/src/Pages/Account/components/RoleTable/index.jsx
index 02dcaaf0d..90a30dcd4 100644
--- a/client/src/Pages/v1/Account/components/RoleTable/index.jsx
+++ b/client/src/Pages/Account/components/RoleTable/index.jsx
@@ -1,9 +1,9 @@
import Typography from "@mui/material/Typography";
import DataTable from "@/Components/v1/Table/index.jsx";
-import DeleteOutlineRoundedIcon from "@mui/icons-material/DeleteOutlineRounded";
+import Icon from "@/Components/v1/Icon";
import { useTranslation } from "react-i18next";
-import { ROLES } from "../../../../../Utils/roleUtils.js";
+import { ROLES } from "../../../../Utils/roleUtils.js";
const RoleTable = ({ roles, handleDeleteRole }) => {
const { t } = useTranslation();
@@ -21,11 +21,13 @@ const RoleTable = ({ roles, handleDeleteRole }) => {
render: (row) => {
if (row === ROLES.SUPERADMIN) return null;
return (
- {
handleDeleteRole(row);
}}
- sx={{ cursor: "pointer" }}
+ style={{ cursor: "pointer" }}
/>
);
},
diff --git a/client/src/Pages/v1/Account/components/TeamPanel.jsx b/client/src/Pages/Account/components/TeamPanel.jsx
similarity index 96%
rename from client/src/Pages/v1/Account/components/TeamPanel.jsx
rename to client/src/Pages/Account/components/TeamPanel.jsx
index 02918eb30..6c658cc60 100644
--- a/client/src/Pages/v1/Account/components/TeamPanel.jsx
+++ b/client/src/Pages/Account/components/TeamPanel.jsx
@@ -4,16 +4,16 @@ import { Button, ButtonGroup, Stack, Typography } from "@mui/material";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
-import { newOrChangedCredentials } from "../../../../Validation/validation.js";
-import { networkService } from "../../../../main.jsx";
-import { createToast } from "../../../../Utils/toastUtils.jsx";
+import { newOrChangedCredentials } from "../../../Validation/validation.js";
+import { networkService } from "../../../main.jsx";
+import { createToast } from "../../../Utils/toastUtils.jsx";
import Select from "@/Components/v1/Inputs/Select/index.jsx";
import { GenericDialog } from "@/Components/v1/Dialog/genericDialog.jsx";
import AddTeamMember from "./AddTeamMember/index.jsx";
import DataTable from "@/Components/v1/Table/index.jsx";
-import { useGetInviteToken } from "../../../../Hooks/v1/inviteHooks.js";
+import { useGetInviteToken } from "../../../Hooks/inviteHooks.js";
import { useNavigate } from "react-router-dom";
-import { useIsSuperAdmin } from "../../../../Hooks/v1/useIsAdmin.js";
+import { useIsSuperAdmin } from "@/Hooks/useIsAdmin.js";
import AddMemberMenu from "./AddMemberMenu/index.jsx";
/**
* TeamPanel component manages the organization and team members,
@@ -104,7 +104,7 @@ const TeamPanel = () => {
team = team.map((member) => ({
...member,
- id: member._id,
+ id: member.id,
role: member.role.map((role) => ROLE_MAP[role]).join(","),
}));
setData(team);
diff --git a/client/src/Pages/v1/Account/index.jsx b/client/src/Pages/Account/index.jsx
similarity index 100%
rename from client/src/Pages/v1/Account/index.jsx
rename to client/src/Pages/Account/index.jsx
diff --git a/client/src/Pages/v1/Auth/CheckEmail.jsx b/client/src/Pages/Auth/CheckEmail.jsx
similarity index 93%
rename from client/src/Pages/v1/Auth/CheckEmail.jsx
rename to client/src/Pages/Auth/CheckEmail.jsx
index c12a84804..95261a768 100644
--- a/client/src/Pages/v1/Auth/CheckEmail.jsx
+++ b/client/src/Pages/Auth/CheckEmail.jsx
@@ -3,13 +3,13 @@ import { useEffect, useState } from "react";
import { useTheme } from "@emotion/react";
import { useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom";
-import { createToast } from "../../../Utils/toastUtils.jsx";
-import { forgotPassword } from "../../../Features/Auth/authSlice.js";
+import { createToast } from "../../Utils/toastUtils.jsx";
+import { forgotPassword } from "../../Features/Auth/authSlice.js";
import { Trans, useTranslation } from "react-i18next";
-import Background from "../../../assets/Images/background-grid.svg?react";
-import EmailIcon from "../../../assets/icons/email.svg?react";
-import Logo from "../../../assets/icons/checkmate-icon.svg?react";
+import Background from "@/assets/Images/background-grid.svg?react";
+import Logo from "@/assets/icons/checkmate-icon.svg?react";
import IconBox from "@/Components/v1/IconBox/index.jsx";
+import Icon from "@/Components/v1/Icon";
import "./index.css";
const CheckEmail = () => {
@@ -160,7 +160,7 @@ const CheckEmail = () => {
svgHeight={24}
mb={theme.spacing(4)}
>
-
+
{t("auth.forgotPassword.heading")}
diff --git a/client/src/Pages/v1/Auth/ForgotPassword.jsx b/client/src/Pages/Auth/ForgotPassword.jsx
similarity index 92%
rename from client/src/Pages/v1/Auth/ForgotPassword.jsx
rename to client/src/Pages/Auth/ForgotPassword.jsx
index 8cbd85d8a..e6ba4c1f7 100644
--- a/client/src/Pages/v1/Auth/ForgotPassword.jsx
+++ b/client/src/Pages/Auth/ForgotPassword.jsx
@@ -1,16 +1,16 @@
import { Box, Stack, Typography, Button } from "@mui/material";
import { useTheme } from "@emotion/react";
-import { createToast } from "../../../Utils/toastUtils.jsx";
+import { createToast } from "../../Utils/toastUtils.jsx";
import { useDispatch, useSelector } from "react-redux";
-import { forgotPassword } from "../../../Features/Auth/authSlice.js";
+import { forgotPassword } from "../../Features/Auth/authSlice.js";
import { useEffect, useState } from "react";
-import { newOrChangedCredentials } from "../../../Validation/validation.js";
+import { newOrChangedCredentials } from "../../Validation/validation.js";
import { useNavigate } from "react-router-dom";
import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
-import Logo from "../../../assets/icons/checkmate-icon.svg?react";
-import Key from "../../../assets/icons/key.svg?react";
-import Background from "../../../assets/Images/background-grid.svg?react";
+import Logo from "@/assets/icons/checkmate-icon.svg?react";
+import Background from "@/assets/Images/background-grid.svg?react";
import IconBox from "@/Components/v1/IconBox/index.jsx";
+import Icon from "@/Components/v1/Icon";
import { Trans, useTranslation } from "react-i18next";
import "./index.css";
@@ -165,7 +165,7 @@ const ForgotPassword = () => {
svgHeight={24}
mb={theme.spacing(4)}
>
-
+
{t("auth.forgotPassword.heading")}
diff --git a/client/src/Pages/v1/Auth/Login/hooks/useLoadingSubmit.jsx b/client/src/Pages/Auth/Login/hooks/useLoadingSubmit.jsx
similarity index 100%
rename from client/src/Pages/v1/Auth/Login/hooks/useLoadingSubmit.jsx
rename to client/src/Pages/Auth/Login/hooks/useLoadingSubmit.jsx
diff --git a/client/src/Pages/v1/Auth/Login/hooks/useLoginForm.jsx b/client/src/Pages/Auth/Login/hooks/useLoginForm.jsx
similarity index 100%
rename from client/src/Pages/v1/Auth/Login/hooks/useLoginForm.jsx
rename to client/src/Pages/Auth/Login/hooks/useLoginForm.jsx
diff --git a/client/src/Pages/v1/Auth/Login/hooks/useLoginSubmit.jsx b/client/src/Pages/Auth/Login/hooks/useLoginSubmit.jsx
similarity index 87%
rename from client/src/Pages/v1/Auth/Login/hooks/useLoginSubmit.jsx
rename to client/src/Pages/Auth/Login/hooks/useLoginSubmit.jsx
index 0eed9bd07..b009793d5 100644
--- a/client/src/Pages/v1/Auth/Login/hooks/useLoginSubmit.jsx
+++ b/client/src/Pages/Auth/Login/hooks/useLoginSubmit.jsx
@@ -1,7 +1,7 @@
import { useDispatch } from "react-redux";
-import { login } from "../../../../../Features/Auth/authSlice.js";
+import { login } from "../../../../Features/Auth/authSlice.js";
import { useNavigate } from "react-router-dom";
-import { createToast } from "../../../../../Utils/toastUtils.jsx";
+import { createToast } from "../../../../Utils/toastUtils.jsx";
import { useTranslation } from "react-i18next";
const useLoginSubmit = () => {
diff --git a/client/src/Pages/v1/Auth/Login/hooks/useValidateLoginForm.jsx b/client/src/Pages/Auth/Login/hooks/useValidateLoginForm.jsx
similarity index 90%
rename from client/src/Pages/v1/Auth/Login/hooks/useValidateLoginForm.jsx
rename to client/src/Pages/Auth/Login/hooks/useValidateLoginForm.jsx
index ca9bef879..0872213ad 100644
--- a/client/src/Pages/v1/Auth/Login/hooks/useValidateLoginForm.jsx
+++ b/client/src/Pages/Auth/Login/hooks/useValidateLoginForm.jsx
@@ -1,5 +1,5 @@
import { useState } from "react";
-import { loginCredentials } from "../../../../../Validation/validation.js";
+import { loginCredentials } from "../../../../Validation/validation.js";
const useValidateLoginForm = () => {
const [errors, setErrors] = useState({
diff --git a/client/src/Pages/v1/Auth/Login/index.jsx b/client/src/Pages/Auth/Login/index.jsx
similarity index 100%
rename from client/src/Pages/v1/Auth/Login/index.jsx
rename to client/src/Pages/Auth/Login/index.jsx
diff --git a/client/src/Pages/v1/Auth/NewPasswordConfirmed.jsx b/client/src/Pages/Auth/NewPasswordConfirmed.jsx
similarity index 89%
rename from client/src/Pages/v1/Auth/NewPasswordConfirmed.jsx
rename to client/src/Pages/Auth/NewPasswordConfirmed.jsx
index 629494098..c92e52552 100644
--- a/client/src/Pages/v1/Auth/NewPasswordConfirmed.jsx
+++ b/client/src/Pages/Auth/NewPasswordConfirmed.jsx
@@ -2,11 +2,11 @@ import { Box, Button, Stack, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router-dom";
import { useDispatch } from "react-redux";
-import { clearAuthState } from "../../../Features/Auth/authSlice.js";
-import Background from "../../../assets/Images/background-grid.svg?react";
-import ConfirmIcon from "../../../assets/icons/check-outlined.svg?react";
-import Logo from "../../../assets/icons/checkmate-icon.svg?react";
+import { clearAuthState } from "../../Features/Auth/authSlice.js";
+import Background from "@/assets/Images/background-grid.svg?react";
+import Logo from "@/assets/icons/checkmate-icon.svg?react";
import IconBox from "@/Components/v1/IconBox/index.jsx";
+import Icon from "@/Components/v1/Icon";
import { Trans, useTranslation } from "react-i18next";
import "./index.css";
@@ -94,7 +94,7 @@ const NewPasswordConfirmed = () => {
svgHeight={24}
mb={theme.spacing(4)}
>
-
+
{t("auth.forgotPassword.heading")}
diff --git a/client/src/Pages/v1/Auth/Register/index.jsx b/client/src/Pages/Auth/Register/index.jsx
similarity index 96%
rename from client/src/Pages/v1/Auth/Register/index.jsx
rename to client/src/Pages/Auth/Register/index.jsx
index d6515b85c..821c56427 100644
--- a/client/src/Pages/v1/Auth/Register/index.jsx
+++ b/client/src/Pages/Auth/Register/index.jsx
@@ -13,11 +13,11 @@ import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import { useParams } from "react-router-dom";
-import { networkService } from "../../../../main.jsx";
-import { newOrChangedCredentials } from "../../../../Validation/validation.js";
-import { register } from "../../../../Features/Auth/authSlice.js";
+import { networkService } from "../../../main.jsx";
+import { newOrChangedCredentials } from "../../../Validation/validation.js";
+import { register } from "../../../Features/Auth/authSlice.js";
import AuthPageWrapper from "../components/AuthPageWrapper.jsx";
-import { createToast } from "../../../../Utils/toastUtils.jsx";
+import { createToast } from "../../../Utils/toastUtils.jsx";
import PropTypes from "prop-types";
const getFeedbackStatus = (form, errors, field, criteria) => {
@@ -176,7 +176,7 @@ const Register = ({ superAdminExists }) => {
delete toSubmit.confirm;
- const action = await dispatch(register(toSubmit));
+ const action = await dispatch(register({ user: toSubmit, token }));
if (action.payload.success) {
navigate("/uptime");
createToast({
diff --git a/client/src/Pages/v1/Auth/SetNewPassword.jsx b/client/src/Pages/Auth/SetNewPassword.jsx
similarity index 94%
rename from client/src/Pages/v1/Auth/SetNewPassword.jsx
rename to client/src/Pages/Auth/SetNewPassword.jsx
index 45f3351f3..ada5d6246 100644
--- a/client/src/Pages/v1/Auth/SetNewPassword.jsx
+++ b/client/src/Pages/Auth/SetNewPassword.jsx
@@ -4,16 +4,16 @@ import { useNavigate } from "react-router-dom";
import { useParams } from "react-router-dom";
import { useTheme } from "@emotion/react";
import { Box, Stack, Typography, Button } from "@mui/material";
-import { setNewPassword } from "../../../Features/Auth/authSlice.js";
-import { createToast } from "../../../Utils/toastUtils.jsx";
-import { newOrChangedCredentials } from "../../../Validation/validation.js";
+import { setNewPassword } from "../../Features/Auth/authSlice.js";
+import { createToast } from "../../Utils/toastUtils.jsx";
+import { newOrChangedCredentials } from "../../Validation/validation.js";
import Check from "@/Components/v1/Check/Check.jsx";
import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx";
import IconBox from "@/Components/v1/IconBox/index.jsx";
-import LockIcon from "../../../assets/icons/lock.svg?react";
-import Logo from "../../../assets/icons/checkmate-icon.svg?react";
-import Background from "../../../assets/Images/background-grid.svg?react";
+import Logo from "@/assets/icons/checkmate-icon.svg?react";
+import Background from "@/assets/Images/background-grid.svg?react";
+import Icon from "@/Components/v1/Icon";
import "./index.css";
import { useValidatePassword } from "./hooks/useValidatePassword.jsx";
import { Trans, useTranslation } from "react-i18next";
@@ -140,7 +140,7 @@ const SetNewPassword = () => {
svgHeight={24}
mb={theme.spacing(4)}
>
-
+
{t("auth.forgotPassword.heading")}
diff --git a/client/src/Pages/v1/Auth/components/AuthHeader.jsx b/client/src/Pages/Auth/components/AuthHeader.jsx
similarity index 87%
rename from client/src/Pages/v1/Auth/components/AuthHeader.jsx
rename to client/src/Pages/Auth/components/AuthHeader.jsx
index a34e83d6a..56b69ef14 100644
--- a/client/src/Pages/v1/Auth/components/AuthHeader.jsx
+++ b/client/src/Pages/Auth/components/AuthHeader.jsx
@@ -1,8 +1,8 @@
// Components
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
-import Logo from "../../../../assets/icons/checkmate-icon.svg?react";
-import LanguageSelector from "../../../../Components/LanguageSelector.jsx";
+import Logo from "@/assets/icons/checkmate-icon.svg?react";
+import LanguageSelector from "@/Components/LanguageSelector.jsx";
import ThemeSwitch from "@/Components/v1/ThemeSwitch/index.jsx";
// Utils
diff --git a/client/src/Pages/v1/Auth/components/AuthPageWrapper.jsx b/client/src/Pages/Auth/components/AuthPageWrapper.jsx
similarity index 93%
rename from client/src/Pages/v1/Auth/components/AuthPageWrapper.jsx
rename to client/src/Pages/Auth/components/AuthPageWrapper.jsx
index 7e358e6ab..a5fbe311b 100644
--- a/client/src/Pages/v1/Auth/components/AuthPageWrapper.jsx
+++ b/client/src/Pages/Auth/components/AuthPageWrapper.jsx
@@ -1,10 +1,10 @@
-import Background from "../../../../assets/Images/background-grid.svg?react";
+import Background from "@/assets/Images/background-grid.svg?react";
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import AuthHeader from "./AuthHeader.jsx";
import { useTheme } from "@mui/material/styles";
-import Logo from "../../../../assets/icons/checkmate-icon.svg?react";
+import Logo from "@/assets/icons/checkmate-icon.svg?react";
import PropTypes from "prop-types";
const AuthPageWrapper = ({ children, heading, welcome }) => {
diff --git a/client/src/Pages/v1/Auth/components/PasswordTooltip.jsx b/client/src/Pages/Auth/components/PasswordTooltip.jsx
similarity index 96%
rename from client/src/Pages/v1/Auth/components/PasswordTooltip.jsx
rename to client/src/Pages/Auth/components/PasswordTooltip.jsx
index 2881eb001..361850aca 100644
--- a/client/src/Pages/v1/Auth/components/PasswordTooltip.jsx
+++ b/client/src/Pages/Auth/components/PasswordTooltip.jsx
@@ -75,7 +75,7 @@ const PasswordTooltip = ({ feedback, form, children }) => {
slotProps={{
tooltip: {
sx: {
- backgroundColor: theme.palette.tertiary.background,
+ backgroundColor: theme.palette.tertiary.main,
border: `0.5px solid ${theme.palette.primary.lowContrast}90`,
borderRadius: theme.spacing(4),
color: theme.palette.primary.contrastText,
@@ -88,7 +88,7 @@ const PasswordTooltip = ({ feedback, form, children }) => {
},
arrow: {
sx: {
- color: theme.palette.tertiary.background,
+ color: theme.palette.tertiary.main,
},
},
}}
diff --git a/client/src/Pages/v1/Auth/hooks/usePasswordFeedback.jsx b/client/src/Pages/Auth/hooks/usePasswordFeedback.jsx
similarity index 96%
rename from client/src/Pages/v1/Auth/hooks/usePasswordFeedback.jsx
rename to client/src/Pages/Auth/hooks/usePasswordFeedback.jsx
index 3c199e25f..73e8fb738 100644
--- a/client/src/Pages/v1/Auth/hooks/usePasswordFeedback.jsx
+++ b/client/src/Pages/Auth/hooks/usePasswordFeedback.jsx
@@ -1,5 +1,5 @@
import { useState } from "react";
-import { newOrChangedCredentials } from "../../../../Validation/validation.js";
+import { newOrChangedCredentials } from "../../../Validation/validation.js";
const usePasswordFeedback = () => {
const [feedback, setFeedback] = useState({});
diff --git a/client/src/Pages/v1/Auth/hooks/useValidatePassword.jsx b/client/src/Pages/Auth/hooks/useValidatePassword.jsx
similarity index 96%
rename from client/src/Pages/v1/Auth/hooks/useValidatePassword.jsx
rename to client/src/Pages/Auth/hooks/useValidatePassword.jsx
index 231b8eeb8..014804d35 100644
--- a/client/src/Pages/v1/Auth/hooks/useValidatePassword.jsx
+++ b/client/src/Pages/Auth/hooks/useValidatePassword.jsx
@@ -1,5 +1,5 @@
import { useMemo, useState } from "react";
-import { newOrChangedCredentials } from "../../../../Validation/validation.js";
+import { newOrChangedCredentials } from "../../../Validation/validation.js";
const getFeedbackStatus = (form, errors, field, criteria) => {
const fieldErrors = errors[field];
diff --git a/client/src/Pages/v1/Auth/index.css b/client/src/Pages/Auth/index.css
similarity index 100%
rename from client/src/Pages/v1/Auth/index.css
rename to client/src/Pages/Auth/index.css
diff --git a/client/src/Pages/v1/Incidents/Components/IncidentTable/index.jsx b/client/src/Pages/Checks/Components/IncidentTable/index.jsx
similarity index 84%
rename from client/src/Pages/v1/Incidents/Components/IncidentTable/index.jsx
rename to client/src/Pages/Checks/Components/IncidentTable/index.jsx
index df457c0e2..bac2a00cb 100644
--- a/client/src/Pages/v1/Incidents/Components/IncidentTable/index.jsx
+++ b/client/src/Pages/Checks/Components/IncidentTable/index.jsx
@@ -13,15 +13,13 @@ import GenericFallback from "@/Components/v1/GenericFallback/index.jsx";
import NetworkError from "@/Components/v1/GenericFallback/NetworkError.jsx";
//Utils
-import { formatDateWithTz } from "../../../../../Utils/timeUtils.js";
+import { formatDateWithTz } from "@/Utils/timeUtilsLegacy.js";
import { useSelector } from "react-redux";
import { useState } from "react";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
-import { useFetchChecksTeam } from "../../../../../Hooks/v1/checkHooks.js";
-import { useFetchChecksByMonitor } from "../../../../../Hooks/v1/checkHooks.js";
-import { useResolveIncident } from "../../../../../Hooks/v1/checkHooks.js";
-import { Button, Typography, useTheme } from "@mui/material";
+import { useFetchChecksTeam, useFetchChecksByMonitor } from "@/Hooks/checkHooks.js";
+import { Typography, useTheme } from "@mui/material";
import { lighten } from "@mui/material/styles";
const GetTooltip = (row) => {
@@ -91,7 +89,6 @@ const IncidentTable = ({
filter,
dateRange,
updateTrigger,
- setUpdateTrigger,
}) => {
//Redux state
const uiTimezone = useSelector((state) => state.ui.timezone);
@@ -103,8 +100,6 @@ const IncidentTable = ({
const selectedMonitorType = selectedMonitorDetails?.type;
//Hooks
- const [resolveIncident, resolveLoading] = useResolveIncident();
-
const [checksMonitor, checksCountMonitor, isLoadingMonitor, networkErrorMonitor] =
useFetchChecksByMonitor({
monitorId: selectedMonitor === "0" ? undefined : selectedMonitor,
@@ -151,15 +146,13 @@ const IncidentTable = ({
setRowsPerPage(event.target.value);
};
- const handleResolveIncident = (checkId) => {
- resolveIncident(checkId, setUpdateTrigger);
- };
-
const headers = [
{
id: "monitorName",
content: t("incidentsTableMonitorName"),
- render: (row) => monitors[row.monitorId]?.name ?? "N/A",
+ render: (row) => {
+ return monitors?.[row.metadata?.monitorId]?.name || "N/A";
+ },
},
{
id: "status",
@@ -193,31 +186,9 @@ const IncidentTable = ({
render: (row) => ,
},
{ id: "message", content: t("incidentsTableMessage"), render: (row) => row.message },
- {
- id: "action",
- content: t("actions"),
- render: (row) => {
- return row.ack === false ? (
- {
- handleResolveIncident(row._id);
- }}
- >
- {t("incidentsTableActionResolve")}
-
- ) : (
-
- {t("incidentsTableResolvedAt")}{" "}
- {formatDateWithTz(row.ackAt, "YYYY-MM-DD HH:mm:ss A", uiTimezone)}
-
- );
- },
- },
];
- if (isLoading || resolveLoading) return ;
+ if (isLoading) return ;
if (networkError) {
return (
diff --git a/client/src/Pages/v1/Incidents/Components/OptionsHeader/index.jsx b/client/src/Pages/Checks/Components/OptionsHeader/index.jsx
similarity index 92%
rename from client/src/Pages/v1/Incidents/Components/OptionsHeader/index.jsx
rename to client/src/Pages/Checks/Components/OptionsHeader/index.jsx
index c16f54f18..e04236a9b 100644
--- a/client/src/Pages/v1/Incidents/Components/OptionsHeader/index.jsx
+++ b/client/src/Pages/Checks/Components/OptionsHeader/index.jsx
@@ -22,10 +22,10 @@ const OptionsHeader = ({
const { t } = useTranslation();
const monitorNames = typeof monitors !== "undefined" ? Object.values(monitors) : [];
const filterOptions = [
- { _id: "all", name: t("incidentsOptionsHeaderFilterAll") },
- { _id: "down", name: t("incidentsOptionsHeaderFilterDown") },
- { _id: "resolve", name: t("incidentsOptionsHeaderFilterCannotResolve") },
- { _id: "resolved", name: t("incidentsOptionsHeaderFilterResolved") },
+ { id: "all", name: t("incidentsOptionsHeaderFilterAll") },
+ { id: "down", name: t("incidentsOptionsHeaderFilterDown") },
+ { id: "resolve", name: t("incidentsOptionsHeaderFilterCannotResolve") },
+ { id: "resolved", name: t("incidentsOptionsHeaderFilterResolved") },
];
// The stacks below which are three in number have the same style so
diff --git a/client/src/Pages/v1/Incidents/Components/OptionsHeader/skeleton.jsx b/client/src/Pages/Checks/Components/OptionsHeader/skeleton.jsx
similarity index 100%
rename from client/src/Pages/v1/Incidents/Components/OptionsHeader/skeleton.jsx
rename to client/src/Pages/Checks/Components/OptionsHeader/skeleton.jsx
diff --git a/client/src/Pages/v1/Incidents/Components/StatusBoxes/StatusBox.jsx b/client/src/Pages/Checks/Components/StatusBoxes/StatusBox.jsx
similarity index 84%
rename from client/src/Pages/v1/Incidents/Components/StatusBoxes/StatusBox.jsx
rename to client/src/Pages/Checks/Components/StatusBoxes/StatusBox.jsx
index cd7078e6c..d7f02ada3 100644
--- a/client/src/Pages/v1/Incidents/Components/StatusBoxes/StatusBox.jsx
+++ b/client/src/Pages/Checks/Components/StatusBoxes/StatusBox.jsx
@@ -1,11 +1,8 @@
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { Box, Stack, Typography } from "@mui/material";
-import Background from "../../../../../assets/Images/background-grid.svg?react";
-import AlertIcon from "../../../../../assets/icons/alert-icon.svg?react";
-import CheckIcon from "../../../../../assets/icons/check-icon.svg?react";
-import CloseIcon from "../../../../../assets/icons/close-icon.svg?react";
-import WarningIcon from "../../../../../assets/icons/warning-icon.svg?react";
+import Background from "@/assets/Images/background-grid.svg?react";
+import Icon from "@/Components/v1/Icon";
const StatusBox = ({ title, value, status }) => {
const theme = useTheme();
@@ -26,28 +23,40 @@ const StatusBox = ({ title, value, status }) => {
color = theme.palette.success.lowContrast;
icon = (
-
+
);
} else if (status === "down") {
color = theme.palette.error.lowContrast;
icon = (
-
+
);
} else if (status === "paused") {
color = theme.palette.warning.lowContrast;
icon = (
-
+
);
} else {
color = theme.palette.accent.main;
icon = (
-
+
);
}
diff --git a/client/src/Pages/v1/Incidents/Components/StatusBoxes/index.jsx b/client/src/Pages/Checks/Components/StatusBoxes/index.jsx
similarity index 100%
rename from client/src/Pages/v1/Incidents/Components/StatusBoxes/index.jsx
rename to client/src/Pages/Checks/Components/StatusBoxes/index.jsx
diff --git a/client/src/Pages/v1/Incidents/Components/StatusBoxes/skeleton.jsx b/client/src/Pages/Checks/Components/StatusBoxes/skeleton.jsx
similarity index 100%
rename from client/src/Pages/v1/Incidents/Components/StatusBoxes/skeleton.jsx
rename to client/src/Pages/Checks/Components/StatusBoxes/skeleton.jsx
diff --git a/client/src/Pages/v1/Incidents/index.jsx b/client/src/Pages/Checks/index.jsx
similarity index 69%
rename from client/src/Pages/v1/Incidents/index.jsx
rename to client/src/Pages/Checks/index.jsx
index 3c0d7f60f..bcd036a58 100644
--- a/client/src/Pages/v1/Incidents/index.jsx
+++ b/client/src/Pages/Checks/index.jsx
@@ -5,41 +5,37 @@ import GenericFallback from "@/Components/v1/GenericFallback/index.jsx";
import IncidentTable from "./Components/IncidentTable/index.jsx";
import OptionsHeader from "./Components/OptionsHeader/index.jsx";
import StatusBoxes from "./Components/StatusBoxes/index.jsx";
-import { Box, Button } from "@mui/material";
//Utils
import { useTheme } from "@emotion/react";
-import { useFetchMonitorsByTeamId } from "../../../Hooks/v1/monitorHooks.js";
-import { useFetchChecksSummaryByTeamId } from "../../../Hooks/v1/checkHooks.js";
-import { useAcknowledgeChecks } from "../../../Hooks/v1/checkHooks.js";
+import { useFetchMonitorsByTeamId } from "@/Hooks/monitorHooks.js";
+import { useFetchChecksSummaryByTeamId } from "@/Hooks/checkHooks.js";
import { useState, useEffect } from "react";
import NetworkError from "@/Components/v1/GenericFallback/NetworkError.jsx";
import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom";
//Constants
-const Incidents = () => {
+const Checks = () => {
// Redux state
const { t } = useTranslation();
- const BREADCRUMBS = [
- { name: t("incidentsPageTitle", "Incidents"), path: "/incidents" },
- ];
+ const BREADCRUMBS = [{ name: t("checksPageTitle"), path: "/checks" }];
// Local state
const [selectedMonitor, setSelectedMonitor] = useState("0");
const [filter, setFilter] = useState(undefined);
- const [dateRange, setDateRange] = useState(undefined);
+ const [dateRange, setDateRange] = useState("hour");
const [monitorLookup, setMonitorLookup] = useState(undefined);
const [updateTrigger, setUpdateTrigger] = useState(false);
//Hooks
- const { acknowledge, isLoadingAcknowledge } = useAcknowledgeChecks();
//Utils
const theme = useTheme();
const [monitors, , isLoading, networkError] = useFetchMonitorsByTeamId({});
const [summary, isLoadingSummary, networkErrorSummary] = useFetchChecksSummaryByTeamId({
+ dateRange,
updateTrigger,
});
const { monitorId } = useParams();
@@ -52,8 +48,8 @@ const Incidents = () => {
useEffect(() => {
const monitorLookup = monitors?.reduce((acc, monitor) => {
- acc[monitor._id] = {
- _id: monitor._id,
+ acc[monitor.id] = {
+ id: monitor.id,
name: monitor.name,
type: monitor.type,
};
@@ -62,11 +58,6 @@ const Incidents = () => {
setMonitorLookup(monitorLookup);
}, [monitors]);
- const handleAcknowledge = () => {
- const monitorId = selectedMonitor === "0" ? null : selectedMonitor;
- acknowledge(setUpdateTrigger, monitorId);
- };
-
if (networkError || networkErrorSummary) {
return (
@@ -78,18 +69,7 @@ const Incidents = () => {
return (
-
-
- {selectedMonitor === "0"
- ? t("incidentsPageActionResolveAll")
- : t("incidentsPageActionResolveMonitor")}
-
-
+
{
);
};
-export default Incidents;
+export default Checks;
diff --git a/client/src/Pages/Incidents/Components/ActiveIncidentsPanel/index.jsx b/client/src/Pages/Incidents/Components/ActiveIncidentsPanel/index.jsx
new file mode 100644
index 000000000..fda257695
--- /dev/null
+++ b/client/src/Pages/Incidents/Components/ActiveIncidentsPanel/index.jsx
@@ -0,0 +1,134 @@
+import PropTypes from "prop-types";
+import { Box, Stack, Typography } from "@mui/material";
+import { useTheme } from "@emotion/react";
+import { useTranslation } from "react-i18next";
+import PanelSkeleton from "../IncidentsSummaryPanel/skeleton.jsx";
+import Icon from "@/Components/v1/Icon";
+import SummaryCard from "../SummaryCard/index.jsx";
+
+/**
+ * ActiveIncidentsPanel Component
+ *
+ * Displays a quick overview of currently active incidents.
+ * Shows only essential information: monitor name, status badge, and duration.
+ *
+ * @param {Object} props
+ * @param {Array} props.incidents - Array of active incidents
+ * @param {number} props.totalCount - Total count of active incidents (for badge)
+ * @param {boolean} props.isLoading - Loading state
+ * @param {Object} props.error - Error object if any
+ * @param {Function} props.onIncidentClick - Callback when incident is clicked (optional)
+ */
+const ActiveIncidentsPanel = ({ totalCount = 0, isLoading = false, error = null }) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+
+ if (isLoading) {
+ return ;
+ }
+
+ if (error) {
+ return (
+
+
+ {t("incidentsPage.incidentsActivePanelError")}
+
+
+ );
+ }
+
+ if (!totalCount || totalCount === 0) {
+ return (
+
+
+
+
+
+
+
+ {t("incidentsPage.allSystemsAreOperational")}
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+ {totalCount}
+
+
+
+ );
+};
+
+ActiveIncidentsPanel.propTypes = {
+ totalCount: PropTypes.number,
+ isLoading: PropTypes.bool,
+ error: PropTypes.object,
+ onIncidentClick: PropTypes.func,
+};
+
+export default ActiveIncidentsPanel;
diff --git a/client/src/Pages/Incidents/Components/IncidentDetailsModal/index.jsx b/client/src/Pages/Incidents/Components/IncidentDetailsModal/index.jsx
new file mode 100644
index 000000000..34efd632e
--- /dev/null
+++ b/client/src/Pages/Incidents/Components/IncidentDetailsModal/index.jsx
@@ -0,0 +1,413 @@
+// Components
+import { Stack, Typography, Grid, Box, Button, Divider } from "@mui/material";
+import { GenericDialog } from "@/Components/v1/Dialog/genericDialog.jsx";
+import GenericFallback from "@/Components/v1/GenericFallback/index.jsx";
+import SummaryCard from "../SummaryCard/index.jsx";
+import ResolveIncidentDialog from "../ResolveIncidentDialog/index.jsx";
+
+// Utils
+import { useEffect, useState } from "react";
+import { useTheme } from "@emotion/react";
+import { useTranslation } from "react-i18next";
+import { formatDateWithTz } from "@/Utils/timeUtilsLegacy.js";
+import { useSelector } from "react-redux";
+import PropTypes from "prop-types";
+
+// Hooks
+import useFetchIncidents from "../../hooks/useFetchIncidents.js";
+import useGetIncidentsDuration from "../../hooks/useGetIncidentsDuration.js";
+
+const IncidentDetailsModal = ({ open, incidentId, onClose, onResolved }) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+ const { fetchIncidentById, resolveIncident } = useFetchIncidents();
+ const [incidentData, setIncidentData] = useState(null);
+ const [isLoading, setIsLoading] = useState(false);
+ const [isResolveDialogOpen, setIsResolveDialogOpen] = useState(false);
+ const uiTimezone = useSelector((state) => state.ui.timezone);
+ const isActive = incidentData?.incident?.status === true;
+ const duration = useGetIncidentsDuration(incidentData?.incident || null, isActive);
+
+ const statusColor = isActive ? theme.palette.error.main : theme.palette.success.main;
+ const toCapitalLetter = (text) =>
+ typeof text === "string" && text.length > 0
+ ? text.charAt(0).toUpperCase() + text.slice(1)
+ : text;
+
+ const KeyValueRow = ({ label, value, sx = {} }) => (
+
+
+
+ {label}
+
+
+
+
+ {value ?? "-"}
+
+
+
+ );
+
+ useEffect(() => {
+ const loadIncident = async () => {
+ if (!incidentId || !open) {
+ setIncidentData(null);
+ setIsLoading(false);
+ return;
+ }
+
+ setIsLoading(true);
+ try {
+ const incidentData = await fetchIncidentById(incidentId);
+ setIncidentData(incidentData);
+ } catch (error) {
+ console.error("Error fetching incident:", error);
+ setIncidentData(null);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ loadIncident();
+ }, [incidentId, open, fetchIncidentById]);
+
+ const openResolveDialog = () => {
+ setIsResolveDialogOpen(true);
+ };
+
+ const closeResolveDialog = () => {
+ setIsResolveDialogOpen(false);
+ };
+
+ const handleAfterResolve = async () => {
+ if (!incidentId) return;
+ const updatedIncident = await fetchIncidentById(incidentId);
+ if (updatedIncident) {
+ setIncidentData(updatedIncident);
+ }
+ };
+
+ const renderContent = () => {
+ if (isLoading) {
+ return (
+
+ );
+ }
+
+ if (!incidentData) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+ {isActive ? t("incidentsPage.active") : t("incidentsPage.resolved")}
+
+
+
+
+
+ {t("incidentsPage.incidentItemMonitor")}
+
+
+ {incidentData?.monitor?.name || t("incidentsPage.unknownMonitor")}
+
+
+
+
+ {t("incidentsPage.URL")}
+
+
+ {incidentData?.monitor?.url || "-"}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {t("incidentsPage.timeline")}
+
+
+
+
+ {!isActive && (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ {isActive
+ ? t("incidentsPage.errorDetails")
+ : t("incidentsPage.resolutionDetails")}
+
+
+
+
+
+
+
+
+ {!isActive && (
+ <>
+
+ {incidentData?.incident?.resolutionType === "manual" && (
+
+ )}
+ >
+ )}
+
+
+
+
+
+
+
+ );
+ };
+
+ return (
+
+
+ {renderContent()}
+ {!isActive &&
+ incidentData?.incident?.resolutionType === "manual" &&
+ incidentData?.user?.email && (
+
+ {t("incidentsPage.resolvedBy")}: {incidentData.user.email}
+
+ )}
+
+ {isActive && (
+
+ {t("incidentsPage.incidentsTableActionResolveManually")}
+
+ )}
+
+
+ {t("close")}
+
+
+
+
+
+ );
+};
+
+IncidentDetailsModal.propTypes = {
+ open: PropTypes.bool.isRequired,
+ incidentId: PropTypes.string,
+ onClose: PropTypes.func.isRequired,
+ onResolved: PropTypes.func,
+};
+
+export default IncidentDetailsModal;
diff --git a/client/src/Pages/Incidents/Components/IncidentTable/IncidentActionsMenu.jsx b/client/src/Pages/Incidents/Components/IncidentTable/IncidentActionsMenu.jsx
new file mode 100644
index 000000000..d125a9fdb
--- /dev/null
+++ b/client/src/Pages/Incidents/Components/IncidentTable/IncidentActionsMenu.jsx
@@ -0,0 +1,156 @@
+// Components
+import IconButton from "@mui/material/IconButton";
+import Menu from "@mui/material/Menu";
+import MenuItem from "@mui/material/MenuItem";
+import Icon from "@/Components/v1/Icon/index.jsx";
+import ResolveIncidentDialog from "../ResolveIncidentDialog/index.jsx";
+
+// Utils
+import { useState } from "react";
+import { useTheme } from "@emotion/react";
+import { useTranslation } from "react-i18next";
+import { TypeToPathMap } from "@/Utils/monitorUtilsLegacy.js";
+import { useNavigate } from "react-router-dom";
+import PropTypes from "prop-types";
+
+const IncidentActionsMenu = ({ incident, monitor, onResolve, onOpenDetails }) => {
+ const [anchorEl, setAnchorEl] = useState(null);
+ const [isDialogOpen, setIsDialogOpen] = useState(false);
+ const theme = useTheme();
+ const { t } = useTranslation();
+ const navigate = useNavigate();
+ const openMenu = (event) => {
+ event.preventDefault();
+ event.stopPropagation();
+ setAnchorEl(event.currentTarget);
+ };
+
+ const closeMenu = (e) => {
+ if (e) {
+ e.stopPropagation();
+ }
+ setAnchorEl(null);
+ };
+
+ const openResolveDialog = (e) => {
+ closeMenu(e);
+ setIsDialogOpen(true);
+ };
+
+ const closeDialog = (e) => {
+ if (e) {
+ e.stopPropagation();
+ }
+ setIsDialogOpen(false);
+ };
+
+ const isActive = incident.status === true;
+ const handleGoToDetails = (e) => {
+ e.stopPropagation();
+ closeMenu(e);
+ if (onOpenDetails) {
+ onOpenDetails(incident.id);
+ }
+ };
+
+ const handleGoToMonitor = async (e) => {
+ e.stopPropagation();
+ closeMenu(e);
+ try {
+ if (monitor) {
+ const path = TypeToPathMap[monitor.type];
+ if (path && monitor.id) {
+ navigate(`/${path}/${monitor.id}`);
+ } else {
+ console.error(`Monitor type not found`);
+ }
+ } else {
+ console.error("Monitor information not found in incident data");
+ }
+ } catch (error) {
+ console.error("Error fetching incident details:", error);
+ }
+ };
+
+ return (
+ <>
+ {
+ event.stopPropagation();
+ openMenu(event);
+ }}
+ sx={{
+ "&:focus": {
+ outline: "none",
+ },
+ "& svg path": {
+ stroke: theme.palette.primary.contrastTextTertiary,
+ },
+ }}
+ >
+
+
+
+
+
+ {t("incidentsPage.incidentsTableActionDetails", "Details")}
+
+
+ {t("incidentsPage.incidentsTableActionGoToMonitor", "Go to monitor")}
+
+ {isActive && (
+ {
+ e.stopPropagation();
+ openResolveDialog(e);
+ }}
+ >
+ {t("incidentsPage.incidentsTableActionResolveManually")}
+
+ )}
+
+
+ >
+ );
+};
+
+IncidentActionsMenu.propTypes = {
+ incident: PropTypes.shape({
+ id: PropTypes.string.isRequired,
+ status: PropTypes.bool.isRequired,
+ }).isRequired,
+ monitor: PropTypes.shape({
+ id: PropTypes.string.isRequired,
+ type: PropTypes.string.isRequired,
+ }).isRequired,
+ onResolve: PropTypes.func.isRequired,
+ onOpenDetails: PropTypes.func,
+};
+
+export default IncidentActionsMenu;
diff --git a/client/src/Pages/Incidents/Components/IncidentTable/index.jsx b/client/src/Pages/Incidents/Components/IncidentTable/index.jsx
new file mode 100644
index 000000000..2a03a8822
--- /dev/null
+++ b/client/src/Pages/Incidents/Components/IncidentTable/index.jsx
@@ -0,0 +1,193 @@
+//Components
+import DataTable from "@/Components/v1/Table/index.jsx";
+import TableSkeleton from "@/Components/v1/Table/skeleton.jsx";
+import Pagination from "@/Components/v1/Table/TablePagination/index.jsx";
+import { StatusLabel } from "@/Components/v1/Label/index.jsx";
+import { HttpStatusLabel } from "@/Components/v1/HttpStatusLabel/index.jsx";
+import GenericFallback from "@/Components/v1/GenericFallback/index.jsx";
+import NetworkError from "@/Components/v1/GenericFallback/NetworkError.jsx";
+import IncidentActionsMenu from "./IncidentActionsMenu.jsx";
+
+//Utils
+import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js";
+import { useSelector } from "react-redux";
+import PropTypes from "prop-types";
+import { useTranslation } from "react-i18next";
+import { Typography, useTheme } from "@mui/material";
+
+const IncidentTable = ({
+ monitors = [],
+ incidents = [],
+ incidentsCount = 0,
+ isLoading = false,
+ networkError = false,
+ page = 0,
+ rowsPerPage = 10,
+ handleChangePage,
+ handleChangeRowsPerPage,
+ resolveIncident,
+ handleUpdateTrigger,
+ onOpenDetails,
+}) => {
+ const uiTimezone = useSelector((state) => state.ui.timezone);
+
+ const { t } = useTranslation();
+ const theme = useTheme();
+
+ const handleResolveIncident = async (incidentId, options = {}) => {
+ try {
+ await resolveIncident(incidentId, options);
+ handleUpdateTrigger();
+ } catch (error) {
+ console.error(t("incidentsPage.errorResolvingIncident"), error);
+ }
+ };
+
+ const headers = [
+ {
+ id: "monitorName",
+ content: t("incidentsTableMonitorName"),
+ render: (row) => {
+ const monitor = monitors.find((monitor) => monitor.id === row.monitorId);
+ return monitor ? monitor.name : "N/A";
+ },
+ },
+ {
+ id: "status",
+ content: t("incidentsTableStatus"),
+ render: (row) => {
+ const status = row.status === true ? "down" : "up";
+ const statusText =
+ row.status === true ? t("incidentsPage.active") : t("incidentsPage.resolved");
+ return (
+
+ );
+ },
+ },
+ {
+ id: "startTime",
+ content: t("incidentsPage.startTime"),
+ render: (row) => {
+ const formattedDate = formatDateWithTz(
+ row.startTime || row.createdAt,
+ "YYYY-MM-DD HH:mm:ss A",
+ uiTimezone
+ );
+ return formattedDate;
+ },
+ },
+ {
+ id: "endTime",
+ content: t("incidentsPage.endTime"),
+ render: (row) => {
+ if (row.endTime) {
+ return formatDateWithTz(row.endTime, "YYYY-MM-DD HH:mm:ss A", uiTimezone);
+ }
+ return "-";
+ },
+ },
+ {
+ id: "resolutionType",
+ content: t("incidentsPage.resolutionType"),
+ render: (row) => {
+ if (row.resolutionType) {
+ return (
+
+ {row.resolutionType}
+
+ );
+ }
+ return "-";
+ },
+ },
+ {
+ id: "statusCode",
+ content: t("incidentsTableStatusCode"),
+ render: (row) => ,
+ },
+ {
+ id: "message",
+ content: t("incidentsTableMessage"),
+ render: (row) => row.message || "-",
+ },
+ {
+ id: "action",
+ content: t("actions"),
+ render: (row) => {
+ return (
+ monitor.id === row.monitorId)}
+ onResolve={handleResolveIncident}
+ onOpenDetails={onOpenDetails}
+ />
+ );
+ },
+ },
+ ];
+
+ if (isLoading) return ;
+
+ if (networkError) {
+ return (
+
+
+
+ );
+ }
+
+ if (!isLoading && !networkError && incidents?.length === 0) {
+ return (
+
+ {t("incidentsTableNoIncidents", "No incidents found")}
+
+ );
+ }
+
+ const incidentsData = Array.isArray(incidents) ? incidents : [];
+
+ return (
+ <>
+
+
+ >
+ );
+};
+
+IncidentTable.propTypes = {
+ incidents: PropTypes.array.isRequired, // Array of incident objects
+ incidentsCount: PropTypes.number.isRequired, // Total count for pagination
+ isLoading: PropTypes.bool.isRequired, // Loading state
+ networkError: PropTypes.bool, // Network error object
+ page: PropTypes.number.isRequired, // Current page number
+ rowsPerPage: PropTypes.number.isRequired, // Number of rows per page
+ handleChangePage: PropTypes.func.isRequired, // Handler for page change
+ handleChangeRowsPerPage: PropTypes.func.isRequired, // Handler for rows per page change
+ resolveIncident: PropTypes.func.isRequired,
+ handleUpdateTrigger: PropTypes.func.isRequired,
+ onOpenDetails: PropTypes.func,
+};
+
+export default IncidentTable;
diff --git a/client/src/Pages/Incidents/Components/IncidentsSummaryPanel/index.jsx b/client/src/Pages/Incidents/Components/IncidentsSummaryPanel/index.jsx
new file mode 100644
index 000000000..33ba21cd1
--- /dev/null
+++ b/client/src/Pages/Incidents/Components/IncidentsSummaryPanel/index.jsx
@@ -0,0 +1,100 @@
+import { useEffect, useState } from "react";
+// Hooks
+import useFetchIncidents from "../../hooks/useFetchIncidents.js";
+import PropTypes from "prop-types";
+// Components
+import { Grid } from "@mui/material";
+import ActiveIncidentsPanel from "../ActiveIncidentsPanel/index.jsx";
+import LatestIncidentsPanel from "../LatestIncidentsPanel/index.jsx";
+import StatisticsPanel from "../StatisticsPanel/index.jsx";
+
+const IncidentsSummaryPanel = ({ updateTrigger }) => {
+ const { fetchIncidentSummary } = useFetchIncidents();
+ const [summary, setSummary] = useState({
+ totalActive: 0,
+ avgResolutionTimeHours: 0,
+ total: 0,
+ topMonitor: null,
+ totalManualResolutions: 0,
+ totalAutomaticResolutions: 0,
+ latestIncidents: [],
+ });
+
+ const [isLoadingSummary, setIsLoadingSummary] = useState(false);
+ const [summaryError, setSummaryError] = useState(null);
+
+ useEffect(() => {
+ const fetchSummary = async () => {
+ setIsLoadingSummary(true);
+ setSummaryError(null);
+
+ try {
+ const summaryData = await fetchIncidentSummary({
+ limit: 3,
+ });
+
+ if (summaryData) {
+ setSummary(summaryData);
+ } else {
+ setSummaryError(new Error("Failed to fetch summary"));
+ }
+ } catch (error) {
+ console.error("Error fetching incident summary:", error);
+ setSummaryError(error);
+ } finally {
+ setIsLoadingSummary(false);
+ }
+ };
+
+ fetchSummary();
+ }, [fetchIncidentSummary, updateTrigger]);
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+IncidentsSummaryPanel.propTypes = {
+ updateTrigger: PropTypes.bool.isRequired,
+};
+export default IncidentsSummaryPanel;
diff --git a/client/src/Pages/Incidents/Components/IncidentsSummaryPanel/skeleton.jsx b/client/src/Pages/Incidents/Components/IncidentsSummaryPanel/skeleton.jsx
new file mode 100644
index 000000000..153cd7c25
--- /dev/null
+++ b/client/src/Pages/Incidents/Components/IncidentsSummaryPanel/skeleton.jsx
@@ -0,0 +1,20 @@
+import { Skeleton } from "@mui/material";
+import SummaryCard from "../SummaryCard/index.jsx";
+
+/**
+ * Simple skeleton loader for summary panels
+ * Reusable for ActiveIncidentsPanel, LatestIncidentsPanel, and StatisticsPanel
+ */
+const PanelSkeleton = () => {
+ return (
+
+
+
+ );
+};
+
+export default PanelSkeleton;
diff --git a/client/src/Pages/Incidents/Components/LatestIncidentsPanel/IncidentItem.jsx b/client/src/Pages/Incidents/Components/LatestIncidentsPanel/IncidentItem.jsx
new file mode 100644
index 000000000..ca5bb640f
--- /dev/null
+++ b/client/src/Pages/Incidents/Components/LatestIncidentsPanel/IncidentItem.jsx
@@ -0,0 +1,96 @@
+import PropTypes from "prop-types";
+import { Box, Stack, Typography } from "@mui/material";
+import { useTheme } from "@emotion/react";
+import { StatusLabel } from "@/Components/v1/Label/index.jsx";
+import { getHumanReadableDuration } from "@/Utils/timeUtilsLegacy.js";
+import Icon from "@/Components/v1/Icon";
+import { useTranslation } from "react-i18next";
+import useGetIncidentsDuration from "../../hooks/useGetIncidentsDuration";
+
+const IncidentItem = ({ incident }) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const isActive = incident.status === true;
+ const duration = useGetIncidentsDuration(incident, isActive);
+ const iconWrapperStyle = {
+ display: "flex",
+ justifyContent: "center",
+ mx: theme.spacing(2),
+ color: theme.palette.primary.contrastTextTertiary,
+ "& svg": {
+ width: 18,
+ height: 18,
+ },
+ "& svg path": {
+ stroke: "currentColor",
+ strokeWidth: 1.5,
+ },
+ };
+
+ return (
+
+
+
+
+
+
+ {incident.monitorName || t("incidentsPage.unknownMonitor")}
+
+
+
+
+
+
+
+
+ {duration}
+
+
+ );
+};
+
+IncidentItem.propTypes = {
+ incident: PropTypes.shape({
+ _id: PropTypes.string,
+ id: PropTypes.string,
+ monitorId: PropTypes.string,
+ monitorName: PropTypes.string,
+ status: PropTypes.bool,
+ startTime: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]),
+ endTime: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]),
+ resolutionType: PropTypes.oneOf(["automatic", "manual"]),
+ }),
+ onClick: PropTypes.func,
+};
+
+export default IncidentItem;
diff --git a/client/src/Pages/Incidents/Components/LatestIncidentsPanel/index.jsx b/client/src/Pages/Incidents/Components/LatestIncidentsPanel/index.jsx
new file mode 100644
index 000000000..ff1ef9417
--- /dev/null
+++ b/client/src/Pages/Incidents/Components/LatestIncidentsPanel/index.jsx
@@ -0,0 +1,114 @@
+import PropTypes from "prop-types";
+import { Box, Stack, Typography, Divider } from "@mui/material";
+import { useTheme } from "@emotion/react";
+import { useTranslation } from "react-i18next";
+import PanelSkeleton from "../IncidentsSummaryPanel/skeleton.jsx";
+import IncidentItem from "./IncidentItem.jsx";
+import SummaryCard from "../SummaryCard/index.jsx";
+import Icon from "@/Components/v1/Icon";
+
+/**
+ * LatestIncidentsPanel Component
+ *
+ * Displays a quick overview of recent incidents (both active and resolved).
+ * Shows only essential information: monitor name, status, duration, and resolution type.
+ *
+ * @param {Object} props
+ * @param {Array} props.incidents - Array of recent incidents
+ * @param {boolean} props.isLoading - Loading state
+ * @param {Object} props.error - Error object if any
+ */
+const LatestIncidentsPanel = ({ incidents = [], isLoading = false, error = null }) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+
+ if (isLoading) {
+ return ;
+ }
+
+ if (error) {
+ return (
+
+
+
+ {t("incidentsPage.incidentsLatestPanelError")}
+
+
+
+ );
+ }
+
+ return (
+
+ {!incidents || incidents.length === 0 ? (
+
+
+
+
+
+
+ {t("incidentsPage.incidentsLatestPanelEmpty")}
+
+
+ ) : (
+
+ {incidents.map((incident, index) => (
+
+
+ {index < incidents.length - 1 && }
+
+ ))}
+
+ )}
+
+ );
+};
+
+LatestIncidentsPanel.propTypes = {
+ incidents: PropTypes.arrayOf(
+ PropTypes.shape({
+ _id: PropTypes.string,
+ monitorId: PropTypes.string,
+ monitorName: PropTypes.string,
+ status: PropTypes.bool,
+ duration: PropTypes.string,
+ resolutionType: PropTypes.oneOf(["automatic", "manual"]),
+ })
+ ),
+ isLoading: PropTypes.bool,
+ error: PropTypes.object,
+ onIncidentClick: PropTypes.func,
+};
+
+export default LatestIncidentsPanel;
diff --git a/client/src/Pages/Incidents/Components/OptionsHeader/index.jsx b/client/src/Pages/Incidents/Components/OptionsHeader/index.jsx
new file mode 100644
index 000000000..8d7439576
--- /dev/null
+++ b/client/src/Pages/Incidents/Components/OptionsHeader/index.jsx
@@ -0,0 +1,139 @@
+// Components
+import { Stack, Typography, Button, ButtonGroup } from "@mui/material";
+import Select from "@/Components/v1/Inputs/Select/index.jsx";
+import PropTypes from "prop-types";
+
+//Utils
+import { useTheme } from "@emotion/react";
+import { useTranslation } from "react-i18next";
+
+const OptionsHeader = ({
+ shouldRender,
+ selectedMonitor = 0,
+ setSelectedMonitor,
+ monitors,
+ filter = "all",
+ setFilter,
+ dateRange = "all",
+ setDateRange,
+}) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+ const monitorNames = typeof monitors !== "undefined" ? Object.values(monitors) : [];
+
+ const filterOptions = [
+ { _id: "all", name: t("incidentsPage.incidentsOptionsHeaderFilterAll") },
+ { _id: "active", name: t("incidentsPage.incidentsOptionsHeaderFilterActive") },
+ { _id: "resolved", name: t("incidentsPage.incidentsOptionsHeaderFilterResolved") },
+ { _id: "manual", name: t("incidentsPage.incidentsOptionsHeaderFilterManual") },
+ { _id: "automatic", name: t("incidentsPage.incidentsOptionsHeaderFilterAutomatic") },
+ ];
+
+ const stackStyles = {
+ direction: "row",
+ alignItems: "center",
+ gap: theme.spacing(6),
+ };
+
+ if (!shouldRender) return;
+
+ return (
+
+
+
+ {t("incidentsOptionsHeader")}
+
+ setSelectedMonitor(e.target.value)}
+ items={monitorNames}
+ sx={{
+ backgroundColor: theme.palette.primary.main,
+ color: theme.palette.primary.contrastTextSecondary,
+ }}
+ maxWidth={250}
+ />
+
+
+
+ {t("incidentsOptionsHeaderFilterBy")}
+
+ setFilter(e.target.value)}
+ items={filterOptions}
+ sx={{
+ backgroundColor: theme.palette.primary.main,
+ color: theme.palette.primary.contrastTextSecondary,
+ }}
+ />
+
+
+
+ {t("incidentsOptionsHeaderShow")}
+
+
+ setDateRange("hour")}
+ >
+ {t("incidentsOptionsHeaderLastHour")}
+
+ setDateRange("day")}
+ >
+ {t("incidentsOptionsHeaderLastDay")}
+
+ setDateRange("week")}
+ >
+ {t("incidentsOptionsHeaderLastWeek")}
+
+ setDateRange("all")}
+ >
+ {t("incidentsOptionsHeaderFilterAll")}
+
+
+
+
+ );
+};
+
+OptionsHeader.propTypes = {
+ shouldRender: PropTypes.bool,
+ selectedMonitor: PropTypes.string,
+ setSelectedMonitor: PropTypes.func,
+ monitors: PropTypes.object,
+ filter: PropTypes.string,
+ setFilter: PropTypes.func,
+ dateRange: PropTypes.string,
+ setDateRange: PropTypes.func,
+};
+
+export default OptionsHeader;
diff --git a/client/src/Pages/Incidents/Components/ResolveIncidentDialog/index.jsx b/client/src/Pages/Incidents/Components/ResolveIncidentDialog/index.jsx
new file mode 100644
index 000000000..3e868b2dc
--- /dev/null
+++ b/client/src/Pages/Incidents/Components/ResolveIncidentDialog/index.jsx
@@ -0,0 +1,101 @@
+import { useEffect, useState } from "react";
+import PropTypes from "prop-types";
+import { Button, Stack } from "@mui/material";
+import { useTranslation } from "react-i18next";
+import { useTheme } from "@emotion/react";
+import { GenericDialog } from "@/Components/v1/Dialog/genericDialog.jsx";
+import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
+
+const ResolveIncidentDialog = ({
+ open,
+ incidentId,
+ onClose,
+ onResolve,
+ onAfterResolve,
+ onResolved,
+ isLoading = false,
+}) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const [comment, setComment] = useState("");
+ const [isSubmitting, setIsSubmitting] = useState(false);
+
+ useEffect(() => {
+ if (open) {
+ setComment("");
+ }
+ }, [open]);
+
+ const handleConfirm = async () => {
+ if (!incidentId) return;
+ const trimmed = comment.trim();
+ try {
+ setIsSubmitting(true);
+ await onResolve(incidentId, trimmed ? { comment: trimmed } : {});
+ if (onAfterResolve) {
+ await onAfterResolve();
+ }
+ onResolved?.();
+ onClose();
+ } catch (error) {
+ console.error("Error resolving incident:", error);
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ return (
+
+
+ setComment(e.target.value)}
+ maxWidth="100%"
+ />
+
+
+ {t("cancel", "Cancel")}
+
+
+ {t("incidentsPage.resolveIncidentDialogConfirm")}
+
+
+
+
+ );
+};
+
+ResolveIncidentDialog.propTypes = {
+ open: PropTypes.bool.isRequired,
+ incidentId: PropTypes.string,
+ onClose: PropTypes.func.isRequired,
+ onResolve: PropTypes.func.isRequired,
+ onAfterResolve: PropTypes.func,
+ isLoading: PropTypes.bool,
+ onResolved: PropTypes.func,
+};
+
+export default ResolveIncidentDialog;
diff --git a/client/src/Pages/Incidents/Components/StatisticsPanel/index.jsx b/client/src/Pages/Incidents/Components/StatisticsPanel/index.jsx
new file mode 100644
index 000000000..7f07f688f
--- /dev/null
+++ b/client/src/Pages/Incidents/Components/StatisticsPanel/index.jsx
@@ -0,0 +1,168 @@
+import PropTypes from "prop-types";
+import { Box, Stack, Typography, Divider } from "@mui/material";
+import { useTheme } from "@emotion/react";
+import PanelSkeleton from "../IncidentsSummaryPanel/skeleton.jsx";
+import { useTranslation } from "react-i18next";
+import Icon from "@/Components/v1/Icon";
+import SummaryCard from "../SummaryCard/index.jsx";
+
+/**
+ * StatisticsPanel Component
+ *
+ * Displays key metrics and statistics about incidents.
+ *
+ * @param {Object} props
+ * @param {Object} props.statistics - Statistics data object
+ * @param {number} props.statistics.total - Total incidents count
+ * @param {number} props.statistics.totalTrend - Trend percentage for total (optional)
+ * @param {string} props.statistics.mttr - Mean Time To Resolve (formatted string, e.g., "2h 15m")
+ * @param {number} props.statistics.mttrTrend - Trend percentage for MTTR (optional)
+ * @param {Object} props.statistics.topMonitor - Top monitor with most incidents
+ * @param {string} props.statistics.topMonitor.name - Monitor name
+ * @param {number} props.statistics.topMonitor.count - Incident count
+ * @param {number} props.statistics.topMonitor.percentage - Percentage of total
+ * @param {string} props.statistics.uptimeAffected - Total downtime (formatted string, e.g., "12h 45m")
+ * @param {number} props.statistics.availability - Availability percentage
+ * @param {boolean} props.isLoading - Loading state
+ * @param {Object} props.error - Error object if any
+ */
+const StatisticsPanel = ({ isLoading = false, error = null, summary = {} }) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+ const totalResolutions =
+ (summary?.totalManualResolutions || 0) + (summary?.totalAutomaticResolutions || 0);
+
+ const iconWrapperStyle = {
+ display: "flex",
+ justifyContent: "center",
+ mx: theme.spacing(2),
+ color: theme.palette.primary.contrastTextTertiary,
+ "& svg": {
+ width: 18,
+ height: 18,
+ },
+ "& svg path": {
+ stroke: "currentColor",
+ strokeWidth: 1.5,
+ },
+ };
+ if (isLoading) {
+ return ;
+ }
+
+ if (error) {
+ return (
+
+
+
+ {t("incidentsPage.incidentsStatisticsPanelError")}
+
+
+
+ );
+ }
+
+ const getMostAffectedMonitor = () => {
+ if (!summary.total || summary.total === 0) {
+ return t("incidentsPage.none");
+ }
+ return summary.topMonitor?.monitorName || t("incidentsPage.none");
+ };
+
+ const rowStyle = {
+ py: theme.spacing(0.5),
+ minHeight: 32,
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ {t("incidentsPage.totalIncidents")}: {summary.total || 0}
+
+
+
+
+
+
+
+
+
+
+ {t("incidentsPage.mostAffectedMonitor")}: {getMostAffectedMonitor()}
+
+
+
+
+
+
+
+
+
+ {t("incidentsPage.avgResolutionTime")}:{" "}
+ {summary.total > 0
+ ? `${summary.avgResolutionTimeHours || 0} ${t("incidentsPage.hours")}`
+ : "N/A"}
+
+
+
+
+ );
+};
+
+StatisticsPanel.propTypes = {
+ summary: PropTypes.object,
+ isLoading: PropTypes.bool,
+ error: PropTypes.object,
+};
+
+export default StatisticsPanel;
diff --git a/client/src/Pages/Incidents/Components/SummaryCard/index.jsx b/client/src/Pages/Incidents/Components/SummaryCard/index.jsx
new file mode 100644
index 000000000..3a8589dc1
--- /dev/null
+++ b/client/src/Pages/Incidents/Components/SummaryCard/index.jsx
@@ -0,0 +1,64 @@
+import { Paper } from "@mui/material";
+import { useTheme } from "@mui/material/styles";
+import PropTypes from "prop-types";
+import { Box, Stack, Typography, Divider } from "@mui/material";
+const SummaryCard = ({ children, isHighPriority = false, sx = {}, title = null }) => {
+ const theme = useTheme();
+ return (
+
+ {title && (
+
+
+
+ {title}
+
+
+
+
+ )}
+
+ {children}
+
+
+ );
+};
+SummaryCard.propTypes = {
+ children: PropTypes.node,
+ isHighPriority: PropTypes.bool,
+ sx: PropTypes.object,
+ title: PropTypes.string,
+};
+export default SummaryCard;
diff --git a/client/src/Pages/Incidents/hooks/useFetchIncidents.js b/client/src/Pages/Incidents/hooks/useFetchIncidents.js
new file mode 100644
index 000000000..e308c9319
--- /dev/null
+++ b/client/src/Pages/Incidents/hooks/useFetchIncidents.js
@@ -0,0 +1,215 @@
+import { useState, useCallback } from "react";
+import { networkService } from "../../../main.jsx";
+import { createToast } from "../../../Utils/toastUtils.jsx";
+import { useTranslation } from "react-i18next";
+/**
+ * Hook to fetch and manage incidents
+ *
+ * Provides multiple functions to fetch incidents with different filters.
+ *
+ * @returns {Object} {
+ * incidents,
+ * incidentsCount,
+ * isLoading,
+ * networkError,
+ * fetchIncidents,
+ * fetchActiveIncidents,
+ * fetchResolvedIncidents,
+ * fetchIncidentsByTeam,
+ * fetchIncidentById,
+ * resolveIncident,
+ * fetchIncidentSummary
+ * }
+ */
+const useFetchIncidents = () => {
+ const [incidents, setIncidents] = useState(null);
+ const [incidentsCount, setIncidentsCount] = useState(null);
+ const [isLoading, setIsLoading] = useState(false);
+ const [networkError, setNetworkError] = useState(false);
+ const { t } = useTranslation();
+ /**
+ * Generic function to fetch incidents by team with custom config
+ *
+ * @param {Object} config - Configuration object
+ * @param {string} [config.monitorId] - Filter by monitor ID
+ * @param {boolean} [config.status] - Filter by status (true=active, false=resolved)
+ * @param {string} [config.resolutionType] - Filter by resolution type (automatic/manual)
+ * @param {string} [config.sortOrder] - Sort order (asc/desc)
+ * @param {string} [config.dateRange] - Date range filter
+ * @param {number} [config.page] - Page number
+ * @param {number} [config.rowsPerPage] - Rows per page
+ */
+ const fetchIncidentsByTeam = useCallback(async (config = {}) => {
+ try {
+ setIsLoading(true);
+ setNetworkError(false);
+
+ const res = await networkService.getIncidentsByTeam(config);
+ setIncidents(res.data?.data?.incidents || []);
+ setIncidentsCount(res.data?.data?.count || 0);
+ } catch (error) {
+ setNetworkError(true);
+ console.error(t("incidentsPage.errorFetchingIncidents"), error);
+ createToast({ body: error.message || t("incidentsPage.errorFetchingIncidents") });
+ } finally {
+ setIsLoading(false);
+ }
+ }, []);
+
+ /**
+ * Fetch active (open) incidents - Default function
+ *
+ * @param {Object} config - Additional configuration
+ */
+ const fetchActiveIncidents = useCallback(
+ async (config = {}) => {
+ await fetchIncidentsByTeam({
+ status: true,
+ sortOrder: "desc",
+ ...config,
+ });
+ },
+ [fetchIncidentsByTeam]
+ );
+
+ const fetchIncidentsByResolutionType = useCallback(
+ async (resolutionType, config = {}) => {
+ await fetchIncidentsByTeam({
+ resolutionType,
+ sortOrder: "desc",
+ ...config,
+ });
+ },
+ [fetchIncidentsByTeam]
+ );
+ /**
+ * Fetch resolved incidents
+ *
+ * @param {Object} config - Additional configuration
+ */
+ const fetchResolvedIncidents = useCallback(
+ async (config = {}) => {
+ await fetchIncidentsByTeam({
+ status: false,
+ sortOrder: "desc",
+ ...config,
+ });
+ },
+ [fetchIncidentsByTeam]
+ );
+
+ /**
+ * Fetch incidents with custom filters (alias for fetchIncidentsByTeam)
+ *
+ * @param {Object} config - Configuration object
+ */
+ const fetchIncidents = useCallback(
+ async (config = {}) => {
+ await fetchIncidentsByTeam(config);
+ },
+ [fetchIncidentsByTeam]
+ );
+
+ /**
+ * Fetch a single incident by ID
+ *
+ * @param {string} incidentId - The ID of the incident to fetch
+ * @returns {Promise} The incident object or null if not found
+ */
+ const fetchIncidentById = useCallback(async (incidentId) => {
+ if (!incidentId) {
+ console.error(t("incidentsPage.noIncidentIdProvided"));
+ return null;
+ }
+
+ try {
+ setIsLoading(true);
+ setNetworkError(false);
+
+ const res = await networkService.getIncidentById(incidentId);
+ return res.data?.data || null;
+ } catch (error) {
+ setNetworkError(true);
+ console.error(t("incidentsPage.errorFetchingIncident"), error);
+ createToast({ body: error.message || t("incidentsPage.errorFetchingIncident") });
+ return null;
+ } finally {
+ setIsLoading(false);
+ }
+ }, []);
+
+ /**
+ * Resolve an incident manually
+ *
+ * @param {string} incidentId - The ID of the incident to resolve
+ * @param {Object} options - Resolution options
+ * @param {string} [options.comment] - Optional comment about the resolution
+ * @param {Function} [onSuccess] - Callback function to call on success
+ * @param {Function} [onError] - Callback function to call on error
+ */
+ const resolveIncident = useCallback(async (incidentId, options = {}) => {
+ if (!incidentId) {
+ console.error(t("incidentsPage.noIncidentIdProvided"));
+ return;
+ }
+
+ try {
+ setIsLoading(true);
+ setNetworkError(false);
+
+ const res = await networkService.resolveIncidentManually(incidentId, options);
+ if (res.data?.success) {
+ createToast({ body: t("incidentsPage.incidentResolvedSuccessfully") });
+ } else {
+ createToast({ body: t("incidentsPage.errorResolvingIncident") });
+ }
+ } catch (error) {
+ setNetworkError(true);
+ console.error(t("incidentsPage.errorResolvingIncident"), error);
+ const errorMessage =
+ error.response?.data?.message ||
+ error.message ||
+ t("incidentsPage.errorResolvingIncident");
+ createToast({ body: errorMessage });
+ } finally {
+ setIsLoading(false);
+ }
+ }, []);
+
+ /**
+ * Fetch incident summary
+ *
+ * @param {Object} config - Configuration object
+ * @param {number} [config.limit=10] - Number of latest incidents to return
+ * @returns {Promise} The summary object or null if error
+ */
+ const fetchIncidentSummary = useCallback(async (config = {}) => {
+ try {
+ const res = await networkService.getIncidentSummary(config);
+ return res.data?.data || null;
+ } catch (error) {
+ console.error(t("incidentsPage.errorFetchingIncidentSummary"), error);
+ createToast({
+ body: error.message || t("incidentsPage.errorFetchingIncidentSummary"),
+ });
+ return null;
+ }
+ }, []);
+
+ return {
+ incidents,
+ incidentsCount,
+ isLoading,
+ networkError,
+ fetchIncidents,
+ fetchActiveIncidents,
+ fetchResolvedIncidents,
+ fetchIncidentsByTeam,
+ fetchIncidentById,
+ resolveIncident,
+ fetchIncidentSummary,
+ fetchIncidentsByResolutionType,
+ };
+};
+
+export default useFetchIncidents;
diff --git a/client/src/Pages/Incidents/hooks/useGetIncidentsDuration.js b/client/src/Pages/Incidents/hooks/useGetIncidentsDuration.js
new file mode 100644
index 000000000..a4f434a80
--- /dev/null
+++ b/client/src/Pages/Incidents/hooks/useGetIncidentsDuration.js
@@ -0,0 +1,25 @@
+import { getHumanReadableDuration } from "@/Utils/timeUtilsLegacy.js";
+const useGetIncidentsDuration = (incident, isActive) => {
+ if (!incident?.startTime) {
+ return "-";
+ }
+ const startTime = new Date(incident?.startTime);
+ const endTime = isActive
+ ? new Date()
+ : incident?.endTime
+ ? new Date(incident.endTime)
+ : null;
+
+ if (!endTime) {
+ return "-";
+ }
+
+ const durationMs = endTime.getTime() - startTime.getTime();
+
+ if (durationMs < 0) {
+ return "-";
+ }
+
+ return getHumanReadableDuration(durationMs);
+};
+export default useGetIncidentsDuration;
diff --git a/client/src/Pages/Incidents/index.jsx b/client/src/Pages/Incidents/index.jsx
new file mode 100644
index 000000000..0377405b7
--- /dev/null
+++ b/client/src/Pages/Incidents/index.jsx
@@ -0,0 +1,178 @@
+// Components
+import { Stack } from "@mui/material";
+import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
+import GenericFallback from "@/Components/v1/GenericFallback/index.jsx";
+import IncidentTable from "./Components/IncidentTable/index.jsx";
+import OptionsHeader from "./Components/OptionsHeader/index.jsx";
+import IncidentsSummaryPanel from "./Components/IncidentsSummaryPanel/index.jsx";
+import IncidentDetailsModal from "./Components/IncidentDetailsModal/index.jsx";
+//Utils
+import { useTheme } from "@emotion/react";
+import { useState, useEffect } from "react";
+import NetworkError from "@/Components/v1/GenericFallback/NetworkError.jsx";
+import { useTranslation } from "react-i18next";
+import { useParams } from "react-router-dom";
+
+// Hooks
+import useFetchIncidents from "./hooks/useFetchIncidents.js";
+import { useFetchMonitorsByTeamId } from "../../Hooks/monitorHooks.js";
+
+const Incidents2 = () => {
+ const { monitorId } = useParams();
+ const { t } = useTranslation();
+
+ const BREADCRUMBS = [
+ { name: t("incidentsPageTitle", "Incidents"), path: "/incidents" },
+ ];
+
+ const [selectedMonitor, setSelectedMonitor] = useState(monitorId || "0");
+ const [filter, setFilter] = useState("all");
+ const [dateRange, setDateRange] = useState("all");
+ const [page, setPage] = useState(0);
+ const [rowsPerPage, setRowsPerPage] = useState(10);
+ const [monitorLookup, setMonitorLookup] = useState(undefined);
+ const [updateTrigger, setUpdateTrigger] = useState(false);
+ const [isDetailsOpen, setIsDetailsOpen] = useState(false);
+ const [selectedIncidentId, setSelectedIncidentId] = useState(null);
+ const handleUpdateTrigger = () => {
+ setUpdateTrigger((prev) => !prev);
+ };
+
+ const [monitors, isLoadingMonitors, monitorsNetworkError] = useFetchMonitorsByTeamId(
+ {}
+ );
+
+ const {
+ incidents,
+ incidentsCount,
+ isLoading: isLoadingIncidents,
+ networkError: incidentsNetworkError,
+ fetchIncidents,
+ fetchActiveIncidents,
+ fetchResolvedIncidents,
+ resolveIncident,
+ fetchIncidentsByResolutionType,
+ } = useFetchIncidents();
+
+ const networkError = monitorsNetworkError || incidentsNetworkError;
+
+ const theme = useTheme();
+
+ useEffect(() => {
+ setPage(0);
+ }, [selectedMonitor, filter, dateRange]);
+
+ useEffect(() => {
+ const config = {
+ monitorId: selectedMonitor !== "0" ? selectedMonitor : undefined,
+ sortOrder: "desc",
+ dateRange,
+ page,
+ rowsPerPage,
+ };
+
+ if (filter === "active") {
+ fetchActiveIncidents(config);
+ } else if (filter === "resolved") {
+ fetchResolvedIncidents(config);
+ } else if (filter === "manual") {
+ fetchIncidentsByResolutionType("manual", config);
+ } else if (filter === "automatic") {
+ fetchIncidentsByResolutionType("automatic", config);
+ } else {
+ fetchIncidents(config);
+ }
+ }, [
+ selectedMonitor,
+ filter,
+ dateRange,
+ page,
+ rowsPerPage,
+ updateTrigger,
+ fetchActiveIncidents,
+ fetchResolvedIncidents,
+ fetchIncidents,
+ fetchIncidentsByResolutionType,
+ ]);
+
+ useEffect(() => {
+ const lookup = monitors?.reduce((acc, monitor) => {
+ acc[monitor.id] = {
+ id: monitor.id,
+ name: monitor.name,
+ type: monitor.type,
+ };
+ return acc;
+ }, {});
+ setMonitorLookup(lookup);
+ }, [monitors]);
+
+ if (networkError) {
+ return (
+
+
+
+ );
+ }
+
+ const handleChangePage = (_, newPage) => {
+ setPage(newPage);
+ };
+
+ const handleChangeRowsPerPage = (event) => {
+ setRowsPerPage(parseInt(event.target.value, 10));
+ setPage(0);
+ };
+ const handleOpenDetails = (id) => {
+ if (!id) return;
+ setSelectedIncidentId(id);
+ setIsDetailsOpen(true);
+ };
+
+ const handleCloseDetails = () => {
+ setIsDetailsOpen(false);
+ setSelectedIncidentId(null);
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Incidents2;
diff --git a/client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx b/client/src/Pages/Infrastructure/Create/Components/CustomAlertsSection.jsx
similarity index 96%
rename from client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx
rename to client/src/Pages/Infrastructure/Create/Components/CustomAlertsSection.jsx
index 5fe1d8984..64f82cec2 100644
--- a/client/src/Pages/v1/Infrastructure/Create/Components/CustomAlertsSection.jsx
+++ b/client/src/Pages/Infrastructure/Create/Components/CustomAlertsSection.jsx
@@ -1,7 +1,7 @@
import ConfigBox from "@/Components/v1/ConfigBox/index.jsx";
import { Box, Stack, Typography } from "@mui/material";
import { CustomThreshold } from "./CustomThreshold/index.jsx";
-import { capitalizeFirstLetter } from "../../../../../Utils/stringUtils.js";
+import { capitalizeFirstLetter } from "../../../../Utils/stringUtils.js";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
import PropTypes from "prop-types";
diff --git a/client/src/Pages/v1/Infrastructure/Create/Components/CustomThreshold/index.jsx b/client/src/Pages/Infrastructure/Create/Components/CustomThreshold/index.jsx
similarity index 100%
rename from client/src/Pages/v1/Infrastructure/Create/Components/CustomThreshold/index.jsx
rename to client/src/Pages/Infrastructure/Create/Components/CustomThreshold/index.jsx
diff --git a/client/src/Pages/Infrastructure/Create/Components/DiskSelection.jsx b/client/src/Pages/Infrastructure/Create/Components/DiskSelection.jsx
new file mode 100644
index 000000000..50c4ed980
--- /dev/null
+++ b/client/src/Pages/Infrastructure/Create/Components/DiskSelection.jsx
@@ -0,0 +1,90 @@
+import React from "react";
+import ConfigBox from "@/Components/v1/ConfigBox/index.jsx";
+import { Box, Stack, Typography } from "@mui/material";
+import Checkbox from "@/Components/v1/Inputs/Checkbox/index.jsx";
+import { useTheme } from "@emotion/react";
+import { useTranslation } from "react-i18next";
+import PropTypes from "prop-types";
+
+const DiskSelection = ({ availableDisks, selectedDisks, onChange }) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+
+ const handleDiskChange = (event, mountpoint) => {
+ const isChecked = event.target.checked;
+ let newSelectedDisks = [];
+
+ if (isChecked) {
+ newSelectedDisks = [...selectedDisks, mountpoint];
+ } else {
+ newSelectedDisks = selectedDisks.filter((disk) => disk !== mountpoint);
+ }
+
+ onChange(newSelectedDisks);
+ };
+
+ return (
+
+
+
+ {t("v1.infrastructure.disk_selection_title")}
+
+
+ {t("v1.infrastructure.disk_selection_description")}
+
+
+
+
+ {!availableDisks || availableDisks.length === 0 ? (
+
+ {t("v1.infrastructure.disk_selection_info")}
+
+ ) : (
+ availableDisks.map((disk) => {
+ const identifier = disk.mountpoint || disk.device;
+ return (
+
+
+ handleDiskChange(e, identifier)}
+ />
+
+
+ );
+ })
+ )}
+
+
+ );
+};
+
+DiskSelection.propTypes = {
+ availableDisks: PropTypes.arrayOf(
+ PropTypes.shape({
+ mountpoint: PropTypes.string.isRequired,
+ })
+ ),
+ selectedDisks: PropTypes.arrayOf(PropTypes.string).isRequired,
+ onChange: PropTypes.func.isRequired,
+};
+
+export default DiskSelection;
diff --git a/client/src/Pages/v1/Infrastructure/Create/Components/MonitorActionButtons.jsx b/client/src/Pages/Infrastructure/Create/Components/MonitorActionButtons.jsx
similarity index 87%
rename from client/src/Pages/v1/Infrastructure/Create/Components/MonitorActionButtons.jsx
rename to client/src/Pages/Infrastructure/Create/Components/MonitorActionButtons.jsx
index 871fe0d7b..2bde5f321 100644
--- a/client/src/Pages/v1/Infrastructure/Create/Components/MonitorActionButtons.jsx
+++ b/client/src/Pages/Infrastructure/Create/Components/MonitorActionButtons.jsx
@@ -2,8 +2,7 @@ import { useState } from "react";
import { Box, Button } from "@mui/material";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
-import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline";
-import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded";
+import Icon from "@/Components/v1/Icon";
import Dialog from "@/Components/v1/Dialog/index.jsx";
import PropTypes from "prop-types";
@@ -36,12 +35,18 @@ const MonitorActionButtons = ({ monitor, isBusy, handlePause, handleRemove }) =>
>
{monitor?.isActive ? (
<>
-
+
{t("pause")}
>
) : (
<>
-
+
{t("resume")}
>
)}
diff --git a/client/src/Pages/v1/Infrastructure/Create/Components/MonitorStatusHeader.jsx b/client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx
similarity index 95%
rename from client/src/Pages/v1/Infrastructure/Create/Components/MonitorStatusHeader.jsx
rename to client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx
index 570f38a7e..8fa6f4ff1 100644
--- a/client/src/Pages/v1/Infrastructure/Create/Components/MonitorStatusHeader.jsx
+++ b/client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx
@@ -1,5 +1,5 @@
import { Box, Stack, Tooltip, Typography } from "@mui/material";
-import { useMonitorUtils } from "../../../../../Hooks/v1/useMonitorUtils.js";
+import { useMonitorUtils } from "../../../../Hooks/useMonitorUtils.js";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
import PulseDot from "@/Components/v1/Animated/PulseDot.jsx";
diff --git a/client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx b/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx
similarity index 97%
rename from client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx
rename to client/src/Pages/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx
index f0a701517..a38ac9fc6 100644
--- a/client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx
+++ b/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx
@@ -17,6 +17,7 @@ const useInfrastructureMonitorForm = () => {
temperature: false,
usage_temperature: "",
secret: "",
+ selectedDisks: [],
});
const onChangeForm = (name, value) => {
@@ -48,6 +49,7 @@ const useInfrastructureMonitorForm = () => {
temperature: gt.temperature !== undefined,
usage_temperature: gt.temperature !== undefined ? gt.temperature.toString() : "",
secret: "",
+ selectedDisks: [],
}));
}, []);
@@ -81,6 +83,7 @@ const useInfrastructureMonitorForm = () => {
? (thresholds.usage_temperature * 100).toString()
: "",
secret: monitor.secret || "",
+ selectedDisks: monitor.selectedDisks || [],
}));
}, []);
return {
diff --git a/client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx b/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx
similarity index 90%
rename from client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx
rename to client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx
index 8a97486c7..04279cac4 100644
--- a/client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx
+++ b/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx
@@ -1,7 +1,4 @@
-import {
- useCreateMonitor,
- useUpdateMonitor,
-} from "../../../../../Hooks/v1/monitorHooks.js";
+import { useCreateMonitor, useUpdateMonitor } from "../../../../Hooks/monitorHooks.js";
const useInfrastructureSubmit = () => {
const [createMonitor, isCreating] = useCreateMonitor();
const [updateMonitor, isUpdating] = useUpdateMonitor();
@@ -29,11 +26,11 @@ const useInfrastructureSubmit = () => {
...(infrastructureMonitor.disk
? { usage_disk: infrastructureMonitor.usage_disk }
: {}),
- temperature: infrastructureMonitor.temperature,
...(infrastructureMonitor.temperature
? { usage_temperature: infrastructureMonitor.usage_temperature }
: {}),
secret: infrastructureMonitor.secret,
+ selectedDisks: infrastructureMonitor.selectedDisks,
};
return form;
};
@@ -52,6 +49,7 @@ const useInfrastructureSubmit = () => {
usage_disk,
temperature,
usage_temperature,
+ selectedDisks,
...rest
} = form;
@@ -63,11 +61,12 @@ const useInfrastructureSubmit = () => {
};
const finalForm = {
- ...(isCreate ? {} : { _id: monitorId }),
+ ...(isCreate ? {} : { id: monitorId }),
...rest,
description: form.name,
type: "hardware",
notifications: infrastructureMonitor.notifications,
+ selectedDisks,
thresholds,
};
// Handle create or update
diff --git a/client/src/Pages/v1/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx b/client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx
similarity index 85%
rename from client/src/Pages/v1/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx
rename to client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx
index 73097d0d8..f7665e65f 100644
--- a/client/src/Pages/v1/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx
+++ b/client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx
@@ -1,6 +1,6 @@
import { useState } from "react";
-import { infrastructureMonitorValidation } from "../../../../../Validation/validation.js";
-import { createToast } from "../../../../../Utils/toastUtils.jsx";
+import { infrastructureMonitorValidation } from "../../../../Validation/validation.js";
+import { createToast } from "../../../../Utils/toastUtils.jsx";
const useValidateInfrastructureForm = () => {
const [errors, setErrors] = useState({});
diff --git a/client/src/Pages/v1/Infrastructure/Create/index.jsx b/client/src/Pages/Infrastructure/Create/index.jsx
similarity index 87%
rename from client/src/Pages/v1/Infrastructure/Create/index.jsx
rename to client/src/Pages/Infrastructure/Create/index.jsx
index d4fd859ec..936d87ab0 100644
--- a/client/src/Pages/v1/Infrastructure/Create/index.jsx
+++ b/client/src/Pages/Infrastructure/Create/index.jsx
@@ -10,9 +10,11 @@ import { HttpAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index
import MonitorStatusHeader from "./Components/MonitorStatusHeader.jsx";
import MonitorActionButtons from "./Components/MonitorActionButtons.jsx";
import CustomAlertsSection from "./Components/CustomAlertsSection.jsx";
+import DiskSelection from "./Components/DiskSelection.jsx";
// Utils
import NotificationsConfig from "@/Components/v1/NotificationConfig/index.jsx";
-import { useGetNotificationsByTeamId } from "../../../../Hooks/v1/useNotifications.js";
+import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications.js";
+import { networkService } from "../../../Utils/NetworkService.js";
import { useParams } from "react-router-dom";
import { useState, useEffect } from "react";
import { useTheme } from "@emotion/react";
@@ -20,9 +22,9 @@ import { useTranslation } from "react-i18next";
import {
useDeleteMonitor,
useFetchGlobalSettings,
- useFetchHardwareMonitorById,
+ useFetchMonitorById,
usePauseMonitor,
-} from "../../../../Hooks/v1/monitorHooks.js";
+} from "@/Hooks/monitorHooks.js";
import useInfrastructureMonitorForm from "./hooks/useInfrastructureMonitorForm.jsx";
import useValidateInfrastructureForm from "./hooks/useValidateInfrastructureForm.jsx";
import useInfrastructureSubmit from "./hooks/useInfrastructureSubmit.jsx";
@@ -35,13 +37,16 @@ const CreateInfrastructureMonitor = () => {
const { t } = useTranslation();
// State
+ const [monitor, setMonitor] = useState(null);
const [https, setHttps] = useState(false);
const [updateTrigger, setUpdateTrigger] = useState(false);
+ const [availableDisks, setAvailableDisks] = useState([]);
// Fetch monitor details if editing
- const [monitor, isLoading] = useFetchHardwareMonitorById({
+ const [isLoading] = useFetchMonitorById({
monitorId,
- updateTrigger,
+ setMonitor,
+ updateTrigger: true,
});
const [deleteMonitor, isDeleting] = useDeleteMonitor();
const [globalSettings, globalSettingsLoading] = useFetchGlobalSettings();
@@ -85,6 +90,29 @@ const CreateInfrastructureMonitor = () => {
} else if (monitor) {
setHttps(monitor.url.startsWith("https"));
initializeInfrastructureMonitorForUpdate(monitor);
+ const fetchLastCheck = async () => {
+ try {
+ let disks = monitor?.recentChecks?.[0]?.disk || [];
+
+ if (disks.length > 0) {
+ setAvailableDisks(disks);
+ return;
+ }
+
+ const response = await networkService.getChecksByMonitor({
+ monitorId,
+ dateRange: "all",
+ rowsPerPage: 1,
+ sortOrder: "desc",
+ });
+ disks = response?.data?.data?.checks?.[0]?.disk || [];
+ setAvailableDisks(disks);
+ } catch (error) {
+ setAvailableDisks([]);
+ }
+ };
+
+ fetchLastCheck();
}
}, [
isCreate,
@@ -99,7 +127,9 @@ const CreateInfrastructureMonitor = () => {
const onSubmit = async (event) => {
event.preventDefault();
const form = buildForm(infrastructureMonitor, https);
- const error = validateForm(form);
+ // When editing, exclude URL from validation since it's disabled and can't be changed
+ const formToValidate = isCreate ? form : { ...form, url: monitor.url };
+ const error = validateForm(formToValidate);
if (error) {
return;
}
@@ -175,14 +205,14 @@ const CreateInfrastructureMonitor = () => {
<>>
)}
- {!isCreate && (
+ {!isCreate && monitor && (
)}
- {!isCreate && (
+ {!isCreate && monitor && (
{
infrastructureMonitor={infrastructureMonitor}
handleCheckboxChange={handleCheckboxChange}
/>
+
+ {monitorId && (
+
+ onChangeForm("selectedDisks", newSelectedDisks)
+ }
+ />
+ )}
+
{
+const BaseContainer = ({ children, sx = {}, shouldExpand = false }) => {
const theme = useTheme();
const { getDimensions } = useHardwareUtils();
return (
@@ -22,7 +22,7 @@ const BaseContainer = ({ children, sx = {} }) => {
sx={{
padding: `${theme.spacing(getDimensions().baseBoxPaddingVertical)} ${theme.spacing(getDimensions().baseBoxPaddingHorizontal)}`,
minWidth: 200,
- width: 225,
+ width: shouldExpand ? "100%" : 225,
backgroundColor: theme.palette.primary.main,
border: 1,
borderStyle: "solid",
@@ -38,6 +38,7 @@ const BaseContainer = ({ children, sx = {} }) => {
BaseContainer.propTypes = {
children: PropTypes.node.isRequired,
sx: PropTypes.object,
+ shouldExpand: PropTypes.bool,
};
export default BaseContainer;
diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx b/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx
similarity index 64%
rename from client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx
rename to client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx
index cd61b7109..c762eeb32 100644
--- a/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx
+++ b/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx
@@ -6,21 +6,37 @@ import { Stack, Typography, Box } from "@mui/material";
import { useTheme } from "@emotion/react";
import PropTypes from "prop-types";
-const Gauge = ({ value, heading, metricOne, valueOne, metricTwo, valueTwo }) => {
+const Gauge = ({
+ value,
+ heading,
+ metricOne,
+ valueOne,
+ metricTwo,
+ valueTwo,
+ metricThree,
+ valueThree,
+ metricFour,
+ valueFour,
+ shouldExpand = false,
+}) => {
const theme = useTheme();
const valueStyle = {
borderRadius: theme.spacing(2),
backgroundColor: theme.palette.tertiary.main,
- width: "40%",
+ minWidth: "40%",
+ maxWidth: "60%",
mb: theme.spacing(2),
mt: theme.spacing(2),
pr: theme.spacing(2),
textAlign: "right",
+ overflow: "hidden",
+ textOverflow: "ellipsis",
+ whiteSpace: "nowrap",
};
return (
-
+
{metricTwo}
{valueTwo}
+
+ {metricThree}
+ {valueThree}
+
+
+ {metricFour}
+ {valueFour}
+
@@ -83,6 +117,11 @@ Gauge.propTypes = {
valueOne: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
metricTwo: PropTypes.string,
valueTwo: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
+ metricThree: PropTypes.string,
+ valueThree: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
+ metricFour: PropTypes.string,
+ valueFour: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
+ shouldExpand: PropTypes.bool,
};
export default Gauge;
diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/index.jsx b/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx
similarity index 56%
rename from client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/index.jsx
rename to client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx
index 432881e7e..3859fe040 100644
--- a/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/index.jsx
+++ b/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx
@@ -1,5 +1,5 @@
// Components
-import { Stack } from "@mui/material";
+import { Box } from "@mui/material";
import Gauge from "./Gauge.jsx";
import SkeletonLayout from "./skeleton.jsx";
import PropTypes from "prop-types";
@@ -10,7 +10,8 @@ import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
const Gauges = ({ isLoading = false, monitor }) => {
- const { decimalToPercentage, formatBytes } = useHardwareUtils();
+ const { decimalToPercentage, formatBytes, formatDeviceName, formatMountpoint } =
+ useHardwareUtils();
const theme = useTheme();
const { t } = useTranslation();
@@ -18,8 +19,7 @@ const Gauges = ({ isLoading = false, monitor }) => {
return ;
}
- const { stats } = monitor ?? {};
- let latestCheck = stats?.aggregateData?.latestCheck;
+ const latestCheck = monitor?.recentChecks?.[0];
const memoryUsagePercent = latestCheck?.memory?.usage_percent ?? 0;
const memoryUsedBytes = latestCheck?.memory?.used_bytes ?? 0;
const memoryTotalBytes = latestCheck?.memory?.total_bytes ?? 0;
@@ -46,23 +46,41 @@ const Gauges = ({ isLoading = false, monitor }) => {
metricTwo: t("frequency"),
valueTwo: `${(cpuFrequency / 1000).toFixed(2)} Ghz`,
},
- ...(latestCheck?.disk ?? []).map((disk, idx) => ({
- type: "disk",
- diskIndex: idx,
- value: decimalToPercentage(disk.usage_percent),
- heading: `Disk${idx} usage`,
- metricOne: t("used"),
- valueOne: formatBytes(disk.total_bytes - disk.free_bytes, true),
- metricTwo: t("total"),
- valueTwo: formatBytes(disk.total_bytes, true),
- })),
+ ...(latestCheck?.disk ?? [])
+ .filter((disk) => {
+ if (!monitor?.selectedDisks || monitor.selectedDisks.length === 0) {
+ return true;
+ }
+ return monitor.selectedDisks.includes(disk.mountpoint || disk.device);
+ })
+ .map((disk, idx) => ({
+ type: "disk",
+ diskIndex: idx,
+ value: decimalToPercentage(disk.usage_percent),
+ heading: `Disk${idx} usage`,
+ metricOne: t("used"),
+ valueOne: formatBytes(disk.total_bytes - disk.free_bytes, true),
+ metricTwo: t("total"),
+ valueTwo: formatBytes(disk.total_bytes, true),
+ metricThree: t("device"),
+ valueThree: formatDeviceName(disk.device),
+ metricFour: t("mountedOn"),
+ valueFour: formatMountpoint(disk.mountpoint),
+ })),
];
+ // Only expand gauges to fill row when there are 4 or more
+ const shouldExpand = gauges.length >= 4;
+
return (
-
{gauges.map((gauge) => {
return (
@@ -74,10 +92,15 @@ const Gauges = ({ isLoading = false, monitor }) => {
valueOne={gauge.valueOne}
metricTwo={gauge.metricTwo}
valueTwo={gauge.valueTwo}
+ metricThree={gauge.metricThree}
+ valueThree={gauge.valueThree}
+ metricFour={gauge.metricFour}
+ valueFour={gauge.valueFour}
+ shouldExpand={shouldExpand}
/>
);
})}
-
+
);
};
diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx b/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx
similarity index 100%
rename from client/src/Pages/v1/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx
rename to client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx
diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx
similarity index 100%
rename from client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx
rename to client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx
diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx
similarity index 100%
rename from client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx
rename to client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx
diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/index.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx
similarity index 100%
rename from client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/index.jsx
rename to client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx
diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/skeleton.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/skeleton.jsx
similarity index 100%
rename from client/src/Pages/v1/Infrastructure/Details/Components/NetworkStats/skeleton.jsx
rename to client/src/Pages/Infrastructure/Details/Components/NetworkStats/skeleton.jsx
diff --git a/client/src/Pages/v1/Infrastructure/Details/Components/StatusBoxes/index.jsx b/client/src/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx
similarity index 94%
rename from client/src/Pages/v1/Infrastructure/Details/Components/StatusBoxes/index.jsx
rename to client/src/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx
index 7f09e82d5..d411886c7 100644
--- a/client/src/Pages/v1/Infrastructure/Details/Components/StatusBoxes/index.jsx
+++ b/client/src/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx
@@ -4,7 +4,7 @@ import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx";
import StatBox from "@/Components/v1/StatBox/index.jsx";
//Utils
-import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js";
+import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils.js";
import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx";
import { useTranslation } from "react-i18next";
@@ -14,8 +14,7 @@ const InfraStatBoxes = ({ shouldRender, monitor }) => {
const { determineState } = useMonitorUtils();
const { t } = useTranslation();
- const { stats } = monitor ?? {};
- const latestCheck = stats?.aggregateData?.latestCheck;
+ const latestCheck = monitor?.recentChecks?.[0];
// Get data from latest check
const physicalCores = latestCheck?.cpu?.physical_core ?? 0;
diff --git a/client/src/Pages/v1/Infrastructure/Details/Hooks/useHardwareUtils.jsx b/client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx
similarity index 75%
rename from client/src/Pages/v1/Infrastructure/Details/Hooks/useHardwareUtils.jsx
rename to client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx
index 8edb6fb87..8938847a4 100644
--- a/client/src/Pages/v1/Infrastructure/Details/Hooks/useHardwareUtils.jsx
+++ b/client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx
@@ -1,4 +1,4 @@
-import { Typography } from "@mui/material";
+import { Typography, Tooltip } from "@mui/material";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
@@ -120,6 +120,85 @@ const useHardwareUtils = () => {
}
};
+ const formatDeviceName = (device) => {
+ const deviceStr = String(device || "");
+
+ // Show full device path
+ return (
+
+
+ {deviceStr}
+
+
+ );
+ };
+
+ const formatMountpoint = (mountpoint) => {
+ const mountpointStr = String(mountpoint || "");
+
+ if (!mountpointStr) {
+ return (
+
+
+ N/A
+
+
+ );
+ }
+
+ // Show full mountpoint path
+ return (
+
+
+ {mountpointStr}
+
+
+ );
+ };
+
/**
* Converts a decimal value to a percentage
*
@@ -178,6 +257,8 @@ const useHardwareUtils = () => {
return {
formatBytes,
+ formatDeviceName,
+ formatMountpoint,
decimalToPercentage,
buildTemps,
getDimensions,
diff --git a/client/src/Pages/v1/Infrastructure/Details/index.jsx b/client/src/Pages/Infrastructure/Details/index.jsx
similarity index 94%
rename from client/src/Pages/v1/Infrastructure/Details/index.jsx
rename to client/src/Pages/Infrastructure/Details/index.jsx
index f9c51c27d..6ae630822 100644
--- a/client/src/Pages/v1/Infrastructure/Details/index.jsx
+++ b/client/src/Pages/Infrastructure/Details/index.jsx
@@ -13,8 +13,8 @@ import TabContext from "@mui/lab/TabContext";
// Utils
import { useTheme } from "@emotion/react";
-import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js";
-import { useFetchHardwareMonitorById } from "../../../../Hooks/v1/monitorHooks.js";
+import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
+import { useFetchHardwareMonitorById } from "../../../Hooks/monitorHooks.js";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom";
@@ -127,7 +127,7 @@ const InfrastructureDetails = () => {
)}
{tab === "network" && (
}
+ endIcon={
+
+ }
sx={{
visibility: isFilterActive ? "visible" : "hidden",
}}
diff --git a/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx b/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx
similarity index 86%
rename from client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx
rename to client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx
index cc2cd5f98..d12ef1d24 100644
--- a/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTable/index.jsx
+++ b/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx
@@ -7,12 +7,12 @@ import { Stack } from "@mui/material";
import { InfrastructureMenu } from "../MonitorsTableMenu/index.jsx";
import LoadingSpinner from "../../../../Uptime/Monitors/Components/LoadingSpinner/index.jsx";
// Assets
-import CPUChipIcon from "../../../../../../assets/icons/cpu-chip.svg?react";
+import Icon from "@/Components/v1/Icon";
import CustomGauge from "@/Components/v1/Charts/CustomGauge/index.jsx";
// Utils
import { useTheme } from "@emotion/react";
-import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js";
+import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils.js";
import { useNavigate } from "react-router-dom";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
@@ -67,9 +67,9 @@ const MonitorsTable = ({
alignItems={"center"}
gap=".25rem"
>
-
{row.processor}
@@ -102,10 +102,10 @@ const MonitorsTable = ({
const data = monitors?.map((monitor) => {
const processor =
- ((monitor.checks[0]?.cpu?.frequency ?? 0) / 1000).toFixed(2) + " GHz";
- const cpu = (monitor?.checks[0]?.cpu.usage_percent ?? 0) * 100;
- const mem = (monitor?.checks[0]?.memory.usage_percent ?? 0) * 100;
- const disk = (monitor?.checks[0]?.disk[0]?.usage_percent ?? 0) * 100;
+ ((monitor.recentChecks?.[0]?.cpu?.frequency ?? 0) / 1000).toFixed(2) + " GHz";
+ const cpu = (monitor?.recentChecks?.[0]?.cpu?.usage_percent ?? 0) * 100;
+ const mem = (monitor?.recentChecks?.[0]?.memory?.usage_percent ?? 0) * 100;
+ const disk = (monitor?.recentChecks?.[0]?.disk?.[0]?.usage_percent ?? 0) * 100;
const status = determineState(monitor);
const percentageColor =
monitor.uptimePercentage < 0.25
@@ -115,7 +115,7 @@ const MonitorsTable = ({
: theme.palette.success.main;
return {
- id: monitor._id,
+ id: monitor.id,
name: monitor.name,
url: monitor.url,
processor,
diff --git a/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx b/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx
similarity index 94%
rename from client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx
rename to client/src/Pages/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx
index d3af34d00..50af0aaa2 100644
--- a/client/src/Pages/v1/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx
+++ b/client/src/Pages/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx
@@ -3,13 +3,13 @@
import { useRef, useState } from "react";
import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router-dom";
-import { createToast } from "../../../../../../Utils/toastUtils.jsx";
+import { createToast } from "@/Utils/toastUtils.jsx";
import { IconButton, Menu, MenuItem } from "@mui/material";
-import Settings from "../../../../../../assets/icons/settings-bold.svg?react";
+import Icon from "@/Components/v1/Icon";
import PropTypes from "prop-types";
import Dialog from "@/Components/v1/Dialog/index.jsx";
-import { networkService } from "../../../../../../Utils/NetworkService.js";
-import { usePauseMonitor } from "../../../../../../Hooks/v1/monitorHooks.js";
+import { networkService } from "@/Utils/NetworkService.js";
+import { usePauseMonitor } from "@/Hooks/monitorHooks.js";
import { useTranslation } from "react-i18next";
/**
@@ -100,7 +100,10 @@ const InfrastructureMenu = ({ monitor, isAdmin, updateCallback }) => {
}}
ref={anchor}
>
-
+
{
const field = toFilterStatus !== undefined ? "status" : undefined;
- const [monitors, summary, isLoading, networkError] = useFetchMonitorsByTeamId({
- limit: 1,
+ const [summary, monitors, count, isLoading, networkError] = useFetchMonitorsWithChecks({
types: TYPES,
- page,
+ limit: 1,
+ page: page,
field: field,
filter: toFilterStatus ?? search,
- rowsPerPage,
- updateTrigger,
+ rowsPerPage: rowsPerPage,
+ monitorUpdateTrigger: updateTrigger,
});
return (
@@ -99,7 +100,7 @@ const InfrastructureMonitors = () => {
{
isSearching={isSearching}
/>
{
const theme = useTheme()
return(
-
{children}
@@ -99,7 +102,7 @@ const Gauges = ({ diagnostics, isLoading }) => {
return (
{
@@ -26,12 +26,12 @@ const Diagnostics = () => {
status="up"
heading={t("status")}
subHeading={
- error
+ error
? t("logsPage.logLevelSelect.values.error")
- : isLoading
+ : isLoading
? t("commonSaving")
- : diagnostics
- ? t("diagnosticsPage.diagnosticDescription")
+ : diagnostics
+ ? t("diagnosticsPage.diagnosticDescription")
: t("general.noOptionsFound", { unit: "data" })
}
/>
diff --git a/client/src/Pages/v1/Logs/Diagnostics/utils/utils.js b/client/src/Pages/Logs/Diagnostics/utils/utils.js
similarity index 100%
rename from client/src/Pages/v1/Logs/Diagnostics/utils/utils.js
rename to client/src/Pages/Logs/Diagnostics/utils/utils.js
diff --git a/client/src/Pages/Logs/Logs/index.jsx b/client/src/Pages/Logs/Logs/index.jsx
new file mode 100644
index 000000000..d0319a387
--- /dev/null
+++ b/client/src/Pages/Logs/Logs/index.jsx
@@ -0,0 +1,176 @@
+import Stack from "@mui/material/Stack";
+import Box from "@mui/material/Box";
+import Select from "@/Components/v1/Inputs/Select/index.jsx";
+import Typography from "@mui/material/Typography";
+import DataTable from "@/Components/v1/Table/index.jsx";
+import Pagination from "@/Components/v1/Table/TablePagination/index.jsx";
+
+import { useFetchLogs } from "../../../Hooks/logHooks.js";
+import { useTheme } from "@emotion/react";
+import { useState } from "react";
+import { useTranslation } from "react-i18next";
+
+const LevelBadge = ({ level }) => {
+ const theme = useTheme();
+
+ const levelColors = {
+ info: theme.palette.success.main,
+ warn: theme.palette.warning.main,
+ error: theme.palette.error.main,
+ debug: theme.palette.accent.main,
+ };
+
+ const color = levelColors[level] || theme.palette.primary.contrastText;
+
+ return (
+
+ {level}
+
+ );
+};
+
+const formatTimestamp = (timestamp) => {
+ if (!timestamp) return "-";
+ const date = new Date(timestamp);
+ return date.toLocaleString();
+};
+
+const Logs = () => {
+ const [logLevel, setLogLevel] = useState("all");
+ const [page, setPage] = useState(0);
+ const [rowsPerPage, setRowsPerPage] = useState(15);
+
+ const theme = useTheme();
+ const { t } = useTranslation();
+ const [logs, isLoading, error] = useFetchLogs();
+
+ const LOG_LEVELS = [
+ { _id: "all", name: t("logsPage.logLevelSelect.values.all") },
+ { _id: "info", name: t("logsPage.logLevelSelect.values.info") },
+ { _id: "warn", name: t("logsPage.logLevelSelect.values.warn") },
+ { _id: "error", name: t("logsPage.logLevelSelect.values.error") },
+ { _id: "debug", name: t("logsPage.logLevelSelect.values.debug") },
+ ];
+
+ const headers = [
+ {
+ id: "timestamp",
+ content: t("logsPage.table.timestamp"),
+ render: (row) => (
+
+ {formatTimestamp(row.timestamp)}
+
+ ),
+ },
+ {
+ id: "level",
+ content: t("logsPage.table.level"),
+ render: (row) => ,
+ },
+ {
+ id: "service",
+ content: t("logsPage.table.service"),
+ render: (row) => (
+ {row.service || "-"}
+ ),
+ },
+ {
+ id: "method",
+ content: t("logsPage.table.method"),
+ render: (row) => (
+ {row.method || "-"}
+ ),
+ },
+ {
+ id: "message",
+ content: t("logsPage.table.message"),
+ render: (row) => (
+
+ {row.message || "-"}
+
+ ),
+ },
+ ];
+
+ const filteredLogs = logs
+ ?.filter((log) => {
+ if (logLevel === "all") return true;
+ return log.level === logLevel;
+ })
+ .reverse()
+ .map((log, idx) => ({ ...log, id: idx }));
+
+ const paginatedLogs = filteredLogs?.slice(
+ page * rowsPerPage,
+ page * rowsPerPage + rowsPerPage
+ );
+
+ const handleChangePage = (event, newPage) => {
+ setPage(newPage);
+ };
+
+ const handleChangeRowsPerPage = (event) => {
+ setRowsPerPage(parseInt(event.target.value, 10));
+ setPage(0);
+ };
+
+ const handleLogLevelChange = (e) => {
+ setLogLevel(e.target.value);
+ setPage(0);
+ };
+
+ return (
+
+
+ {t("logsPage.logLevelSelect.title")}
+
+
+
+
+
+ {filteredLogs?.length > 0 && (
+
+ )}
+
+ );
+};
+
+export default Logs;
diff --git a/client/src/Pages/v1/Logs/Queue/components/FailedJobTable/index.jsx b/client/src/Pages/Logs/Queue/components/FailedJobTable/index.jsx
similarity index 96%
rename from client/src/Pages/v1/Logs/Queue/components/FailedJobTable/index.jsx
rename to client/src/Pages/Logs/Queue/components/FailedJobTable/index.jsx
index 743f52639..49f6317bf 100644
--- a/client/src/Pages/v1/Logs/Queue/components/FailedJobTable/index.jsx
+++ b/client/src/Pages/Logs/Queue/components/FailedJobTable/index.jsx
@@ -4,7 +4,7 @@ import DataTable from "@/Components/v1/Table/index.jsx";
import { useNavigate } from "react-router-dom";
import { useTheme } from "@emotion/react";
-import { TypeToPathMap } from "../../../../../../Utils/monitorUtils.js";
+import { TypeToPathMap } from "../../../../../Utils/monitorUtilsLegacy.js";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
diff --git a/client/src/Pages/v1/Logs/Queue/components/JobTable/index.jsx b/client/src/Pages/Logs/Queue/components/JobTable/index.jsx
similarity index 97%
rename from client/src/Pages/v1/Logs/Queue/components/JobTable/index.jsx
rename to client/src/Pages/Logs/Queue/components/JobTable/index.jsx
index 7d309e845..33636b6f5 100644
--- a/client/src/Pages/v1/Logs/Queue/components/JobTable/index.jsx
+++ b/client/src/Pages/Logs/Queue/components/JobTable/index.jsx
@@ -5,7 +5,7 @@ import Typography from "@mui/material/Typography";
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router-dom";
-import { TypeToPathMap } from "../../../../../../Utils/monitorUtils.js";
+import { TypeToPathMap } from "../../../../../Utils/monitorUtilsLegacy.js";
import { useTranslation } from "react-i18next";
import { createHeaderFactory } from "@/Components/v1/Table/TableUtils.js";
diff --git a/client/src/Pages/v1/Logs/Queue/components/Metrics/index.jsx b/client/src/Pages/Logs/Queue/components/Metrics/index.jsx
similarity index 100%
rename from client/src/Pages/v1/Logs/Queue/components/Metrics/index.jsx
rename to client/src/Pages/Logs/Queue/components/Metrics/index.jsx
diff --git a/client/src/Pages/v1/Logs/Queue/components/MetricsTable/index.jsx b/client/src/Pages/Logs/Queue/components/MetricsTable/index.jsx
similarity index 100%
rename from client/src/Pages/v1/Logs/Queue/components/MetricsTable/index.jsx
rename to client/src/Pages/Logs/Queue/components/MetricsTable/index.jsx
diff --git a/client/src/Pages/v1/Logs/Queue/index.jsx b/client/src/Pages/Logs/Queue/index.jsx
similarity index 95%
rename from client/src/Pages/v1/Logs/Queue/index.jsx
rename to client/src/Pages/Logs/Queue/index.jsx
index 10eca4d3b..b2d0dc685 100644
--- a/client/src/Pages/v1/Logs/Queue/index.jsx
+++ b/client/src/Pages/Logs/Queue/index.jsx
@@ -10,7 +10,7 @@ import Divider from "@mui/material/Divider";
// Utils
import { useState } from "react";
-import { useFetchQueueData, useFlushQueue } from "../../../../Hooks/v1/logHooks.js";
+import { useFetchQueueData, useFlushQueue } from "../../../Hooks/logHooks.js";
import { useTranslation } from "react-i18next";
import { useTheme } from "@emotion/react";
diff --git a/client/src/Pages/v1/Logs/index.jsx b/client/src/Pages/Logs/index.jsx
similarity index 100%
rename from client/src/Pages/v1/Logs/index.jsx
rename to client/src/Pages/Logs/index.jsx
diff --git a/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx b/client/src/Pages/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx
similarity index 100%
rename from client/src/Pages/v1/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx
rename to client/src/Pages/Maintenance/CreateMaintenance/Components/ConfigSelect/index.jsx
diff --git a/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx b/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx
similarity index 83%
rename from client/src/Pages/v1/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx
rename to client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx
index c3a4681a1..2bf7a991f 100644
--- a/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx
+++ b/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorList/index.jsx
@@ -1,6 +1,6 @@
// Components
import { Stack, Typography } from "@mui/material";
-import DeleteIcon from "@mui/icons-material/Delete";
+import Icon from "@/Components/v1/Icon";
import { useTheme } from "@emotion/react";
import PropTypes from "prop-types";
@@ -14,8 +14,10 @@ const MonitorListItem = ({ monitor, onDelete }) => {
width="100%"
>
{monitor.name}
- onDelete(monitor)}
/>
@@ -24,7 +26,7 @@ const MonitorListItem = ({ monitor, onDelete }) => {
MonitorListItem.propTypes = {
monitor: PropTypes.shape({
- _id: PropTypes.string.isRequired,
+ id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
}).isRequired,
onDelete: PropTypes.func.isRequired,
@@ -33,7 +35,7 @@ MonitorListItem.propTypes = {
const MonitorList = ({ selectedMonitors, setSelectedMonitors }) => {
const onDelete = (monitorToDelete) => {
const newMonitors = selectedMonitors.filter(
- (monitor) => monitor._id !== monitorToDelete._id
+ (monitor) => monitor.id !== monitorToDelete.id
);
setSelectedMonitors(newMonitors);
};
@@ -47,7 +49,7 @@ const MonitorList = ({ selectedMonitors, setSelectedMonitors }) => {
>
{selectedMonitors?.map((monitor) => (
@@ -59,7 +61,7 @@ const MonitorList = ({ selectedMonitors, setSelectedMonitors }) => {
MonitorList.propTypes = {
selectedMonitors: PropTypes.arrayOf(
PropTypes.shape({
- _id: PropTypes.string.isRequired,
+ id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
})
).isRequired,
diff --git a/client/src/Pages/v1/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx b/client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx
similarity index 100%
rename from client/src/Pages/v1/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx
rename to client/src/Pages/Maintenance/CreateMaintenance/Components/MonitorsConfig/index.jsx
diff --git a/client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx b/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx
similarity index 81%
rename from client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx
rename to client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx
index a75cbe206..a5e853160 100644
--- a/client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx
+++ b/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceActions.jsx
@@ -1,4 +1,4 @@
-import { networkService } from "../../../../../main.jsx";
+import { networkService } from "../../../../main.jsx";
import dayjs from "dayjs";
import {
MS_PER_SECOND,
@@ -6,7 +6,7 @@ import {
MS_PER_HOUR,
MS_PER_DAY,
MS_PER_WEEK,
-} from "../../../../../Utils/timeUtils.js";
+} from "../../../../Utils/timeUtilsLegacy.js";
const useMaintenanceActions = () => {
const MS_LOOKUP = {
@@ -22,6 +22,12 @@ const useMaintenanceActions = () => {
weekly: MS_PER_DAY * 7,
};
const handleSubmitForm = async (maintenanceWindowId, form) => {
+ const toStringId = (monitor) => {
+ if (!monitor) {
+ return "";
+ }
+ return monitor._id ?? monitor.id ?? "";
+ };
const start = dayjs(form.startDate)
.set("hour", form.startTime.hour())
.set("minute", form.startTime.minute());
@@ -33,7 +39,7 @@ const useMaintenanceActions = () => {
const repeat = REPEAT_LOOKUP[form.repeat];
const submit = {
- monitors: form.monitors.map((monitor) => monitor._id),
+ monitors: form.monitors.map((monitor) => toStringId(monitor)).filter(Boolean),
name: form.name,
start: start.toISOString(),
end: end.toISOString(),
diff --git a/client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx b/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx
similarity index 88%
rename from client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx
rename to client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx
index d21906e41..4ab56bef9 100644
--- a/client/src/Pages/v1/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx
+++ b/client/src/Pages/Maintenance/CreateMaintenance/hooks/useMaintenanceData.jsx
@@ -1,4 +1,4 @@
-import { networkService } from "../../../../../main.jsx";
+import { networkService } from "../../../../main.jsx";
import dayjs from "dayjs";
import {
MS_PER_SECOND,
@@ -6,7 +6,7 @@ import {
MS_PER_HOUR,
MS_PER_DAY,
MS_PER_WEEK,
-} from "../../../../../Utils/timeUtils.js";
+} from "../../../../Utils/timeUtilsLegacy.js";
const useMaintenanceData = () => {
const REVERSE_REPEAT_LOOKUP = {
0: "none",
@@ -41,7 +41,7 @@ const useMaintenanceData = () => {
limit: null,
types: ["http", "ping", "pagespeed", "port"],
});
- const fetchedMonitors = response.data.data.monitors;
+ const fetchedMonitors = response.data.data;
return fetchedMonitors;
};
@@ -55,7 +55,9 @@ const useMaintenanceData = () => {
const endTime = dayjs(end);
const durationInMs = endTime.diff(startTime, "milliseconds").toString();
const { duration, durationUnit } = getDurationAndUnit(durationInMs);
- const monitor = monitorList.find((monitor) => monitor._id === monitorId);
+ const monitor = monitorList.find(
+ (monitor) => (monitor._id ?? monitor.id) === monitorId
+ );
const maintenanceWindowInformation = {
name,
repeat: REVERSE_REPEAT_LOOKUP[repeat],
diff --git a/client/src/Pages/v1/Maintenance/CreateMaintenance/index.css b/client/src/Pages/Maintenance/CreateMaintenance/index.css
similarity index 100%
rename from client/src/Pages/v1/Maintenance/CreateMaintenance/index.css
rename to client/src/Pages/Maintenance/CreateMaintenance/index.css
diff --git a/client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx b/client/src/Pages/Maintenance/CreateMaintenance/index.jsx
similarity index 89%
rename from client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx
rename to client/src/Pages/Maintenance/CreateMaintenance/index.jsx
index d0e7b03cd..af3f34512 100644
--- a/client/src/Pages/v1/Maintenance/CreateMaintenance/index.jsx
+++ b/client/src/Pages/Maintenance/CreateMaintenance/index.jsx
@@ -7,8 +7,8 @@ import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import { LocalizationProvider } from "@mui/x-date-pickers";
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
import { MobileTimePicker } from "@mui/x-date-pickers/MobileTimePicker";
-import { maintenanceWindowValidation } from "../../../../Validation/validation.js";
-import { createToast } from "../../../../Utils/toastUtils.jsx";
+import { maintenanceWindowValidation } from "@/Validation/validation.js";
+import { createToast } from "@/Utils/toastUtils.jsx";
import MonitorList from "./Components/MonitorList/index.jsx";
import ConfigSelect from "./Components/ConfigSelect/index.jsx";
import useMaintenanceData from "./hooks/useMaintenanceData.jsx";
@@ -16,13 +16,13 @@ import useMaintenanceActions from "./hooks/useMaintenanceActions.jsx";
import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
-import CalendarIcon from "../../../../assets/icons/calendar.svg?react";
+import Icon from "@/Components/v1/Icon";
import "./index.css";
import Search from "@/Components/v1/Inputs/Search/index.jsx";
-import { logger } from "../../../../Utils/Logger.js";
+import { logger } from "@/Utils/Logger.js";
import { useNavigate, useParams } from "react-router-dom";
-import { buildErrors, hasValidationErrors } from "../../../../Validation/error.js";
+import { buildErrors, hasValidationErrors } from "@/Validation/error.js";
import { useTranslation } from "react-i18next";
import dayjs from "dayjs";
@@ -108,6 +108,7 @@ const CreateMaintenance = () => {
const handleSubmit = async () => {
if (hasValidationErrors(form, maintenanceWindowValidation, setErrors)) return;
+
const request = handleSubmitForm(maintenanceWindowId, form);
try {
setIsLoading(true);
@@ -216,10 +217,24 @@ const CreateMaintenance = () => {
disablePast
disableHighlightToday
value={form.startDate}
- slots={{ openPickerIcon: () => }}
+ slots={{
+ openPickerIcon: () => (
+
+ ),
+ }}
slotProps={{
switchViewButton: { sx: { display: "none" } },
nextIconButton: { sx: { ml: theme.spacing(2) } },
+ day: {
+ sx: {
+ "&.MuiPickersDay-root.Mui-disabled": {
+ color: theme.palette.primary.contrastTextTertiary,
+ },
+ },
+ },
field: {
sx: {
width: "fit-content",
@@ -253,30 +268,6 @@ const CreateMaintenance = () => {
"&:hover": { backgroundColor: "transparent" },
},
},
- // CAIO_REVIEW, entire popper
- popper: {
- sx: {
- "& .MuiPickersDay-root": {
- color: theme.palette.primary.contrastText,
- "&.Mui-selected": {
- backgroundColor: theme.palette.accent.main, // Selected day background
- color: theme.palette.primary.contrastText, // Selected day text color
- },
- "&:hover": {
- backgroundColor: theme.palette.accent.light, // Hover background
- },
- "&.Mui-disabled": {
- color: theme.palette.secondary.main, // Disabled day color
- },
- },
- "& .MuiDayCalendar-weekDayLabel": {
- color: theme.palette.primary.contrastText,
- },
- "& .MuiPickersCalendarHeader-label": {
- color: theme.palette.primary.contrastText,
- },
- },
- },
}}
sx={{}}
onChange={(newDate) => {
diff --git a/client/src/Pages/v1/Maintenance/MaintenanceTable/ActionsMenu/index.jsx b/client/src/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx
similarity index 89%
rename from client/src/Pages/v1/Maintenance/MaintenanceTable/ActionsMenu/index.jsx
rename to client/src/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx
index b2654da49..709bca212 100644
--- a/client/src/Pages/v1/Maintenance/MaintenanceTable/ActionsMenu/index.jsx
+++ b/client/src/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx
@@ -3,11 +3,11 @@ import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router-dom";
import { useSelector } from "react-redux";
import { IconButton, Menu, MenuItem } from "@mui/material";
-import { logger } from "../../../../../Utils/Logger.js";
-import Settings from "../../../../../assets/icons/settings-bold.svg?react";
+import { logger } from "@/Utils/Logger.js";
+import Icon from "@/Components/v1/Icon";
import PropTypes from "prop-types";
-import { networkService } from "../../../../../main.jsx";
-import { createToast } from "../../../../../Utils/toastUtils.jsx";
+import { networkService } from "../../../../main.jsx";
+import { createToast } from "@/Utils/toastUtils.jsx";
import { useTranslation } from "react-i18next";
import Dialog from "@/Components/v1/Dialog/index.jsx";
@@ -27,7 +27,7 @@ const ActionsMenu = ({ /* isAdmin, */ maintenanceWindow, updateCallback }) => {
try {
setIsLoading(true);
await networkService.deleteMaintenanceWindow({
- maintenanceWindowId: maintenanceWindow._id,
+ maintenanceWindowId: maintenanceWindow.id,
});
updateCallback();
createToast({ body: "Maintenance window deleted successfully." });
@@ -47,7 +47,7 @@ const ActionsMenu = ({ /* isAdmin, */ maintenanceWindow, updateCallback }) => {
active: !maintenanceWindow.active,
};
await networkService.editMaintenanceWindow({
- maintenanceWindowId: maintenanceWindow._id,
+ maintenanceWindowId: maintenanceWindow.id,
maintenanceWindow: data,
});
updateCallback();
@@ -60,7 +60,7 @@ const ActionsMenu = ({ /* isAdmin, */ maintenanceWindow, updateCallback }) => {
};
const handleEdit = () => {
- navigate(`/maintenance/create/${maintenanceWindow._id}`);
+ navigate(`/maintenance/create/${maintenanceWindow.id}`);
};
const openMenu = (event) => {
@@ -98,7 +98,10 @@ const ActionsMenu = ({ /* isAdmin, */ maintenanceWindow, updateCallback }) => {
},
}}
>
-
+
{sort.order === "asc" ? (
-
+
) : (
-
+
)}
@@ -89,9 +94,15 @@ const MaintenanceTable = ({
}}
>
{sort.order === "asc" ? (
-
+
) : (
-
+
)}
@@ -192,7 +203,7 @@ const MaintenanceTable = ({
},
},
onRowClick: (row) => {
- navigate(`/maintenance/create/${row._id}`);
+ navigate(`/maintenance/create/${row.id}`);
},
}}
headers={headers}
diff --git a/client/src/Pages/v1/Maintenance/index.css b/client/src/Pages/Maintenance/index.css
similarity index 100%
rename from client/src/Pages/v1/Maintenance/index.css
rename to client/src/Pages/Maintenance/index.css
diff --git a/client/src/Pages/v1/Maintenance/index.jsx b/client/src/Pages/Maintenance/index.jsx
similarity index 98%
rename from client/src/Pages/v1/Maintenance/index.jsx
rename to client/src/Pages/Maintenance/index.jsx
index cba0e9f18..b49cd9f6e 100644
--- a/client/src/Pages/v1/Maintenance/index.jsx
+++ b/client/src/Pages/Maintenance/index.jsx
@@ -4,7 +4,7 @@ import { useState, useEffect } from "react";
import "./index.css";
import MaintenanceTable from "./MaintenanceTable/index.jsx";
import { useSelector } from "react-redux";
-import { networkService } from "../../../main.jsx";
+import { networkService } from "../../main.jsx";
import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
diff --git a/client/src/Pages/v1/NotFound/index.jsx b/client/src/Pages/NotFound/index.jsx
similarity index 96%
rename from client/src/Pages/v1/NotFound/index.jsx
rename to client/src/Pages/NotFound/index.jsx
index 48825c9f6..27e3173b0 100644
--- a/client/src/Pages/v1/NotFound/index.jsx
+++ b/client/src/Pages/NotFound/index.jsx
@@ -1,5 +1,5 @@
import PropTypes from "prop-types";
-import NotFoundSvg from "../../../assets/Images/sushi_404.svg";
+import NotFoundSvg from "@/assets/Images/sushi_404.svg";
import { Button, Stack, Typography } from "@mui/material";
import { useNavigate } from "react-router-dom";
import { useTheme } from "@emotion/react";
diff --git a/client/src/Pages/v1/Notifications/components/ActionMenu.jsx b/client/src/Pages/Notifications/components/ActionMenu.jsx
similarity index 90%
rename from client/src/Pages/v1/Notifications/components/ActionMenu.jsx
rename to client/src/Pages/Notifications/components/ActionMenu.jsx
index d57ad931f..a3ccc4bbb 100644
--- a/client/src/Pages/v1/Notifications/components/ActionMenu.jsx
+++ b/client/src/Pages/Notifications/components/ActionMenu.jsx
@@ -1,7 +1,7 @@
// Components
import Menu from "@mui/material/Menu";
import IconButton from "@mui/material/IconButton";
-import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined";
+import Icon from "@/Components/v1/Icon";
import MenuItem from "@mui/material/MenuItem";
// Utils
@@ -32,13 +32,13 @@ const ActionMenu = ({ notification, onDelete }) => {
const handleRemove = (e) => {
e.stopPropagation();
- onDelete(notification._id);
+ onDelete(notification.id);
handleClose();
};
const handleConfigure = (e) => {
e.stopPropagation();
- navigate(`/notifications/${notification._id}`);
+ navigate(`/notifications/${notification.id}`);
handleClose();
};
@@ -49,7 +49,10 @@ const ActionMenu = ({ notification, onDelete }) => {
onClick={handleClick}
onMouseDown={(e) => e.stopPropagation()}
>
-
+
{
const [notification, setNotification] = useState({
notificationName: "",
address: "",
- type: NOTIFICATION_TYPES[0]._id,
+ type: NOTIFICATION_TYPES[0].id,
});
const [errors, setErrors] = useState({});
const { t } = useTranslation();
@@ -66,7 +66,7 @@ const CreateNotifications = () => {
const [isOpen, setIsOpen] = useState(false);
const getNotificationTypeValue = (typeId) => {
- return NOTIFICATION_TYPES.find((type) => type._id === typeId)?.value || "email";
+ return NOTIFICATION_TYPES.find((type) => type.id === typeId)?.value || "email";
};
const extractError = (error, field) =>
diff --git a/client/src/Pages/v1/Notifications/index.jsx b/client/src/Pages/Notifications/index.jsx
similarity index 96%
rename from client/src/Pages/v1/Notifications/index.jsx
rename to client/src/Pages/Notifications/index.jsx
index 78ec6e9bb..70ed3ef96 100644
--- a/client/src/Pages/v1/Notifications/index.jsx
+++ b/client/src/Pages/Notifications/index.jsx
@@ -14,7 +14,7 @@ import { useNavigate } from "react-router-dom";
import {
useGetNotificationsByTeamId,
useDeleteNotification,
-} from "../../../Hooks/v1/useNotifications.js";
+} from "../../Hooks/useNotifications.js";
import { useTranslation } from "react-i18next";
const Notifications = () => {
@@ -100,7 +100,7 @@ const Notifications = () => {
{t("notifications.createTitle")}
navigate(`/notifications/${row._id}`),
+ onRowClick: (row) => navigate(`/notifications/${row.id}`),
rowSX: {
cursor: "pointer",
"&:hover td": {
diff --git a/client/src/Pages/v1/Notifications/utils.js b/client/src/Pages/Notifications/utils.js
similarity index 85%
rename from client/src/Pages/v1/Notifications/utils.js
rename to client/src/Pages/Notifications/utils.js
index 2a3d32f4d..9ad422101 100644
--- a/client/src/Pages/v1/Notifications/utils.js
+++ b/client/src/Pages/Notifications/utils.js
@@ -1,10 +1,10 @@
export const NOTIFICATION_TYPES = [
- { _id: 1, name: "E-mail", value: "email" },
- { _id: 2, name: "Slack", value: "slack" },
- { _id: 3, name: "PagerDuty", value: "pager_duty" },
- { _id: 4, name: "Webhook", value: "webhook" },
- { _id: 5, name: "Discord", value: "discord" },
- { _id: 6, name: "Matrix", value: "matrix" },
+ { id: 1, name: "E-mail", value: "email" },
+ { id: 2, name: "Slack", value: "slack" },
+ { id: 3, name: "PagerDuty", value: "pager_duty" },
+ { id: 4, name: "Webhook", value: "webhook" },
+ { id: 5, name: "Discord", value: "discord" },
+ { id: 6, name: "Matrix", value: "matrix" },
];
export const TITLE_MAP = {
diff --git a/client/src/Pages/v1/PageSpeed/Create/index.css b/client/src/Pages/PageSpeed/Create/index.css
similarity index 100%
rename from client/src/Pages/v1/PageSpeed/Create/index.css
rename to client/src/Pages/PageSpeed/Create/index.css
diff --git a/client/src/Pages/v1/PageSpeed/Create/index.jsx b/client/src/Pages/PageSpeed/Create/index.jsx
similarity index 94%
rename from client/src/Pages/v1/PageSpeed/Create/index.jsx
rename to client/src/Pages/PageSpeed/Create/index.jsx
index cb856bd37..6a786eb00 100644
--- a/client/src/Pages/v1/PageSpeed/Create/index.jsx
+++ b/client/src/Pages/PageSpeed/Create/index.jsx
@@ -3,10 +3,9 @@ import { Box, Stack, Tooltip, Typography, Button, ButtonGroup } from "@mui/mater
import ConfigBox from "@/Components/v1/ConfigBox/index.jsx";
import Select from "@/Components/v1/Inputs/Select/index.jsx";
import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
-import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline";
+import Icon from "@/Components/v1/Icon";
import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
import PulseDot from "@/Components/v1/Animated/PulseDot.jsx";
-import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded";
import SkeletonLayout from "./skeleton.jsx";
import NotificationsConfig from "@/Components/v1/NotificationConfig/index.jsx";
import Dialog from "@/Components/v1/Dialog/index.jsx";
@@ -16,22 +15,22 @@ import Radio from "@/Components/v1/Inputs/Radio/index.jsx";
// Utils
import { useState, useEffect } from "react";
import { useSelector } from "react-redux";
-import { monitorValidation } from "../../../../Validation/validation.js";
-import { parseDomainName } from "../../../../Utils/monitorUtils.js";
+import { monitorValidation } from "../../../Validation/validation.js";
+import { parseDomainName } from "../../../Utils/monitorUtilsLegacy.js";
import { useTranslation } from "react-i18next";
-import { useGetNotificationsByTeamId } from "../../../../Hooks/v1/useNotifications.js";
+import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications.js";
import { useTheme } from "@emotion/react";
-import { createToast } from "../../../../Utils/toastUtils.jsx";
+import { createToast } from "../../../Utils/toastUtils.jsx";
import { useParams } from "react-router";
-import { useMonitorUtils } from "../../../../Hooks/v1/useMonitorUtils.js";
+import { useMonitorUtils } from "../../../Hooks/useMonitorUtils.js";
import {
useCreateMonitor,
useFetchMonitorById,
useDeleteMonitor,
useUpdateMonitor,
usePauseMonitor,
-} from "../../../../Hooks/v1/monitorHooks.js";
+} from "../../../Hooks/monitorHooks.js";
const PageSpeedSetup = () => {
const { monitorId } = useParams();
@@ -178,7 +177,7 @@ const PageSpeedSetup = () => {
const handleRemove = async (event) => {
event.preventDefault();
- await deleteMonitor({ monitor, redirect: "/pagespeed" });
+ await deleteMonitor({ monitor: { id: monitorId }, redirect: "/pagespeed" });
};
const isBusy = isLoading || isCreating || isDeleting || isUpdating || isPausing;
@@ -313,12 +312,18 @@ const PageSpeedSetup = () => {
>
{monitor?.isActive ? (
<>
-
+
{t("pause")}
>
) : (
<>
-
+
{t("resume")}
>
)}
diff --git a/client/src/Pages/v1/PageSpeed/Create/skeleton.jsx b/client/src/Pages/PageSpeed/Create/skeleton.jsx
similarity index 100%
rename from client/src/Pages/v1/PageSpeed/Create/skeleton.jsx
rename to client/src/Pages/PageSpeed/Create/skeleton.jsx
diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChart.jsx b/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx
similarity index 99%
rename from client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChart.jsx
rename to client/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx
index 5c79cc06c..d8c118237 100644
--- a/client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChart.jsx
+++ b/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx
@@ -11,7 +11,7 @@ import {
import { useTheme } from "@emotion/react";
import { useMemo, useState } from "react";
import { Box, Stack, Typography } from "@mui/material";
-import { formatDateWithTz } from "../../../../../../Utils/timeUtils.js";
+import { formatDateWithTz } from "../../../../../Utils/timeUtilsLegacy.js";
import { useSelector } from "react-redux";
const config = {
diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx b/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx
similarity index 92%
rename from client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx
rename to client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx
index c0da1600f..525e4d0d2 100644
--- a/client/src/Pages/v1/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx
+++ b/client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx
@@ -1,6 +1,6 @@
import { Box, Typography, Divider } from "@mui/material";
import Checkbox from "@/Components/v1/Inputs/Checkbox/index.jsx";
-import MetricsIcon from "../../../../../../assets/icons/ruler-icon.svg?react";
+import Icon from "@/Components/v1/Icon";
import LegendBox from "@/Components/v1/Charts/LegendBox/index.jsx";
import { useTranslation } from "react-i18next";
import { useTheme } from "@emotion/react";
@@ -10,7 +10,12 @@ const AreaChartLegend = ({ metrics, handleMetrics }) => {
const { t } = useTranslation();
return (
}
+ icon={
+
+ }
header="Metrics"
>
diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChart.jsx b/client/src/Pages/PageSpeed/Details/Components/Charts/PieChart.jsx
similarity index 100%
rename from client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChart.jsx
rename to client/src/Pages/PageSpeed/Details/Components/Charts/PieChart.jsx
diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChartLegend.jsx b/client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx
similarity index 94%
rename from client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChartLegend.jsx
rename to client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx
index 1297bd699..5cfdad608 100644
--- a/client/src/Pages/v1/PageSpeed/Details/Components/Charts/PieChartLegend.jsx
+++ b/client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx
@@ -1,7 +1,7 @@
import { Stack, Box, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import LegendBox from "@/Components/v1/Charts/LegendBox/index.jsx";
-import SpeedometerIcon from "../../../../../../assets/icons/speedometer-icon.svg?react";
+import Icon from "@/Components/v1/Icon";
import PropTypes from "prop-types";
const PieChartLegend = ({ audits }) => {
@@ -9,7 +9,12 @@ const PieChartLegend = ({ audits }) => {
return (
}
+ icon={
+
+ }
header="Performance metrics"
sx={{ flex: 1 }}
>
diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx b/client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx
similarity index 91%
rename from client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx
rename to client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx
index 1281a396e..9020ddd6d 100644
--- a/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx
+++ b/client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx
@@ -2,7 +2,7 @@ import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx";
import AreaChart from "../Charts/AreaChart.jsx";
import AreaChartLegend from "../Charts/AreaChartLegend.jsx";
import SkeletonLayout from "./skeleton.jsx";
-import ScoreIcon from "../../../../../../assets/icons/monitor-graph-line.svg?react";
+import Icon from "@/Components/v1/Icon";
import { Stack } from "@mui/material";
import PropTypes from "prop-types";
@@ -24,7 +24,12 @@ const PageSpeedAreaChart = ({ shouldRender, monitor, metrics, handleMetrics }) =
>
}
+ icon={
+
+ }
header="Score history"
height="100%"
borderRadiusRight={16}
diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx b/client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx
similarity index 100%
rename from client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx
rename to client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx
diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx b/client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx
similarity index 50%
rename from client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx
rename to client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx
index ff265317c..6ff26c739 100644
--- a/client/src/Pages/v1/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx
+++ b/client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx
@@ -1,12 +1,24 @@
import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx";
import StatBox from "@/Components/v1/StatBox/index.jsx";
import { Typography } from "@mui/material";
-import { getHumanReadableDuration } from "../../../../../../Utils/timeUtils.js";
+import { getHumanReadableDuration } from "../../../../../Utils/timeUtilsLegacy.js";
import { useTranslation } from "react-i18next";
-const PageSpeedStatusBoxes = ({ shouldRender, monitor }) => {
- const uptimeDuration = getHumanReadableDuration(monitor?.uptimeDuration);
- const time = getHumanReadableDuration(monitor?.lastChecked);
+const PageSpeedStatusBoxes = ({ shouldRender, monitorStats }) => {
+ const lastChecked = monitorStats?.lastCheckTimestamp
+ ? Date.now() - monitorStats.lastCheckTimestamp
+ : 0;
+
+ // Determine time since last failure
+ const timeOfLastFailure = monitorStats?.timeOfLastFailure;
+ const timeSinceLastFailure =
+ timeOfLastFailure > 0
+ ? Date.now() - timeOfLastFailure
+ : Date.now() - new Date(monitorStats?.createdAt);
+
+ const streakTime = getHumanReadableDuration(timeSinceLastFailure);
+
+ const time = getHumanReadableDuration(lastChecked);
const { t } = useTranslation();
@@ -16,7 +28,7 @@ const PageSpeedStatusBoxes = ({ shouldRender, monitor }) => {
heading="checks since"
subHeading={
<>
- {uptimeDuration}
+ {streakTime}
{t("ago")}
>
}
diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/index.jsx b/client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx
similarity index 91%
rename from client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/index.jsx
rename to client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx
index 27f9e885b..a2e1b5f96 100644
--- a/client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/index.jsx
+++ b/client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx
@@ -1,5 +1,5 @@
import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx";
-import PerformanceIcon from "../../../../../../assets/icons/performance-report.svg?react";
+import Icon from "@/Components/v1/Icon";
import PieChart from "../Charts/PieChart.jsx";
import { Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
@@ -17,7 +17,12 @@ const PerformanceReport = ({ shouldRender, audits }) => {
return (
}
+ icon={
+
+ }
header="Performance report"
Legend={ }
borderRadiusRight={16}
diff --git a/client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx b/client/src/Pages/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx
similarity index 100%
rename from client/src/Pages/v1/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx
rename to client/src/Pages/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx
diff --git a/client/src/Pages/PageSpeed/Details/index.tsx b/client/src/Pages/PageSpeed/Details/index.tsx
new file mode 100644
index 000000000..f1e4df4ba
--- /dev/null
+++ b/client/src/Pages/PageSpeed/Details/index.tsx
@@ -0,0 +1,64 @@
+import { BasePage } from "@/Components/v2/design-elements";
+import type { PageSpeedDetailsResponse } from "@/Types/Monitor";
+import { HeaderMonitorControls } from "@/Components/v2/common";
+import Stack from "@mui/material/Stack";
+import {
+ HistogramPageSpeedDetails,
+ PiePageSpeed,
+ PiePageSpeedLegend,
+ MonitorStatBoxes,
+} from "@/Components/v2/monitors";
+
+import { useIsAdmin } from "@/Hooks/useIsAdmin";
+import { useGet } from "@/Hooks/UseApi";
+import { useParams } from "react-router-dom";
+import { useTheme } from "@mui/material";
+
+const PageSpeedDetails = () => {
+ const { monitorId } = useParams();
+ const isAdmin = useIsAdmin();
+ const theme = useTheme();
+ const {
+ data: monitorData,
+ isLoading,
+ error,
+ refetch,
+ } = useGet(
+ monitorId ? `/monitors/pagespeed/details/${monitorId}?dateRange=day` : null,
+ {},
+ { keepPreviousData: true, refreshInterval: 30000 }
+ );
+
+ const monitor = monitorData?.monitor;
+ const monitorStats = monitorData?.monitorStats || null;
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
+export default PageSpeedDetails;
diff --git a/client/src/Pages/v1/PageSpeed/Details/index.jsx b/client/src/Pages/PageSpeed/Details/old.jsx
similarity index 88%
rename from client/src/Pages/v1/PageSpeed/Details/index.jsx
rename to client/src/Pages/PageSpeed/Details/old.jsx
index 836f0ca37..3190787b3 100644
--- a/client/src/Pages/v1/PageSpeed/Details/index.jsx
+++ b/client/src/Pages/PageSpeed/Details/old.jsx
@@ -9,16 +9,15 @@ import PerformanceReport from "./Components/PerformanceReport/index.jsx";
import GenericFallback from "@/Components/v1/GenericFallback/index.jsx";
// Utils
import { useTheme } from "@emotion/react";
-import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js";
+import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
import { useParams } from "react-router-dom";
-import { useFetchStatsByMonitorId } from "../../../../Hooks/v1/monitorHooks.js";
+import { useFetchPageSpeedMonitorById } from "../../../Hooks/monitorHooks.js";
import { useState } from "react";
import { useTranslation } from "react-i18next";
// Constants
const BREADCRUMBS = [
{ name: "pagespeed", path: "/pagespeed" },
{ name: "details", path: `` },
- // { name: "details", path: `/pagespeed/${monitorId}` }, // Not needed?
];
const PageSpeedDetails = () => {
@@ -36,13 +35,9 @@ const PageSpeedDetails = () => {
});
const [trigger, setTrigger] = useState(false);
// Network
- const [monitor, audits, isLoading, networkError] = useFetchStatsByMonitorId({
+ const [monitor, monitorStats, isLoading, networkError] = useFetchPageSpeedMonitorById({
monitorId,
- sortOrder: "desc",
- limit: 50,
dateRange: "day",
- numToDisplay: null,
- normalize: null,
updateTrigger: trigger,
});
@@ -100,7 +95,7 @@ const PageSpeedDetails = () => {
/>
{
/>
);
diff --git a/client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx b/client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx
similarity index 95%
rename from client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx
rename to client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx
index 4e551526b..4a85863c4 100644
--- a/client/src/Pages/v1/PageSpeed/Monitors/Components/Card/index.jsx
+++ b/client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx
@@ -1,17 +1,13 @@
import PropTypes from "prop-types";
-// import PageSpeedIcon from "../../../../assets/icons/page-speed.svg?react";
-import PageSpeedIcon from "../../../../../../assets/icons/page-speed.svg?react";
+import Icon from "@/Components/v1/Icon";
import { StatusLabel } from "@/Components/v1/Label/index.jsx";
import { Box, Grid, Stack, Typography } from "@mui/material";
import { useNavigate } from "react-router-dom";
import { useTheme } from "@emotion/react";
import { Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip } from "recharts";
import { useSelector } from "react-redux";
-import {
- formatDateWithTz,
- formatDurationSplit,
-} from "../../../../../../Utils/timeUtils.js";
-import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js";
+import { formatDateWithTz, formatDurationSplit } from "@/Utils/timeUtilsLegacy.js";
+import { useMonitorUtils } from "@/Hooks/useMonitorUtils.js";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import IconBox from "@/Components/v1/IconBox/index.jsx";
@@ -224,7 +220,7 @@ const Card = ({ monitor }) => {
navigate(`/pagespeed/${monitor._id}`)}
+ onClick={() => navigate(`/pagespeed/${monitor.id}`)}
border={1}
borderColor={theme.palette.primary.lowContrast}
borderRadius={theme.shape.borderRadius}
@@ -244,7 +240,10 @@ const Card = ({ monitor }) => {
}}
>
-
+
{
+const MonitorGrid = ({ monitors, shouldRender = true }) => {
+ if (!shouldRender) return null;
+
return (
{
{monitors?.map((monitor) => (
))}
diff --git a/client/src/Pages/PageSpeed/Monitors/Components/PageSpeedMonitorsTable.tsx b/client/src/Pages/PageSpeed/Monitors/Components/PageSpeedMonitorsTable.tsx
new file mode 100644
index 000000000..37093c7a2
--- /dev/null
+++ b/client/src/Pages/PageSpeed/Monitors/Components/PageSpeedMonitorsTable.tsx
@@ -0,0 +1,254 @@
+import Stack from "@mui/material/Stack";
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import { Table, Pagination, StatusLabel } from "@/Components/v2/design-elements";
+import { HistogramPageSpeed } from "@/Components/v2/monitors";
+import { ArrowDown, ArrowUp } from "lucide-react";
+import type { Header } from "@/Components/v2/design-elements/Table";
+import { ActionsMenu } from "@/Components/v2/actions-menu";
+
+import { useTranslation } from "react-i18next";
+import useMediaQuery from "@mui/material/useMediaQuery";
+import { useTheme } from "@mui/material/styles";
+import { useNavigate } from "react-router-dom";
+import { usePatch } from "@/Hooks/UseApi";
+
+import type { Monitor, MonitorWithChecks } from "@/Types/Monitor";
+import type { ActionMenuItem } from "@/Components/v2/actions-menu";
+
+export const PageSpeedMonitorsTable = ({
+ monitors,
+ refetch,
+ setSelectedMonitor,
+ sortField,
+ setSortField,
+ sortOrder,
+ setSortOrder,
+ count,
+ page,
+ setPage,
+ rowsPerPage,
+ setRowsPerPage,
+}: {
+ monitors: MonitorWithChecks[];
+ refetch: Function;
+ setSelectedMonitor: Function;
+ sortField: string;
+ setSortField: (field: string) => void;
+ sortOrder: "asc" | "desc";
+ setSortOrder: (order: "asc" | "desc") => void;
+ count: number;
+ page: number;
+ setPage: (page: number) => void;
+ rowsPerPage: number;
+ setRowsPerPage: (rowsPerPage: number) => void;
+}) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const isSmall = useMediaQuery(theme.breakpoints.down("md"));
+ const navigate = useNavigate();
+ const {
+ patch,
+ // loading: isPatching,
+ // error: postError,
+ } = usePatch();
+
+ const handlePageChange = (
+ _e: React.MouseEvent | null,
+ newPage: number
+ ) => {
+ setPage(newPage);
+ };
+
+ const handleRowsPerPageChange = (
+ e: React.ChangeEvent
+ ) => {
+ const value = Number(e.target.value);
+ setPage(0);
+ setRowsPerPage(value);
+ };
+
+ const handleSort = (e: any, field: string) => {
+ e.preventDefault();
+ e.stopPropagation();
+ if (sortField === field) {
+ const newOrder = sortOrder === "asc" ? "desc" : "asc";
+ setSortOrder(newOrder);
+ } else {
+ setSortField(field);
+ setSortOrder("asc");
+ }
+ refetch();
+ };
+
+ const getActions = (monitor: Monitor): ActionMenuItem[] => {
+ return [
+ {
+ id: 1,
+ label: t("pages.common.monitors.actions.openSite"),
+ action: () => {
+ window.open(monitor.url, "_blank", "noreferrer");
+ },
+ closeMenu: true,
+ },
+ {
+ id: 2,
+ label: t("pages.common.monitors.actions.details"),
+ action: () => {
+ navigate(`${monitor.id}`);
+ },
+ },
+ {
+ id: 3,
+ label: t("pages.common.monitors.actions.incidents"),
+ action: () => {
+ navigate(`/incidents?monitorId=${monitor.id}`);
+ },
+ },
+ {
+ id: 4,
+ label: t("pages.common.monitors.actions.configure"),
+ action: () => {
+ navigate(`/pagespeed/configure/${monitor.id}`);
+ },
+ },
+ // {
+ // id: 5,
+ // label: "Clone",
+ // action: () => {
+
+ // },
+ // },
+ {
+ id: 6,
+ label:
+ monitor.isActive === false
+ ? t("common.buttons.resume")
+ : t("common.buttons.pause"),
+ action: async () => {
+ await patch(`/monitors/${monitor.id}/active`);
+ refetch();
+ },
+ closeMenu: true,
+ },
+ {
+ id: 7,
+ label: (
+
+ {t("common.buttons.delete")}
+
+ ),
+ action: () => {
+ setSelectedMonitor(monitor);
+ },
+ closeMenu: true,
+ },
+ ];
+ };
+
+ const getHeaders = () => {
+ const renderSortIcon = (isActive: boolean) => (
+
+ {isActive ? (
+ sortOrder === "asc" ? (
+
+ ) : (
+
+ )
+ ) : null}
+
+ );
+
+ const headers: Header[] = [
+ {
+ id: "name",
+ content: (
+ handleSort(e, "name")}
+ sx={{ cursor: "pointer" }}
+ >
+ {t("common.table.headers.name")}
+ {renderSortIcon(sortField === "name")}
+
+ ),
+ render: (row) => {
+ return row.name;
+ },
+ },
+ {
+ id: "status",
+ content: (
+ handleSort(e, "status")}
+ sx={{ cursor: "pointer" }}
+ >
+ {t("common.table.headers.status")}
+ {renderSortIcon(sortField === "status")}
+
+ ),
+ render: (row) => {
+ return ;
+ },
+ },
+ {
+ id: "histogram",
+ content: t("pages.pageSpeed.table.headers.pageSpeedScore"),
+ render: (row) => {
+ return (
+
+
+
+ );
+ },
+ },
+
+ {
+ id: "actions",
+ content: t("common.table.headers.actions"),
+ render: (row) => {
+ return ;
+ },
+ },
+ ];
+ return headers;
+ };
+
+ let headers = getHeaders();
+
+ if (isSmall) {
+ headers = headers.filter((h) => h.id !== "histogram");
+ }
+ return (
+
+ {
+ navigate(`/pagespeed/${row.id}`);
+ }}
+ />
+
+
+ );
+};
diff --git a/client/src/Pages/PageSpeed/Monitors/index.tsx b/client/src/Pages/PageSpeed/Monitors/index.tsx
new file mode 100644
index 000000000..887ec02c0
--- /dev/null
+++ b/client/src/Pages/PageSpeed/Monitors/index.tsx
@@ -0,0 +1,117 @@
+import {
+ MonitorBasePageWithStates,
+ UpStatusBox,
+ DownStatusBox,
+ PausedStatusBox,
+ PageSpeedKeyPriorityFallback,
+} from "@/Components/v2/design-elements";
+import { Dialog } from "@/Components/v2/inputs";
+import { HeaderCreate } from "@/Components/v2/common";
+import Stack from "@mui/material/Stack";
+import { PageSpeedMonitorsTable } from "@/Pages/PageSpeed/Monitors/Components/PageSpeedMonitorsTable";
+import type { Monitor } from "@/Types/Monitor";
+
+import { useTheme } from "@mui/material";
+import { useTranslation } from "react-i18next";
+import { useState } from "react";
+import { useIsAdmin } from "@/Hooks/useIsAdmin";
+import { useGet, useDelete } from "@/Hooks/UseApi";
+import type { MonitorsWithChecksResponse } from "@/Types/Monitor";
+import type { AppSettingsResponse } from "@/Types/Settings";
+
+const PageSpeedMonitorsPage = () => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const isAdmin = useIsAdmin();
+ const { deleteFn, loading: isDeleting } = useDelete();
+
+ const monitorsUrl = "/monitors/team/with-checks?type=pagespeed&limit=10";
+ const {
+ data: monitorsData,
+ isLoading: monitorsIsLoading,
+ error: monitorsError,
+ refetch,
+ } = useGet(monitorsUrl, {}, { refreshInterval: 30000 });
+
+ const settingsUrl = "/settings";
+ const {
+ data: settingsData,
+ isLoading: settingsIsLoading,
+ error: settingsError,
+ } = useGet(settingsUrl);
+
+ const [selectedMonitor, setSelectedMonitor] = useState(null);
+ const isDialogOpen = Boolean(selectedMonitor);
+ const [sortField, setSortField] = useState("name");
+ const [sortOrder, setSortOrder] = useState<"asc" | "desc">("asc");
+ const [page, setPage] = useState(0);
+ const [rowsPerPage, setRowsPerPage] = useState(10);
+
+ const monitors = monitorsData?.monitors;
+ const monitorsCount = monitorsData?.count;
+ const summary = monitorsData?.summary;
+
+ const isLoading = monitorsIsLoading || settingsIsLoading;
+
+ const showApiKeyWarning = isAdmin && settingsData && !settingsData.pagespeedKeySet;
+
+ const handleConfirm = async () => {
+ if (!selectedMonitor) return;
+ await deleteFn(`/monitors/${selectedMonitor.id}`);
+ setSelectedMonitor(null);
+ refetch();
+ };
+
+ const handleCancel = () => {
+ setSelectedMonitor(null);
+ };
+
+ return (
+ : undefined}
+ >
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default PageSpeedMonitorsPage;
diff --git a/client/src/Pages/v1/PageSpeed/Monitors/index.jsx b/client/src/Pages/PageSpeed/Monitors/old.jsx
similarity index 70%
rename from client/src/Pages/v1/PageSpeed/Monitors/index.jsx
rename to client/src/Pages/PageSpeed/Monitors/old.jsx
index 7485a72c9..f1bfcd0dd 100644
--- a/client/src/Pages/v1/PageSpeed/Monitors/index.jsx
+++ b/client/src/Pages/PageSpeed/Monitors/old.jsx
@@ -10,9 +10,9 @@ import FallbackPageSpeedWarning from "@/Components/v1/Fallback/FallbackPageSpeed
// Utils
import { useTheme } from "@emotion/react";
-import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js";
-import { useFetchMonitorsByTeamId } from "../../../../Hooks/v1/monitorHooks.js";
-import { useFetchSettings } from "../../../../Hooks/v1/settingsHooks.js";
+import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
+import { useFetchMonitorsWithChecks } from "@/Hooks/monitorHooks.js";
+import { useFetchSettings } from "@/Hooks/settingsHooks.js";
// Constants
const BREADCRUMBS = [{ name: `pagespeed`, path: "/pagespeed" }];
const TYPES = ["pagespeed"];
@@ -20,14 +20,21 @@ const PageSpeed = () => {
const theme = useTheme();
const isAdmin = useIsAdmin();
- const [monitors, monitorsSummary, isLoading, networkError] = useFetchMonitorsByTeamId({
- limit: 10,
+ const [
+ summary,
+ monitorsWithChecks,
+ monitorsWithChecksCount,
+ monitorsWithChecksIsLoading,
+ monitorsWithChecksNetworkError,
+ ] = useFetchMonitorsWithChecks({
types: TYPES,
+ limit: 10,
page: null,
rowsPerPage: null,
filter: null,
field: null,
order: null,
+ monitorUpdateTrigger: null,
});
const [settingsData, setSettingsData] = useState(undefined);
@@ -40,9 +47,9 @@ const PageSpeed = () => {
return (
<>
{
diff --git a/client/src/Pages/v1/ServerUnreachable.jsx b/client/src/Pages/ServerUnreachable.jsx
similarity index 92%
rename from client/src/Pages/v1/ServerUnreachable.jsx
rename to client/src/Pages/ServerUnreachable.jsx
index 8230f473b..bfbbfa90a 100644
--- a/client/src/Pages/v1/ServerUnreachable.jsx
+++ b/client/src/Pages/ServerUnreachable.jsx
@@ -2,14 +2,14 @@ import React, { useState } from "react";
import { Box, Typography, Button, Stack } from "@mui/material";
import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router-dom";
-import { networkService } from "../../Utils/NetworkService.js";
+import { networkService } from "@/Utils/NetworkService.js";
import Alert from "@/Components/v1/Alert/index.jsx";
-import { createToast } from "../../Utils/toastUtils.jsx";
+import { createToast } from "@/Utils/toastUtils.jsx";
import { useTranslation } from "react-i18next";
-import Background from "../../assets/Images/background-grid.svg?react";
-import Logo from "../../assets/icons/checkmate-icon.svg?react";
+import Background from "@/assets/Images/background-grid.svg?react";
+import Logo from "@/assets/icons/checkmate-icon.svg?react";
import ThemeSwitch from "@/Components/v1/ThemeSwitch/index.jsx";
-import LanguageSelector from "../../Components/LanguageSelector.jsx";
+import LanguageSelector from "@/Components/LanguageSelector.jsx";
const ServerUnreachable = () => {
const theme = useTheme();
diff --git a/client/src/Pages/Settings/DummyChart.tsx b/client/src/Pages/Settings/DummyChart.tsx
new file mode 100644
index 000000000..39323522e
--- /dev/null
+++ b/client/src/Pages/Settings/DummyChart.tsx
@@ -0,0 +1,59 @@
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import { useTheme } from "@mui/material/styles";
+import { HeatmapResponseTime, HistogramResponseTime } from "@/Components/v2/common";
+import type { CheckSnapshot } from "@/Types/Check";
+
+interface DummyChartProps {
+ chartType: string;
+}
+
+const generateDummyChecks = (): CheckSnapshot[] => {
+ const checks: CheckSnapshot[] = [];
+ for (let i = 0; i < 25; i++) {
+ const isUp = Math.random() > 0.1;
+ checks.push({
+ id: `dummy-${i}`,
+ status: isUp,
+ statusCode: isUp ? 200 : 500,
+ responseTime: Math.floor(Math.random() * 80) + 20,
+ message: "",
+ createdAt: new Date(Date.now() - i * 60000).toISOString(),
+ });
+ }
+ return checks;
+};
+
+const dummyChecks = generateDummyChecks();
+
+const DummyChart = ({ chartType }: DummyChartProps) => {
+ const theme = useTheme();
+
+ return (
+
+
+ Preview
+
+ {chartType === "histogram" ? (
+
+ ) : (
+
+ )}
+
+ );
+};
+
+export default DummyChart;
diff --git a/client/src/Pages/v1/Settings/SettingsAbout.jsx b/client/src/Pages/Settings/SettingsAbout.jsx
similarity index 100%
rename from client/src/Pages/v1/Settings/SettingsAbout.jsx
rename to client/src/Pages/Settings/SettingsAbout.jsx
diff --git a/client/src/Pages/v1/Settings/SettingsDemoMonitors.jsx b/client/src/Pages/Settings/SettingsDemoMonitors.jsx
similarity index 100%
rename from client/src/Pages/v1/Settings/SettingsDemoMonitors.jsx
rename to client/src/Pages/Settings/SettingsDemoMonitors.jsx
diff --git a/client/src/Pages/v1/Settings/SettingsEmail.jsx b/client/src/Pages/Settings/SettingsEmail.jsx
similarity index 98%
rename from client/src/Pages/v1/Settings/SettingsEmail.jsx
rename to client/src/Pages/Settings/SettingsEmail.jsx
index 7b5ad175e..6f0b02137 100644
--- a/client/src/Pages/v1/Settings/SettingsEmail.jsx
+++ b/client/src/Pages/Settings/SettingsEmail.jsx
@@ -12,8 +12,8 @@ import { PropTypes } from "prop-types";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx";
-import { useSendTestEmail } from "../../../Hooks/v1/useSendTestEmail.js";
-import { createToast } from "../../../Utils/toastUtils.jsx";
+import { useSendTestEmail } from "@/Hooks/useSendTestEmail.js";
+import { createToast } from "@/Utils/toastUtils.jsx";
const SettingsEmail = ({
isAdmin,
diff --git a/client/src/Pages/Settings/SettingsExport.jsx b/client/src/Pages/Settings/SettingsExport.jsx
new file mode 100644
index 000000000..fe04697f4
--- /dev/null
+++ b/client/src/Pages/Settings/SettingsExport.jsx
@@ -0,0 +1,65 @@
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import Button from "@mui/material/Button";
+import ConfigBox from "@/Components/v1/ConfigBox/index.jsx";
+// Utils
+import { useTheme } from "@emotion/react";
+import { PropTypes } from "prop-types";
+import { useTranslation } from "react-i18next";
+import Dialog from "@/Components/v1/Dialog/index.jsx";
+import { useState } from "react";
+
+const SettingsDemoMonitors = ({ isAdmin, HEADER_SX, handleChange, isLoading }) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ // Local state
+ const [isOpen, setIsOpen] = useState(false);
+
+ if (!isAdmin) {
+ return null;
+ }
+
+ return (
+ <>
+
+
+
+ Export monitors to JSON
+
+
+ Export your monitors data as a JSON file for backup or transfer.
+
+
+
+ {
+ const syntheticEvent = {
+ target: {
+ name: "export",
+ },
+ };
+ handleChange(syntheticEvent);
+ }}
+ sx={{ mt: theme.spacing(4) }}
+ >
+ Export Monitors to JSON
+
+
+
+ >
+ );
+};
+
+SettingsDemoMonitors.propTypes = {
+ isAdmin: PropTypes.bool,
+ handleChange: PropTypes.func,
+ HEADER_SX: PropTypes.object,
+};
+
+export default SettingsDemoMonitors;
diff --git a/client/src/Pages/v1/Settings/SettingsGlobalThresholds.jsx b/client/src/Pages/Settings/SettingsGlobalThresholds.jsx
similarity index 100%
rename from client/src/Pages/v1/Settings/SettingsGlobalThresholds.jsx
rename to client/src/Pages/Settings/SettingsGlobalThresholds.jsx
diff --git a/client/src/Pages/v1/Settings/SettingsPagespeed.jsx b/client/src/Pages/Settings/SettingsPagespeed.jsx
similarity index 100%
rename from client/src/Pages/v1/Settings/SettingsPagespeed.jsx
rename to client/src/Pages/Settings/SettingsPagespeed.jsx
diff --git a/client/src/Pages/v1/Settings/SettingsStats.jsx b/client/src/Pages/Settings/SettingsStats.jsx
similarity index 86%
rename from client/src/Pages/v1/Settings/SettingsStats.jsx
rename to client/src/Pages/Settings/SettingsStats.jsx
index b68006ab2..690b306be 100644
--- a/client/src/Pages/v1/Settings/SettingsStats.jsx
+++ b/client/src/Pages/Settings/SettingsStats.jsx
@@ -36,16 +36,6 @@ const SettingsStats = ({ isAdmin, HEADING_SX, handleChange, settingsData, errors
-
{t("settingsPage.statsSettings.clearAllStatsDescription")}
diff --git a/client/src/Pages/v1/Settings/SettingsTimeZone.jsx b/client/src/Pages/Settings/SettingsTimeZone.jsx
similarity index 97%
rename from client/src/Pages/v1/Settings/SettingsTimeZone.jsx
rename to client/src/Pages/Settings/SettingsTimeZone.jsx
index f684c871d..8ed7e5b3b 100644
--- a/client/src/Pages/v1/Settings/SettingsTimeZone.jsx
+++ b/client/src/Pages/Settings/SettingsTimeZone.jsx
@@ -3,7 +3,7 @@ import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import ConfigBox from "@/Components/v1/ConfigBox/index.jsx";
import Search from "@/Components/v1/Inputs/Search/index.jsx";
-import timezones from "../../../Utils/timezones.json";
+import timezones from "@/Utils/timezones.json";
// Utils
import { useTheme } from "@emotion/react";
diff --git a/client/src/Pages/v1/Settings/SettingsUI.jsx b/client/src/Pages/Settings/SettingsUI.jsx
similarity index 66%
rename from client/src/Pages/v1/Settings/SettingsUI.jsx
rename to client/src/Pages/Settings/SettingsUI.jsx
index 7129e775b..ef9f44eaa 100644
--- a/client/src/Pages/v1/Settings/SettingsUI.jsx
+++ b/client/src/Pages/Settings/SettingsUI.jsx
@@ -3,16 +3,20 @@ import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import ConfigBox from "@/Components/v1/ConfigBox/index.jsx";
import Select from "@/Components/v1/Inputs/Select/index.jsx";
+import DummyChart from "./DummyChart";
+import { ThemeProvider } from "@mui/material/styles";
+import { lightTheme, darkTheme } from "@/Utils/Theme/v2Theme";
// Utils
import { useTheme } from "@emotion/react";
import { PropTypes } from "prop-types";
import { useTranslation } from "react-i18next";
-const SettingsUI = ({ HEADING_SX, handleChange, mode, language }) => {
+const SettingsUI = ({ HEADING_SX, handleChange, mode, language, chartType }) => {
const { t, i18n } = useTranslation();
const theme = useTheme();
const languages = Object.keys(i18n.options.resources || {});
+ const v2Theme = mode === "dark" ? darkTheme : lightTheme;
return (
@@ -44,6 +48,19 @@ const SettingsUI = ({ HEADING_SX, handleChange, mode, language }) => {
onChange={handleChange}
items={languages.map((lang) => ({ _id: lang, name: lang.toUpperCase() }))}
>
+
+
+
+
);
@@ -54,6 +71,7 @@ SettingsUI.propTypes = {
handleChange: PropTypes.func,
mode: PropTypes.string,
language: PropTypes.string,
+ chartType: PropTypes.string,
};
export default SettingsUI;
diff --git a/client/src/Pages/v1/Settings/SettingsURL.jsx b/client/src/Pages/Settings/SettingsURL.jsx
similarity index 100%
rename from client/src/Pages/v1/Settings/SettingsURL.jsx
rename to client/src/Pages/Settings/SettingsURL.jsx
diff --git a/client/src/Pages/v1/Settings/index.jsx b/client/src/Pages/Settings/index.jsx
similarity index 69%
rename from client/src/Pages/v1/Settings/index.jsx
rename to client/src/Pages/Settings/index.jsx
index 2732e331a..bd3513a2c 100644
--- a/client/src/Pages/v1/Settings/index.jsx
+++ b/client/src/Pages/Settings/index.jsx
@@ -9,9 +9,10 @@ import SettingsDemoMonitors from "./SettingsDemoMonitors.jsx";
import SettingsAbout from "./SettingsAbout.jsx";
import SettingsEmail from "./SettingsEmail.jsx";
import SettingsGlobalThresholds from "./SettingsGlobalThresholds.jsx";
+import SettingsExport from "./SettingsExport.jsx";
import Button from "@mui/material/Button";
// Utils
-import { settingsValidation } from "../../../Validation/validation.js";
+import { settingsValidation } from "@/Validation/validation.js";
import { useState } from "react";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
@@ -21,22 +22,30 @@ import {
setMode,
setLanguage,
setShowURL,
-} from "../../../Features/UI/uiSlice.js";
+ setChartType,
+} from "@/Features/UI/uiSlice.js";
import SettingsStats from "./SettingsStats.jsx";
-import { useFetchSettings, useSaveSettings } from "../../../Hooks/v1/settingsHooks.js";
-import { useIsAdmin } from "../../../Hooks/v1/useIsAdmin.js";
+import { useFetchSettings, useSaveSettings } from "@/Hooks/settingsHooks.js";
+import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
import {
useAddDemoMonitors,
useDeleteAllMonitors,
useDeleteMonitorStats,
-} from "../../../Hooks/v1/monitorHooks.js";
+ useFetchJson,
+} from "@/Hooks/monitorHooks.js";
// Constants
const BREADCRUMBS = [{ name: `Settings`, path: "/settings" }];
const Settings = () => {
// Redux state
- const { mode, language, timezone, showURL } = useSelector((state) => state.ui);
+ const {
+ mode,
+ language = "en",
+ timezone,
+ showURL,
+ chartType = "histogram",
+ } = useSelector((state) => state.ui);
// Local state
const [settingsData, setSettingsData] = useState({});
@@ -66,6 +75,7 @@ const Settings = () => {
});
const [deleteAllMonitors, isDeletingMonitors] = useDeleteAllMonitors();
const [deleteMonitorStats, isDeletingMonitorStats] = useDeleteMonitorStats();
+ const [fetchJson, isFetchingJson] = useFetchJson();
// Setup
const isAdmin = useIsAdmin();
@@ -77,11 +87,6 @@ const Settings = () => {
const handleChange = async (e) => {
const { name, value, checked } = e.target;
- // Special case for showURL until handled properly in the backend
- if (name === "showURL") {
- dispatch(setShowURL(value));
- return;
- }
let newValue;
if (
name === "systemEmailIgnoreTLS" ||
@@ -92,6 +97,12 @@ const Settings = () => {
) {
newValue = checked;
}
+
+ // Ensure showURL is a proper boolean
+ if (name === "showURL") {
+ newValue = value === true || value === "true";
+ }
+
// Build next state early
const newSettingsData = {
...settingsData,
@@ -113,6 +124,11 @@ const Settings = () => {
return;
}
+ if (name === "chartType") {
+ dispatch(setChartType(value));
+ return;
+ }
+
if (name === "deleteStats") {
await deleteMonitorStats();
return;
@@ -128,38 +144,72 @@ const Settings = () => {
return;
}
- // Validate
- const { error } = settingsValidation.validate(newSettingsData.settings, {
- abortEarly: false,
- });
- if (!error || error.details.length === 0) {
- setErrors({});
- } else {
- const newErrors = {};
- error.details.forEach((err) => {
- newErrors[err.path[0]] = err.message;
+ if (name === "export") {
+ const json = await fetchJson();
+ if (!json || json.length === 0) {
+ return;
+ }
+
+ const blob = new Blob([JSON.stringify(json, null, 2)], {
+ type: "application/json",
});
- setErrors(newErrors);
+ const url = URL.createObjectURL(blob);
+
+ const link = document.createElement("a");
+ link.href = url;
+ link.download = "monitors.json";
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ URL.revokeObjectURL(url);
+ return;
}
setSettingsData(newSettingsData);
+
+ // Update Redux immediately for UI feedback
+ if (name === "showURL") {
+ dispatch(setShowURL(newValue));
+ }
};
const handleSave = () => {
- const { error } = settingsValidation.validate(settingsData.settings, {
+ // Validate]
+
+ const toSubmit = {
+ checkTTL: settingsData.settings.checkTTL,
+ pagespeedApiKey: settingsData.settings.pagespeedApiKey,
+ language: settingsData.settings.language,
+ timezone: settingsData.settings.timezone,
+ systemEmailHost: settingsData.settings.systemEmailHost,
+ systemEmailPort: settingsData.settings.systemEmailPort,
+ systemEmailSecure: settingsData.settings.systemEmailSecure,
+ systemEmailPool: settingsData.settings.systemEmailPool,
+ systemEmailAddress: settingsData.settings.systemEmailAddress,
+ systemEmailPassword: settingsData.settings.systemEmailPassword,
+ systemEmailUser: settingsData.settings.systemEmailUser,
+ systemEmailConnectionHost: settingsData.settings.systemEmailConnectionHost,
+ systemEmailTLSServername: settingsData.settings.systemEmailTLSServername,
+ systemEmailIgnoreTLS: settingsData.settings.systemEmailIgnoreTLS,
+ systemEmailRequireTLS: settingsData.settings.systemEmailRequireTLS,
+ systemEmailRejectUnauthorized: settingsData.settings.systemEmailRejectUnauthorized,
+ showURL: settingsData.settings.showURL,
+ globalThresholds: settingsData.settings.globalThresholds,
+ };
+
+ const { error } = settingsValidation.validate(toSubmit, {
abortEarly: false,
});
if (!error || error.details.length === 0) {
setErrors({});
+ saveSettings(toSubmit);
} else {
const newErrors = {};
error.details.forEach((err) => {
newErrors[err.path[0]] = err.message;
});
-
setErrors(newErrors);
}
- saveSettings(settingsData?.settings);
};
return (
@@ -176,6 +226,7 @@ const Settings = () => {
handleChange={handleChange}
mode={mode}
language={language}
+ chartType={chartType}
/>
{
setEmailPasswordHasBeenReset={setEmailPasswordHasBeenReset}
/>
+
-
+
{monitor.name}
- {
onDelete(monitor);
}}
@@ -43,7 +47,7 @@ const MonitorListItem = ({
const MonitorList = ({ selectedMonitors, setSelectedMonitors }) => {
const onDelete = (monitorToDelete) => {
const newMonitors = selectedMonitors.filter(
- (monitor) => monitor._id !== monitorToDelete._id
+ (monitor) => monitor.id !== monitorToDelete.id
);
setSelectedMonitors(newMonitors);
};
@@ -80,8 +84,8 @@ const MonitorList = ({ selectedMonitors, setSelectedMonitors }) => {
>
{selectedMonitors?.map((monitor, index) => (
{(provided, snapshot) => (
diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Progress/index.jsx b/client/src/Pages/StatusPage/Create/Components/Progress/index.jsx
similarity index 80%
rename from client/src/Pages/v1/StatusPage/Create/Components/Progress/index.jsx
rename to client/src/Pages/StatusPage/Create/Components/Progress/index.jsx
index 232a12f1d..594fd9709 100644
--- a/client/src/Pages/v1/StatusPage/Create/Components/Progress/index.jsx
+++ b/client/src/Pages/StatusPage/Create/Components/Progress/index.jsx
@@ -1,14 +1,19 @@
import { Button, Box } from "@mui/material";
import ProgressUpload from "@/Components/v1/ProgressBars/index.jsx";
-import ImageIcon from "@mui/icons-material/Image";
+import Icon from "@/Components/v1/Icon";
import { useTranslation } from "react-i18next";
-import { formatBytes } from "../../../../../../Utils/fileUtils.js";
+import { formatBytes } from "../../../../../Utils/fileUtils.js";
const Progress = ({ isLoading, progressValue, logo, logoType, removeLogo, errors }) => {
const { t } = useTranslation();
if (isLoading) {
return (
}
+ icon={
+
+ }
label={logo?.name}
size={formatBytes(logo?.size)}
progress={progressValue}
diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Skeleton/index.jsx b/client/src/Pages/StatusPage/Create/Components/Skeleton/index.jsx
similarity index 100%
rename from client/src/Pages/v1/StatusPage/Create/Components/Skeleton/index.jsx
rename to client/src/Pages/StatusPage/Create/Components/Skeleton/index.jsx
diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/ConfigStack.jsx b/client/src/Pages/StatusPage/Create/Components/Tabs/ConfigStack.jsx
similarity index 100%
rename from client/src/Pages/v1/StatusPage/Create/Components/Tabs/ConfigStack.jsx
rename to client/src/Pages/StatusPage/Create/Components/Tabs/ConfigStack.jsx
diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Content.jsx b/client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx
similarity index 99%
rename from client/src/Pages/v1/StatusPage/Create/Components/Tabs/Content.jsx
rename to client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx
index d11825e69..e84de16dc 100644
--- a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Content.jsx
+++ b/client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx
@@ -24,7 +24,7 @@ const Content = ({
// Handlers
const handleMonitorsChange = (selectedMonitors) => {
handleFormChange({
- target: { name: "monitors", value: selectedMonitors.map((monitor) => monitor._id) },
+ target: { name: "monitors", value: selectedMonitors.map((monitor) => monitor.id) },
});
setSelectedMonitors(selectedMonitors);
};
diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx b/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx
similarity index 98%
rename from client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx
rename to client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx
index a668a9db8..d4c583d3a 100644
--- a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx
+++ b/client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx
@@ -11,7 +11,7 @@ import Progress from "../Progress/index.jsx";
// Utils
import { useTheme } from "@emotion/react";
-import timezones from "../../../../../../Utils/timezones.json";
+import timezones from "../../../../../Utils/timezones.json";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
import { useMemo, useState, useCallback } from "react";
diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/index.jsx b/client/src/Pages/StatusPage/Create/Components/Tabs/index.jsx
similarity index 100%
rename from client/src/Pages/v1/StatusPage/Create/Components/Tabs/index.jsx
rename to client/src/Pages/StatusPage/Create/Components/Tabs/index.jsx
diff --git a/client/src/Pages/v1/StatusPage/Create/Hooks/useCreateStatusPage.jsx b/client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx
similarity index 61%
rename from client/src/Pages/v1/StatusPage/Create/Hooks/useCreateStatusPage.jsx
rename to client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx
index 808598052..c0bcf5ab4 100644
--- a/client/src/Pages/v1/StatusPage/Create/Hooks/useCreateStatusPage.jsx
+++ b/client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx
@@ -1,14 +1,14 @@
import { useState } from "react";
-import { networkService } from "../../../../../main.jsx";
-import { createToast } from "../../../../../Utils/toastUtils.jsx";
+import { networkService } from "../../../../main.jsx";
+import { createToast } from "../../../../Utils/toastUtils.jsx";
-const useCreateStatusPage = (isCreate, url) => {
+const useCreateStatusPage = (isCreate) => {
const [isLoading, setIsLoading] = useState(false);
const [networkError, setNetworkError] = useState(false);
- const createStatusPage = async ({ form }) => {
+ const createStatusPage = async ({ form, id }) => {
setIsLoading(true);
try {
- await networkService.createStatusPage({ form, isCreate, url });
+ await networkService.createStatusPage({ form, isCreate, id });
return true;
} catch (error) {
setNetworkError(true);
diff --git a/client/src/Pages/v1/StatusPage/Create/Hooks/useMonitorsFetch.jsx b/client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx
similarity index 74%
rename from client/src/Pages/v1/StatusPage/Create/Hooks/useMonitorsFetch.jsx
rename to client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx
index 2a7db202a..03ebb07bf 100644
--- a/client/src/Pages/v1/StatusPage/Create/Hooks/useMonitorsFetch.jsx
+++ b/client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx
@@ -1,7 +1,7 @@
import { useEffect, useState } from "react";
-import { networkService } from "../../../../../main.jsx";
+import { networkService } from "../../../../main.jsx";
import { useSelector } from "react-redux";
-import { createToast } from "../../../../../Utils/toastUtils.jsx";
+import { createToast } from "../../../../Utils/toastUtils.jsx";
const useMonitorsFetch = () => {
const { user } = useSelector((state) => state.auth);
@@ -14,9 +14,9 @@ const useMonitorsFetch = () => {
try {
const response = await networkService.getMonitorsByTeamId({
limit: null, // donot return any checks for the monitors
- types: ["http", "ping", "port"], // status page is available for uptime, ping, and port monitors
+ types: ["http", "ping", "port", "game"], // include game servers in status page monitor selection
});
- setMonitors(response.data.data.monitors);
+ setMonitors(response.data.data);
} catch (error) {
setNetworkError(true);
createToast({ body: error.message });
diff --git a/client/src/Pages/v1/StatusPage/Create/index.jsx b/client/src/Pages/StatusPage/Create/index.jsx
similarity index 95%
rename from client/src/Pages/v1/StatusPage/Create/index.jsx
rename to client/src/Pages/StatusPage/Create/index.jsx
index d9fafed70..4eb171232 100644
--- a/client/src/Pages/v1/StatusPage/Create/index.jsx
+++ b/client/src/Pages/StatusPage/Create/index.jsx
@@ -8,11 +8,11 @@ import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
//Utils
import { useTheme } from "@emotion/react";
import { useState, useEffect, useRef, useCallback } from "react";
-import { statusPageValidation } from "../../../../Validation/validation.js";
-import { buildErrors } from "../../../../Validation/error.js";
+import { statusPageValidation } from "../../../Validation/validation.js";
+import { buildErrors } from "../../../Validation/error.js";
import { useMonitorsFetch } from "./Hooks/useMonitorsFetch.jsx";
import { useCreateStatusPage } from "./Hooks/useCreateStatusPage.jsx";
-import { createToast } from "../../../../Utils/toastUtils.jsx";
+import { createToast } from "../../../Utils/toastUtils.jsx";
import { useNavigate } from "react-router-dom";
import { useStatusPageFetch } from "../Status/Hooks/useStatusPageFetch.jsx";
import { useParams } from "react-router-dom";
@@ -54,15 +54,14 @@ const CreateStatusPage = () => {
//Utils
const theme = useTheme();
const [monitors, isLoading, networkError] = useMonitorsFetch();
- const [createStatusPage] = useCreateStatusPage(isCreate);
const navigate = useNavigate();
const { t } = useTranslation();
+ const [createStatusPage] = useCreateStatusPage(isCreate);
const [statusPage, statusPageMonitors, statusPageIsLoading, , fetchStatusPage] =
useStatusPageFetch(isCreate, url);
const [deleteStatusPage, isDeleting] = useStatusPageDelete(fetchStatusPage, url);
- console.log(JSON.stringify(form, null, 2));
// Handlers
const handleFormChange = (e) => {
let { type, name, value, checked } = e.target;
@@ -144,12 +143,13 @@ const CreateStatusPage = () => {
...form,
logo: { type: form.logo?.type ?? null, size: form.logo?.size ?? null },
};
+
const { error } = statusPageValidation.validate(toSubmit, {
abortEarly: false,
});
if (typeof error === "undefined") {
- const success = await createStatusPage({ form });
+ const success = await createStatusPage({ form, id: statusPage?.id });
if (success) {
createToast({
body: isCreate ? t("statusPage.createSuccess") : t("statusPage.updateSuccess"),
@@ -205,7 +205,7 @@ const CreateStatusPage = () => {
companyName: statusPage?.companyName,
isPublished: statusPage?.isPublished,
timezone: statusPage?.timezone,
- monitors: statusPageMonitors.map((monitor) => monitor._id),
+ monitors: statusPageMonitors.map((monitor) => monitor.id),
color: statusPage?.color,
logo: newLogo,
showCharts: statusPage?.showCharts ?? true,
diff --git a/client/src/Pages/v1/StatusPage/Status/Components/AdminLink/index.jsx b/client/src/Pages/StatusPage/Status/Components/AdminLink/index.jsx
similarity index 100%
rename from client/src/Pages/v1/StatusPage/Status/Components/AdminLink/index.jsx
rename to client/src/Pages/StatusPage/Status/Components/AdminLink/index.jsx
diff --git a/client/src/Pages/v1/StatusPage/Status/Components/ControlsHeader/index.jsx b/client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx
similarity index 92%
rename from client/src/Pages/v1/StatusPage/Status/Components/ControlsHeader/index.jsx
rename to client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx
index c56aa40cc..fe1c4c852 100644
--- a/client/src/Pages/v1/StatusPage/Status/Components/ControlsHeader/index.jsx
+++ b/client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx
@@ -1,9 +1,8 @@
// Components
import { Box, Stack, Typography, Button } from "@mui/material";
import Image from "@/Components/v1/Image/index.jsx";
-import SettingsIcon from "../../../../../../assets/icons/settings-bold.svg?react";
+import Icon from "@/Components/v1/Icon";
import ThemeSwitch from "@/Components/v1/ThemeSwitch/index.jsx";
-import ArrowOutwardIcon from "@mui/icons-material/ArrowOutward";
//Utils
import { useTheme } from "@mui/material/styles";
import { useNavigate } from "react-router-dom";
@@ -46,7 +45,12 @@ const Controls = ({ url, type }) => {
},
}}
>
- {t("configure")}
+ {" "}
+ {t("configure")}
@@ -111,7 +115,10 @@ const ControlsHeader = ({ statusPage, isPublic, url, type = "uptime" }) => {
}}
>
{t("publicLink")}
-
+
)}
diff --git a/client/src/Pages/v1/StatusPage/Status/Components/MonitorsList/index.jsx b/client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx
similarity index 77%
rename from client/src/Pages/v1/StatusPage/Status/Components/MonitorsList/index.jsx
rename to client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx
index 59ad4d522..33f771233 100644
--- a/client/src/Pages/v1/StatusPage/Status/Components/MonitorsList/index.jsx
+++ b/client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx
@@ -6,7 +6,7 @@ import { StatusLabel } from "@/Components/v1/Label/index.jsx";
//Utils
import { useTheme } from "@mui/material/styles";
-import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js";
+import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils.js";
import PropTypes from "prop-types";
import { useSelector } from "react-redux";
@@ -28,35 +28,31 @@ const MonitorsList = ({
const status = determineState(monitor);
return (
{statusPage.showCharts !== false && (
)}
-
-
-
);
diff --git a/client/src/Pages/v1/StatusPage/Status/Components/Skeleton/index.jsx b/client/src/Pages/StatusPage/Status/Components/Skeleton/index.jsx
similarity index 100%
rename from client/src/Pages/v1/StatusPage/Status/Components/Skeleton/index.jsx
rename to client/src/Pages/StatusPage/Status/Components/Skeleton/index.jsx
diff --git a/client/src/Pages/v1/StatusPage/Status/Components/StatusBar/index.jsx b/client/src/Pages/StatusPage/Status/Components/StatusBar/index.jsx
similarity index 84%
rename from client/src/Pages/v1/StatusPage/Status/Components/StatusBar/index.jsx
rename to client/src/Pages/StatusPage/Status/Components/StatusBar/index.jsx
index 1898ff3d7..7b9460068 100644
--- a/client/src/Pages/v1/StatusPage/Status/Components/StatusBar/index.jsx
+++ b/client/src/Pages/StatusPage/Status/Components/StatusBar/index.jsx
@@ -1,7 +1,6 @@
// Components
import { Stack, Typography } from "@mui/material";
-import CheckCircleIcon from "@mui/icons-material/CheckCircle";
-import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
+import Icon from "@/Components/v1/Icon";
// Utils
import { useTheme } from "@mui/material/styles";
@@ -10,8 +9,10 @@ import PropTypes from "prop-types";
const getMonitorStatus = (monitors, theme) => {
const monitorsStatus = {
icon: (
-
),
};
@@ -19,8 +20,10 @@ const getMonitorStatus = (monitors, theme) => {
monitorsStatus.msg = "All systems operational";
monitorsStatus.color = theme.palette.success.lowContrast;
monitorsStatus.icon = (
-
);
}
diff --git a/client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageDelete.jsx b/client/src/Pages/StatusPage/Status/Hooks/useStatusPageDelete.jsx
similarity index 89%
rename from client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageDelete.jsx
rename to client/src/Pages/StatusPage/Status/Hooks/useStatusPageDelete.jsx
index 038a771ba..73b9a8a5f 100644
--- a/client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageDelete.jsx
+++ b/client/src/Pages/StatusPage/Status/Hooks/useStatusPageDelete.jsx
@@ -1,6 +1,6 @@
import { useState } from "react";
-import { networkService } from "../../../../../Utils/NetworkService.js";
-import { createToast } from "../../../../../Utils/toastUtils.jsx";
+import { networkService } from "../../../../Utils/NetworkService.js";
+import { createToast } from "../../../../Utils/toastUtils.jsx";
import { useTranslation } from "react-i18next";
/**
diff --git a/client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageFetch.jsx b/client/src/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx
similarity index 87%
rename from client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageFetch.jsx
rename to client/src/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx
index 506082887..7db6b6c5e 100644
--- a/client/src/Pages/v1/StatusPage/Status/Hooks/useStatusPageFetch.jsx
+++ b/client/src/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx
@@ -1,9 +1,9 @@
import { useEffect, useState, useCallback } from "react";
-import { networkService } from "../../../../../main.jsx";
+import { networkService } from "../../../../main.jsx";
import { useSelector } from "react-redux";
-import { createToast } from "../../../../../Utils/toastUtils.jsx";
+import { createToast } from "../../../../Utils/toastUtils.jsx";
import { useTheme } from "@emotion/react";
-import { useMonitorUtils } from "../../../../../Hooks/v1/useMonitorUtils.js";
+import { useMonitorUtils } from "../../../../Hooks/useMonitorUtils.js";
const useStatusPageFetch = (isCreate = false, url) => {
const [isLoading, setIsLoading] = useState(true);
diff --git a/client/src/Pages/v1/StatusPage/Status/index.jsx b/client/src/Pages/StatusPage/Status/index.jsx
similarity index 98%
rename from client/src/Pages/v1/StatusPage/Status/index.jsx
rename to client/src/Pages/StatusPage/Status/index.jsx
index e700bbbd9..adcd9c2c7 100644
--- a/client/src/Pages/v1/StatusPage/Status/index.jsx
+++ b/client/src/Pages/StatusPage/Status/index.jsx
@@ -12,7 +12,7 @@ import TextLink from "@/Components/v1/TextLink/index.jsx";
// Utils
import { useStatusPageFetch } from "./Hooks/useStatusPageFetch.jsx";
import { useTheme } from "@emotion/react";
-import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js";
+import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
import { useLocation } from "react-router-dom";
import { useParams } from "react-router-dom";
import { useTranslation } from "react-i18next";
diff --git a/client/src/Pages/v1/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx b/client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx
similarity index 93%
rename from client/src/Pages/v1/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx
rename to client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx
index 1afe25409..9882239bd 100644
--- a/client/src/Pages/v1/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx
+++ b/client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx
@@ -2,7 +2,7 @@ import DataTable from "@/Components/v1/Table/index.jsx";
import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router-dom";
import { StatusLabel } from "@/Components/v1/Label/index.jsx";
-import ArrowOutwardIcon from "@mui/icons-material/ArrowOutward";
+import Icon from "@/Components/v1/Icon";
import { Stack, Typography } from "@mui/material";
import { useTranslation } from "react-i18next";
const StatusPagesTable = ({ data }) => {
@@ -48,7 +48,12 @@ const StatusPagesTable = ({ data }) => {
}}
>
{content}
- {row.isPublished && }
+ {row.isPublished && (
+
+ )}
);
},
diff --git a/client/src/Pages/v1/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx b/client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx
similarity index 82%
rename from client/src/Pages/v1/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx
rename to client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx
index 89df1b84d..9336e5d31 100644
--- a/client/src/Pages/v1/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx
+++ b/client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx
@@ -1,7 +1,7 @@
import { useState, useEffect } from "react";
-import { networkService } from "../../../../../main.jsx";
+import { networkService } from "../../../../main.jsx";
import { useSelector } from "react-redux";
-import { createToast } from "../../../../../Utils/toastUtils.jsx";
+import { createToast } from "../../../../Utils/toastUtils.jsx";
const useStatusPagesFetch = () => {
const { user } = useSelector((state) => state.auth);
@@ -14,7 +14,7 @@ const useStatusPagesFetch = () => {
const fetchStatusPages = async () => {
try {
const res = await networkService.getStatusPagesByTeamId();
- setStatusPages(res?.data?.data);
+ setStatusPages(res?.data?.data || []);
} catch (error) {
setNetworkError(true);
createToast({
diff --git a/client/src/Pages/v1/StatusPage/StatusPages/index.jsx b/client/src/Pages/StatusPage/StatusPages/index.jsx
similarity index 95%
rename from client/src/Pages/v1/StatusPage/StatusPages/index.jsx
rename to client/src/Pages/StatusPage/StatusPages/index.jsx
index 3c60fe950..7137d01ad 100644
--- a/client/src/Pages/v1/StatusPage/StatusPages/index.jsx
+++ b/client/src/Pages/StatusPage/StatusPages/index.jsx
@@ -7,7 +7,7 @@ import PageStateWrapper from "@/Components/v1/PageStateWrapper/index.jsx";
// Utils
import { useTheme } from "@emotion/react";
import { useStatusPagesFetch } from "./Hooks/useStatusPagesFetch.jsx";
-import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js";
+import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
const BREADCRUMBS = [{ name: `Status Pages`, path: "" }];
const StatusPages = () => {
diff --git a/client/src/Pages/v1/Uptime/BulkImport/Upload.jsx b/client/src/Pages/Uptime/BulkImport/Upload.jsx
similarity index 100%
rename from client/src/Pages/v1/Uptime/BulkImport/Upload.jsx
rename to client/src/Pages/Uptime/BulkImport/Upload.jsx
diff --git a/client/src/Pages/v1/Uptime/BulkImport/index.jsx b/client/src/Pages/Uptime/BulkImport/index.jsx
similarity index 95%
rename from client/src/Pages/v1/Uptime/BulkImport/index.jsx
rename to client/src/Pages/Uptime/BulkImport/index.jsx
index 9f3efcd67..6f2f7bcb5 100644
--- a/client/src/Pages/v1/Uptime/BulkImport/index.jsx
+++ b/client/src/Pages/Uptime/BulkImport/index.jsx
@@ -5,14 +5,14 @@ import { useState } from "react";
import { Box, Stack, Typography, Button, Link } from "@mui/material";
//Components
-import { createToast } from "../../../../Utils/toastUtils.jsx";
+import { createToast } from "../../../Utils/toastUtils.jsx";
import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
import ConfigBox from "@/Components/v1/ConfigBox/index.jsx";
import UploadFile from "./Upload.jsx";
import { useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import { Trans, useTranslation } from "react-i18next";
-import { useCreateBulkMonitors } from "../../../../Hooks/v1/monitorHooks.js";
+import { useCreateBulkMonitors } from "../../../Hooks/monitorHooks.js";
const BulkImport = () => {
const theme = useTheme();
diff --git a/client/src/Pages/v1/Uptime/Create/index.css b/client/src/Pages/Uptime/Create/index.css
similarity index 100%
rename from client/src/Pages/v1/Uptime/Create/index.css
rename to client/src/Pages/Uptime/Create/index.css
diff --git a/client/src/Pages/v1/Uptime/Create/index.jsx b/client/src/Pages/Uptime/Create/index.jsx
similarity index 95%
rename from client/src/Pages/v1/Uptime/Create/index.jsx
rename to client/src/Pages/Uptime/Create/index.jsx
index 202f64d85..01fc9eb7a 100644
--- a/client/src/Pages/v1/Uptime/Create/index.jsx
+++ b/client/src/Pages/Uptime/Create/index.jsx
@@ -26,14 +26,11 @@ import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
-import { monitorValidation } from "../../../../Validation/validation.js";
-import { createToast } from "../../../../Utils/toastUtils.jsx";
-import {
- PauseOutlined as PauseOutlinedIcon,
- PlayArrowOutlined as PlayArrowOutlinedIcon,
-} from "@mui/icons-material";
-import { useMonitorUtils } from "../../../../Hooks/v1/useMonitorUtils.js";
-import { useGetNotificationsByTeamId } from "../../../../Hooks/v1/useNotifications.js";
+import { monitorValidation } from "../../../Validation/validation.js";
+import { createToast } from "../../../Utils/toastUtils.jsx";
+import Icon from "@/Components/v1/Icon";
+import { useMonitorUtils } from "../../../Hooks/useMonitorUtils.js";
+import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications.js";
import { useParams } from "react-router-dom";
import {
useCreateMonitor,
@@ -42,7 +39,7 @@ import {
usePauseMonitor,
useFetchMonitorById,
useFetchMonitorGames,
-} from "../../../../Hooks/v1/monitorHooks.js";
+} from "../../../Hooks/monitorHooks.js";
/**
* Create page renders monitor creation or configuration views.
@@ -83,13 +80,13 @@ const UptimeCreate = ({ isClone = false }) => {
const [isFetchingMonitor] = useFetchMonitorById({
monitorId,
setMonitor,
- updateTrigger: true,
+ updateTrigger,
});
// Fetch games
const [isFetchingGames] = useFetchMonitorGames({
setGames,
- triggerUpdate: true,
+ updateTrigger,
});
// Combine the loading states
@@ -107,11 +104,16 @@ const UptimeCreate = ({ isClone = false }) => {
// Constants
const MS_PER_MINUTE = 60000;
const FREQUENCIES = [
+ { _id: 0.25, name: t("time.fifteenSeconds") },
+ { _id: 0.5, name: t("time.thirtySeconds") },
{ _id: 1, name: t("time.oneMinute") },
{ _id: 2, name: t("time.twoMinutes") },
{ _id: 3, name: t("time.threeMinutes") },
{ _id: 4, name: t("time.fourMinutes") },
{ _id: 5, name: t("time.fiveMinutes") },
+ { _id: 10, name: t("time.tenMinutes") },
+ { _id: 15, name: t("time.fifteenMinutes") },
+ { _id: 30, name: t("time.thirtyMinutes") },
];
const GAMELIST = Object.entries(games).map(([key, value]) => ({
@@ -194,7 +196,7 @@ const UptimeCreate = ({ isClone = false }) => {
};
} else {
form = {
- _id: monitor._id,
+ id: monitor.id,
url: monitor.url,
name: monitor.name || monitor.url.substring(0, 50),
statusWindowSize: monitor.statusWindowSize,
@@ -281,13 +283,10 @@ const UptimeCreate = ({ isClone = false }) => {
}));
};
- const handlePause = async () => {
- await pauseMonitor({ monitorId, triggerUpdate });
- };
-
const handleRemove = async (event) => {
event.preventDefault();
- await deleteMonitor({ monitor, redirect: "/uptime" });
+ const TEMP_MONITOR = { id: monitor.id };
+ await deleteMonitor({ monitor: TEMP_MONITOR, redirect: "/uptime" });
};
const isBusy = isLoading || isCreating || isDeleting || isUpdating || isPausing;
@@ -423,9 +422,24 @@ const UptimeCreate = ({ isClone = false }) => {
color="secondary"
loading={isBusy}
startIcon={
- monitor?.isActive ? :
+ monitor?.isActive ? (
+
+ ) : (
+
+ )
}
- onClick={handlePause}
+ onClick={() => {
+ pauseMonitor({
+ monitorId: monitor?.id,
+ triggerUpdate,
+ });
+ }}
>
{monitor?.isActive ? t("pause") : t("resume")}
diff --git a/client/src/Pages/v1/Uptime/Create/skeleton.jsx b/client/src/Pages/Uptime/Create/skeleton.jsx
similarity index 100%
rename from client/src/Pages/v1/Uptime/Create/skeleton.jsx
rename to client/src/Pages/Uptime/Create/skeleton.jsx
diff --git a/client/src/Pages/v2/Uptime/CheckTable.tsx b/client/src/Pages/Uptime/Details/Components/ChecksTable.tsx
similarity index 51%
rename from client/src/Pages/v2/Uptime/CheckTable.tsx
rename to client/src/Pages/Uptime/Details/Components/ChecksTable.tsx
index c2811c453..2cfdc1f15 100644
--- a/client/src/Pages/v2/Uptime/CheckTable.tsx
+++ b/client/src/Pages/Uptime/Details/Components/ChecksTable.tsx
@@ -1,60 +1,68 @@
-import { Table, Pagination } from "@/Components/v2/DesignElements";
-import { StatusLabel } from "@/Components/v2/DesignElements";
+import { Table, Pagination, StatusLabel } from "@/Components/v2/design-elements";
import Box from "@mui/material/Box";
-
-import type { Header } from "@/Components/v2/DesignElements/Table";
+import type { Header } from "@/Components/v2/design-elements/";
import type { Check } from "@/Types/Check";
-import type { ApiResponse } from "@/Hooks/v2/UseApi";
-import type { MonitorStatus } from "@/Types/Monitor";
-import { useState } from "react";
+import { useNavigate } from "react-router";
import { useTranslation } from "react-i18next";
-import { useGet } from "@/Hooks/v2/UseApi";
-import { formatDateWithTz } from "@/Utils/v2/TimeUtils";
+import { formatDateWithTz } from "@/Utils/TimeUtils";
+import type { RootState } from "@/Types/state";
import { useSelector } from "react-redux";
+
const getHeaders = (t: Function, uiTimezone: string) => {
const headers: Header[] = [
{
id: "status",
- content: t("status"),
+ content: t("common.table.headers.status"),
render: (row) => {
- return ;
+ return ;
},
},
{
id: "date",
- content: t("date&Time"),
+ content: t("pages.checks.table.headers.dateTime"),
render: (row) => {
return formatDateWithTz(row.createdAt, "ddd, MMMM D, YYYY, HH:mm A", uiTimezone);
},
},
{
- id: "statusCode",
- content: t("statusCode"),
+ id: "message",
+ content: t("common.table.headers.message"),
render: (row) => {
- return row.httpStatusCode || "N/A";
+ return row.message || "N/A";
+ },
+ },
+ {
+ id: "statusCode",
+ content: t("pages.checks.table.headers.statusCode"),
+ render: (row) => {
+ return row.statusCode || "N/A";
},
},
];
return headers;
};
-export const CheckTable = ({ monitorId }: { monitorId: string }) => {
- const [page, setPage] = useState(0);
- const [rowsPerPage, setRowsPerPage] = useState(5);
+export const ChecksTable = ({
+ checks,
+ count,
+ page,
+ setPage,
+ rowsPerPage,
+ setRowsPerPage,
+}: {
+ checks: Check[];
+ count: number;
+ page: number;
+ setPage: (page: number) => void;
+ rowsPerPage: number;
+ setRowsPerPage: (rowsPerPage: number) => void;
+}) => {
+ const navigate = useNavigate();
const { t } = useTranslation();
- const uiTimezone = useSelector((state: any) => state.ui.timezone);
+ const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
const headers = getHeaders(t, uiTimezone);
- const { response, error } = useGet(
- `/monitors/${monitorId}/checks?page=${page}&rowsPerPage=${rowsPerPage}`,
- {},
- { keepPreviousData: true }
- );
-
- const checks = response?.data?.checks || [];
- const count = response?.data?.count || 0;
-
const handlePageChange = (
_e: React.MouseEvent | null,
newPage: number
@@ -70,15 +78,14 @@ export const CheckTable = ({ monitorId }: { monitorId: string }) => {
setRowsPerPage(value);
};
- if (error) {
- console.error(error);
- }
-
return (
{
+ navigate(`/checks/${row.id}`);
+ }}
/>
{
+ const theme = useTheme();
+ const isAdmin = useIsAdmin();
+ const { monitorId } = useParams<{ monitorId: string }>();
+ const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
+
+ const [page, setPage] = useState(0);
+ const [rowsPerPage, setRowsPerPage] = useState(5);
+ const [dateRange, setDateRange] = useState("recent");
+
+ const monitorDetailsUrl = useMemo(() => {
+ if (!monitorId) {
+ return null;
+ }
+ const params = new URLSearchParams();
+ params.append("dateRange", dateRange);
+ params.append("normalize", "true");
+ return `/monitors/uptime/details/${monitorId}?${params.toString()}`;
+ }, [monitorId, dateRange]);
+
+ const {
+ data: monitorDetailsData,
+ isLoading: monitorIsLoading,
+ refetch: refetchMonitor,
+ } = useGet(
+ monitorDetailsUrl,
+ {},
+ { refreshInterval: 10000, keepPreviousData: true }
+ );
+
+ const monitorData = monitorDetailsData?.monitorData;
+ const monitor = monitorData?.monitor;
+ const monitorStats = monitorDetailsData?.monitorStats ?? null;
+
+ // Certificate fetch - only for HTTP monitors
+ const certificateUrl = useMemo(() => {
+ if (!monitorId || monitor?.type !== "http") {
+ return null;
+ }
+ return `/monitors/certificate/${monitorId}`;
+ }, [monitorId, monitor?.type]);
+
+ const { data: certificateData } = useGet(certificateUrl);
+
+ const certificateExpiry = useMemo(() => {
+ if (!certificateData?.certificateDate) {
+ return undefined;
+ }
+ return (
+ formatDateWithTz(
+ certificateData.certificateDate,
+ certificateDateFormat,
+ uiTimezone
+ ) ?? "N/A"
+ );
+ }, [certificateData, uiTimezone]);
+
+ const checksUrl = useMemo(() => {
+ if (!monitorId || !monitor?.type) {
+ return null;
+ }
+ const params = new URLSearchParams();
+ params.append("type", monitor.type);
+ params.append("sortOrder", "desc");
+ params.append("dateRange", dateRange);
+ params.append("page", String(page));
+ params.append("rowsPerPage", String(rowsPerPage));
+ return `/checks/${monitorId}?${params.toString()}`;
+ }, [monitorId, monitor?.type, dateRange, page, rowsPerPage]);
+
+ const { data: checksData, isLoading: checksIsLoading } = useGet(
+ checksUrl,
+ {},
+ { keepPreviousData: true }
+ );
+
+ const checks = checksData?.checks ?? [];
+ const checksCount = checksData?.checksCount ?? 0;
+
+ return (
+
+
+
+
+
+ }
+ checks={monitorData?.groupedUpChecks ?? []}
+ range={dateRange}
+ />
+ }
+ checks={monitorData?.groupedDownChecks ?? []}
+ range={dateRange}
+ />
+
+
+
+
+
+ );
+};
+
+export default UptimeDetailsPage;
diff --git a/client/src/Pages/v1/Uptime/Details/index.jsx b/client/src/Pages/Uptime/Details/old.jsx
similarity index 95%
rename from client/src/Pages/v1/Uptime/Details/index.jsx
rename to client/src/Pages/Uptime/Details/old.jsx
index f5e283495..144ceb4dc 100644
--- a/client/src/Pages/v1/Uptime/Details/index.jsx
+++ b/client/src/Pages/Uptime/Details/old.jsx
@@ -15,10 +15,10 @@ import { useState } from "react";
import { useParams } from "react-router-dom";
import { useSelector } from "react-redux";
import { useTheme } from "@emotion/react";
-import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js";
-import { useFetchUptimeMonitorById } from "../../../../Hooks/v1/monitorHooks.js";
+import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
+import { useFetchUptimeMonitorById } from "../../../Hooks/monitorHooks.js";
import useCertificateFetch from "./Hooks/useCertificateFetch.jsx";
-import { useFetchChecksByMonitor } from "../../../../Hooks/v1/checkHooks.js";
+import { useFetchChecksByMonitor } from "../../../Hooks/checkHooks.js";
import { useTranslation } from "react-i18next";
// Constants
diff --git a/client/src/Pages/v1/Uptime/Monitors/Components/Filter/index.jsx b/client/src/Pages/Uptime/Monitors/Components/Filter/index.jsx
similarity index 97%
rename from client/src/Pages/v1/Uptime/Monitors/Components/Filter/index.jsx
rename to client/src/Pages/Uptime/Monitors/Components/Filter/index.jsx
index b9a6824c1..62ba0be13 100644
--- a/client/src/Pages/v1/Uptime/Monitors/Components/Filter/index.jsx
+++ b/client/src/Pages/Uptime/Monitors/Components/Filter/index.jsx
@@ -3,7 +3,7 @@ import PropTypes from "prop-types";
import FilterHeader from "@/Components/v1/FilterHeader/index.jsx";
import { useMemo } from "react";
import { Box, Button } from "@mui/material";
-import ClearIcon from "@mui/icons-material/Clear";
+import Icon from "@/Components/v1/Icon";
import { useTranslation } from "react-i18next";
/**
@@ -131,7 +131,12 @@ const Filter = ({
color={theme.palette.primary.contrastText}
onClick={handleReset}
variant="contained"
- endIcon={ }
+ endIcon={
+
+ }
sx={{
visibility: isFilterActive ? "visible" : "hidden",
}}
diff --git a/client/src/Pages/v1/Uptime/Monitors/Components/LoadingSpinner/index.jsx b/client/src/Pages/Uptime/Monitors/Components/LoadingSpinner/index.jsx
similarity index 100%
rename from client/src/Pages/v1/Uptime/Monitors/Components/LoadingSpinner/index.jsx
rename to client/src/Pages/Uptime/Monitors/Components/LoadingSpinner/index.jsx
diff --git a/client/src/Pages/v1/Uptime/Monitors/Components/SearchComponent/index.jsx b/client/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx
similarity index 100%
rename from client/src/Pages/v1/Uptime/Monitors/Components/SearchComponent/index.jsx
rename to client/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx
diff --git a/client/src/Pages/v1/Uptime/Monitors/Components/Skeleton/index.jsx b/client/src/Pages/Uptime/Monitors/Components/Skeleton/index.jsx
similarity index 100%
rename from client/src/Pages/v1/Uptime/Monitors/Components/Skeleton/index.jsx
rename to client/src/Pages/Uptime/Monitors/Components/Skeleton/index.jsx
diff --git a/client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/index.jsx b/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/index.jsx
similarity index 100%
rename from client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/index.jsx
rename to client/src/Pages/Uptime/Monitors/Components/StatusBoxes/index.jsx
diff --git a/client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx b/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx
similarity index 100%
rename from client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx
rename to client/src/Pages/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx
diff --git a/client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx b/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx
similarity index 88%
rename from client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx
rename to client/src/Pages/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx
index c745e7935..136c3575d 100644
--- a/client/src/Pages/v1/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx
+++ b/client/src/Pages/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx
@@ -1,9 +1,8 @@
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { Box, Stack, Typography } from "@mui/material";
-import Arrow from "../../../../../../assets/icons/top-right-arrow.svg?react";
-import Background from "../../../../../../assets/Images/background-grid.svg?react";
-import ClockSnooze from "../../../../../../assets/icons/clock-snooze.svg?react";
+import Icon from "@/Components/v1/Icon";
+import Background from "@/assets/Images/background-grid.svg?react";
const StatusBox = ({ title, value, status }) => {
const theme = useTheme();
@@ -20,21 +19,30 @@ const StatusBox = ({ title, value, status }) => {
color = theme.palette.success.lowContrast;
icon = (
-
+
);
} else if (status === "down") {
color = theme.palette.error.lowContrast;
icon = (
-
+
);
} else if (status === "paused") {
color = theme.palette.warning.lowContrast;
icon = (
-
+
);
}
diff --git a/client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx b/client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx
similarity index 91%
rename from client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx
rename to client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx
index 9cf2d247d..8c3239dd3 100644
--- a/client/src/Pages/v1/Uptime/Monitors/Components/UptimeDataTable/index.jsx
+++ b/client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx
@@ -1,8 +1,7 @@
// Components
import { Box, Stack } from "@mui/material";
import DataTable from "@/Components/v1/Table/index.jsx";
-import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded";
-import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded";
+import Icon from "@/Components/v1/Icon";
import Host from "@/Components/v1/Host/index.jsx";
import { StatusLabel } from "@/Components/v1/Label/index.jsx";
import BarChart from "@/Components/v1/Charts/BarChart/index.jsx";
@@ -13,7 +12,7 @@ import TableSkeleton from "@/Components/v1/Table/skeleton.jsx";
// Utils
import { useTheme } from "@emotion/react";
-import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js";
+import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils.js";
import { useNavigate } from "react-router-dom";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
@@ -24,13 +23,13 @@ import { useTranslation } from "react-i18next";
* @param {boolean} props.isAdmin - Whether the current user has admin privileges
* @param {boolean} props.isLoading - Loading state of the table
* @param {Array<{
- * _id: string,
+ * id: string,
* url: string,
* title: string,
* percentage: number,
* percentageColor: string,
* monitor: {
- * _id: string,
+ * id: string,
* type: string,
* checks: Array
* }
@@ -92,16 +91,22 @@ const UptimeDataTable = ({
}}
>
{sort?.order === "asc" ? (
-
+
) : (
-
+
)}
),
render: (row) => (
{sort?.order === "asc" ? (
-
+
) : (
-
+
)}
@@ -200,7 +211,7 @@ const UptimeDataTable = ({
},
},
onRowClick: (row) => {
- navigate(`/uptime/${row._id}`);
+ navigate(`/uptime/${row.id}`);
},
emptyView: "No monitors found",
}}
diff --git a/client/src/Pages/Uptime/Monitors/Components/UptimeMonitorsTable.tsx b/client/src/Pages/Uptime/Monitors/Components/UptimeMonitorsTable.tsx
new file mode 100644
index 000000000..cc225ad81
--- /dev/null
+++ b/client/src/Pages/Uptime/Monitors/Components/UptimeMonitorsTable.tsx
@@ -0,0 +1,259 @@
+import Stack from "@mui/material/Stack";
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import {
+ Table,
+ type Header,
+ Pagination,
+ StatusLabel,
+} from "@/Components/v2/design-elements";
+import { HeatmapResponseTime, HistogramResponseTime } from "@/Components/v2/common";
+import { ActionsMenu } from "@/Components/v2/actions-menu";
+import { ArrowDown, ArrowUp } from "lucide-react";
+
+import { useTranslation } from "react-i18next";
+import { useTheme } from "@mui/material/styles";
+import { useNavigate } from "react-router-dom";
+import { usePost } from "@/Hooks/UseApi";
+import { useSelector } from "react-redux";
+
+import type { Monitor } from "@/Types/Monitor";
+import type { ActionMenuItem } from "@/Components/v2/actions-menu";
+import type { RootState } from "@/Types/state";
+
+export const MonitorTable = ({
+ monitors,
+ refetch,
+ setSelectedMonitor,
+ sortField,
+ setSortField,
+ sortOrder,
+ setSortOrder,
+ count,
+ page,
+ setPage,
+ rowsPerPage,
+ setRowsPerPage,
+}: {
+ monitors: Monitor[];
+ refetch: Function;
+ setSelectedMonitor: (monitor: Monitor | null) => void;
+ sortField: string;
+ setSortField: (field: string) => void;
+ sortOrder: "asc" | "desc";
+ setSortOrder: (order: "asc" | "desc") => void;
+ count: number;
+ page: number;
+ setPage: (page: number) => void;
+ rowsPerPage: number;
+ setRowsPerPage: (rowsPerPage: number) => void;
+}) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const navigate = useNavigate();
+ const chartType = useSelector((state: RootState) => state.ui?.chartType ?? "histogram");
+ const {
+ post,
+ // loading: isPosting,
+ // error: postError,
+ } = usePost();
+
+ const handleSort = (e: any, field: string) => {
+ e.preventDefault();
+ e.stopPropagation();
+ if (sortField === field) {
+ const newOrder = sortOrder === "asc" ? "desc" : "asc";
+ setSortOrder(newOrder);
+ } else {
+ setSortField(field);
+ setSortOrder("asc");
+ }
+ refetch();
+ };
+
+ const getActions = (monitor: Monitor): ActionMenuItem[] => {
+ return [
+ {
+ id: 1,
+ label: t("pages.common.monitors.actions.openSite"),
+ action: () => {
+ window.open(monitor.url, "_blank", "noreferrer");
+ },
+ closeMenu: true,
+ },
+ {
+ id: 2,
+ label: t("pages.common.monitors.actions.details"),
+ action: () => {
+ navigate(`${monitor.id}`);
+ },
+ },
+ {
+ id: 3,
+ label: t("pages.common.monitors.actions.incidents"),
+ action: () => {
+ navigate(`/incidents?monitorId=${monitor.id}`);
+ },
+ },
+ {
+ id: 4,
+ label: t("pages.common.monitors.actions.configure"),
+ action: () => {
+ navigate(`/uptime/configure/${monitor.id}`);
+ },
+ },
+ // {
+ // id: 5,
+ // label: "Clone",
+ // action: () => {
+
+ // },
+ // },
+ {
+ id: 6,
+ label:
+ monitor.isActive === false
+ ? t("pages.common.monitors.actions.resume")
+ : t("pages.common.monitors.actions.pause"),
+ action: async () => {
+ await post(`/monitors/pause/${monitor.id}`, {});
+ refetch();
+ },
+ closeMenu: true,
+ },
+ {
+ id: 7,
+ label: (
+
+ {t("pages.common.monitors.actions.delete")}
+
+ ),
+ action: () => setSelectedMonitor(monitor),
+ closeMenu: true,
+ },
+ ];
+ };
+
+ const getHeaders = (chartType: string) => {
+ const renderSortIcon = (isActive: boolean) => (
+
+ {isActive ? (
+ sortOrder === "asc" ? (
+
+ ) : (
+
+ )
+ ) : null}
+
+ );
+ const headers: Header[] = [
+ {
+ id: "name",
+ content: (
+ handleSort(e, "name")}
+ sx={{ cursor: "pointer" }}
+ >
+ {t("common.table.headers.name")}
+ {renderSortIcon(sortField === "name")}
+
+ ),
+
+ render: (row) => {
+ return row?.name;
+ },
+ },
+ {
+ id: "status",
+ content: (
+ handleSort(e, "status")}
+ sx={{ cursor: "pointer" }}
+ >
+ {t("common.table.headers.status")}
+ {renderSortIcon(sortField === "status")}
+
+ ),
+ render: (row) => {
+ return (
+
+ );
+ },
+ },
+ {
+ id: "histogram",
+ content: t("pages.uptime.table.headers.responseTime"),
+ render: (row) => {
+ if (chartType === "histogram") {
+ return ;
+ } else {
+ return ;
+ }
+ },
+ },
+ {
+ id: "type",
+ content: (
+ handleSort(e, "type")}
+ sx={{ cursor: "pointer" }}
+ >
+ {t("common.table.headers.type")}
+ {renderSortIcon(sortField === "type")}
+
+ ),
+ render: (row) => {
+ return row.type;
+ },
+ },
+ {
+ id: "actions",
+ content: t("common.table.headers.actions"),
+ render: (row) => {
+ return ;
+ },
+ },
+ ];
+ return headers;
+ };
+
+ const headers = getHeaders(chartType);
+
+ return (
+
+ {
+ navigate(`/uptime/${row.id}`);
+ }}
+ />
+ setPage(newPage)}
+ onRowsPerPageChange={(e) => setRowsPerPage(Number(e.target.value))}
+ />
+
+ );
+};
diff --git a/client/src/Pages/v1/Uptime/Monitors/Hooks/useDebounce.jsx b/client/src/Pages/Uptime/Monitors/Hooks/useDebounce.jsx
similarity index 100%
rename from client/src/Pages/v1/Uptime/Monitors/Hooks/useDebounce.jsx
rename to client/src/Pages/Uptime/Monitors/Hooks/useDebounce.jsx
diff --git a/client/src/Pages/Uptime/Monitors/index.tsx b/client/src/Pages/Uptime/Monitors/index.tsx
new file mode 100644
index 000000000..5c3828d07
--- /dev/null
+++ b/client/src/Pages/Uptime/Monitors/index.tsx
@@ -0,0 +1,216 @@
+import { ControlsFilter } from "@/Components/v2/monitors";
+import {
+ MonitorBasePageWithStates,
+ UpStatusBox,
+ DownStatusBox,
+ PausedStatusBox,
+} from "@/Components/v2/design-elements";
+import { TextField, Dialog } from "@/Components/v2/inputs";
+import Stack from "@mui/material/Stack";
+import { MonitorTable } from "@/Pages/Uptime/Monitors/Components/UptimeMonitorsTable";
+import { HeaderCreate } from "@/Components/v2/common";
+
+import { useTranslation } from "react-i18next";
+import useMediaQuery from "@mui/material/useMediaQuery";
+import { useGet, useDelete } from "@/Hooks/UseApi";
+import type { Monitor, MonitorType, MonitorsWithChecksResponse } from "@/Types/Monitor";
+import { useState, useMemo, useCallback } from "react";
+import { useSelector, useDispatch } from "react-redux";
+import { setRowsPerPage } from "@/Features/UI/uiSlice.js";
+import { useIsAdmin } from "@/Hooks/useIsAdmin";
+import type { RootState } from "@/Types/state";
+import { useTheme } from "@mui/material";
+
+const UptimeMonitorsPage = () => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+ const isSmall = useMediaQuery(theme.breakpoints.down("md"));
+ const dispatch = useDispatch();
+ const isAdmin = useIsAdmin();
+ // Redux state
+ const rowsPerPage = useSelector(
+ (state: RootState) => state.ui?.monitors?.rowsPerPage ?? 10
+ );
+
+ // Local state
+ const [selectedTypes, setSelectedTypes] = useState([]);
+ const [selectedStatus, setSelectedStatus] = useState("");
+ const [selectedState, setSelectedState] = useState("");
+ const [page, setPage] = useState(0);
+ const [search, setSearch] = useState("");
+ const [sortField, setSortField] = useState("");
+ const [sortOrder, setSortOrder] = useState<"asc" | "desc">("asc");
+ const [selectedMonitor, setSelectedMonitor] = useState(null);
+ const isDialogOpen = Boolean(selectedMonitor);
+
+ // Convert filter selections to API filter values
+ // Status: "up" -> true, "down" -> false
+ // State: "active" -> true, "paused" -> false
+ const toFilterStatus = useMemo(() => {
+ if (selectedStatus === "up") return "true";
+ if (selectedStatus === "down") return "false";
+ return undefined;
+ }, [selectedStatus]);
+
+ const toFilterActive = useMemo(() => {
+ if (selectedState === "active") return "true";
+ if (selectedState === "paused") return "false";
+ return undefined;
+ }, [selectedState]);
+
+ // Determine field and filter for the API request
+ // Priority: status > isActive > search > sort
+ const filterLookup = new Map([
+ [toFilterStatus, "status"],
+ [toFilterActive, "isActive"],
+ ]);
+ const activeFilter = [...filterLookup].find(([key]) => key !== undefined);
+ const field = activeFilter?.[1] || (search ? "name" : sortField || undefined);
+ const filter = activeFilter?.[0] || search;
+
+ // Default to all types when none selected
+ const effectiveTypes =
+ selectedTypes.length > 0 ? selectedTypes : ["http", "ping", "docker", "port"];
+
+ // Build URL for monitors with checks
+ const monitorsWithChecksUrl = useMemo(() => {
+ const params = new URLSearchParams();
+ effectiveTypes.forEach((type) => params.append("type", type));
+ params.append("limit", "25");
+ if (page !== undefined) params.append("page", String(page));
+ if (rowsPerPage) params.append("rowsPerPage", String(rowsPerPage));
+ if (filter) params.append("filter", filter);
+ if (field) params.append("field", field);
+ if (sortOrder) params.append("order", sortOrder);
+ return `/monitors/team/with-checks?${params.toString()}`;
+ }, [effectiveTypes, page, rowsPerPage, filter, field, sortOrder]);
+
+ // Data fetching
+ const {
+ data: monitors,
+ isLoading: monitorsLoading,
+ error,
+ refetch: refetchMonitorsAndSummary,
+ } = useGet(
+ "/monitors/team?type=http&type=ping&type=port&type=docker",
+ {},
+ { keepPreviousData: true }
+ );
+
+ const {
+ data: monitorsWithChecksData,
+ isLoading: monitorsWithChecksLoading,
+ error: monitorsWithChecksError,
+ refetch,
+ } = useGet(
+ monitorsWithChecksUrl,
+ {},
+ { refreshInterval: 5000, keepPreviousData: true }
+ );
+
+ const { monitors: monitorsWithChecks, summary, count } = monitorsWithChecksData ?? {};
+
+ // Delete hook
+ const { deleteFn, loading: isDeleting } = useDelete();
+
+ // Handlers
+ const handleClearFilters = useCallback(() => {
+ setSelectedTypes([]);
+ setSelectedStatus("");
+ setSelectedState("");
+ setSearch("");
+ }, []);
+
+ const handleConfirm = async () => {
+ if (!selectedMonitor) return;
+ await deleteFn(`/monitors/${selectedMonitor.id}`);
+ setSelectedMonitor(null);
+ refetch();
+ refetchMonitorsAndSummary();
+ };
+
+ const handleCancel = () => {
+ setSelectedMonitor(null);
+ };
+
+ const isLoading = monitorsLoading || monitorsWithChecksLoading;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {
+ setSearch(event.target.value);
+ }}
+ />
+
+ {
+ dispatch(
+ setRowsPerPage({
+ value: rowsPerPage,
+ table: "monitors",
+ })
+ );
+ setPage(0);
+ }}
+ />
+
+
+ );
+};
+
+export default UptimeMonitorsPage;
diff --git a/client/src/Pages/v1/Uptime/Monitors/index.jsx b/client/src/Pages/Uptime/Monitors/old.jsx
similarity index 87%
rename from client/src/Pages/v1/Uptime/Monitors/index.jsx
rename to client/src/Pages/Uptime/Monitors/old.jsx
index cacfc23b4..67019362f 100644
--- a/client/src/Pages/v1/Uptime/Monitors/index.jsx
+++ b/client/src/Pages/Uptime/Monitors/old.jsx
@@ -5,7 +5,7 @@
// Components
import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
-import Greeting from "../../../../Utils/greeting.jsx";
+import Greeting from "../../../Utils/greeting.jsx";
import StatusBoxes from "./Components/StatusBoxes/index.jsx";
import UptimeDataTable from "./Components/UptimeDataTable/index.jsx";
import Pagination from "@/Components/v1/Table/TablePagination/index.jsx";
@@ -20,16 +20,16 @@ import MonitorCountHeader from "@/Components/v1/MonitorCountHeader/index.jsx";
import { Stack, Box, Button } from "@mui/material";
// Utils
import { useState, useCallback, useEffect } from "react";
-import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js";
+import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router-dom";
import { useSelector, useDispatch } from "react-redux";
-import { setRowsPerPage } from "../../../../Features/UI/uiSlice.js";
+import { setRowsPerPage } from "../../../Features/UI/uiSlice.js";
import PropTypes from "prop-types";
import {
- useFetchMonitorsWithSummary,
useFetchMonitorsWithChecks,
-} from "../../../../Hooks/v1/monitorHooks.js";
+ useFetchMonitorsByTeamId,
+} from "@/Hooks/monitorHooks.js";
import { useTranslation } from "react-i18next";
const TYPES = ["http", "ping", "docker", "port", "game"];
@@ -103,12 +103,6 @@ const UptimeMonitors = () => {
setMonitorUpdateTrigger((prev) => !prev);
}, []);
- const [monitors, monitorsSummary, monitorsWithSummaryIsLoading, networkError] =
- useFetchMonitorsWithSummary({
- types: TYPES,
- monitorUpdateTrigger,
- });
-
const handleReset = () => {
setSelectedState(undefined);
setSelectedTypes(undefined);
@@ -123,16 +117,17 @@ const UptimeMonitors = () => {
]);
const activeFilter = [...filterLookup].find(([key]) => key !== undefined);
- const field = activeFilter?.[1] || sort?.field;
+ const field = activeFilter?.[1] || (search ? "name" : sort?.field);
const filter = activeFilter?.[0] || search;
const effectiveTypes = selectedTypes?.length ? selectedTypes : TYPES;
const [
+ summary,
monitorsWithChecks,
monitorsWithChecksCount,
monitorsWithChecksIsLoading,
- monitorsWithChecksNetworkError,
+ networkError,
] = useFetchMonitorsWithChecks({
types: effectiveTypes,
limit: 25,
@@ -144,13 +139,17 @@ const UptimeMonitors = () => {
monitorUpdateTrigger,
});
+ const [monitors, listIsLoading, listNetworkError] = useFetchMonitorsByTeamId({
+ type: ["http", "ping", "docker", "port", "game"],
+ });
+
useEffect(() => {
if (isSearching) {
setPage(undefined);
}
}, [isSearching]);
- const isLoading = monitorsWithSummaryIsLoading || monitorsWithChecksIsLoading;
+ const isLoading = monitorsWithChecksIsLoading;
return (
<>
@@ -174,14 +173,14 @@ const UptimeMonitors = () => {
/>
{
- const levelColors = {
- info: theme.palette.success.main,
- warn: theme.palette.warning.main,
- error: theme.palette.error.main,
- debug: theme.palette.accent.main,
- };
-
- const color = levelColors[log.level] || theme.palette.primary.contrastText;
-
- return (
-
- [{log.timestamp}] {" "}
- {log.level.toUpperCase()}
- {": "}
- {`(${log.service})`}
- {`(${log.method})`}
- {": "}
- {log.message}
-
-
- );
-};
-
-const Logs = () => {
- // Local state
- const [logLevel, setLogLevel] = useState("all");
-
- // Hooks
- const theme = useTheme();
- const { t } = useTranslation();
- const [logs, isLoading, error] = useFetchLogs();
- // Setup
- const LOG_LEVELS = [
- { _id: "all", name: t("logsPage.logLevelSelect.values.all") },
- { _id: "info", name: t("logsPage.logLevelSelect.values.info") },
- { _id: "warn", name: t("logsPage.logLevelSelect.values.warn") },
- { _id: "error", name: t("logsPage.logLevelSelect.values.error") },
- { _id: "debug", name: t("logsPage.logLevelSelect.values.debug") },
- ];
- return (
-
-
-
- {t("logsPage.logLevelSelect.title")}
- {
- setLogLevel(e.target.value);
- }}
- />
-
-
-
-
- {logs
- ?.filter((log) => {
- if (logLevel === "all") return true;
- return log.level === logLevel;
- })
- .reverse()
- .map((log, idx) => formatLog(theme, log, idx))}
-
-
-
- );
-};
-
-export default Logs;
diff --git a/client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/index.jsx b/client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/index.jsx
deleted file mode 100644
index 45f35a42e..000000000
--- a/client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/index.jsx
+++ /dev/null
@@ -1,147 +0,0 @@
-// Components
-import { Stack, Typography, Box } from "@mui/material";
-import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx";
-import UptimeIcon from "../../../../../../assets/icons/uptime-icon.svg?react";
-import IncidentsIcon from "../../../../../../assets/icons/incidents.svg?react";
-import AverageResponseIcon from "../../../../../../assets/icons/average-response-icon.svg?react";
-import UpBarChart from "../Charts/UpBarChart.jsx";
-import DownBarChart from "../Charts/DownBarChart.jsx";
-import ResponseGaugeChart from "../Charts/ResponseGaugeChart.jsx";
-import SkeletonLayout from "./skeleton.jsx";
-// Utils
-import { formatDateWithTz } from "../../../../../../Utils/timeUtils.js";
-import PropTypes from "prop-types";
-import { useTheme } from "@emotion/react";
-import { useState } from "react";
-
-const ChartBoxes = ({
- isLoading = false,
- monitorData,
- dateRange,
- uiTimezone,
- dateFormat,
-}) => {
- // Local state
- const [hoveredUptimeData, setHoveredUptimeData] = useState(null);
- const [hoveredIncidentsData, setHoveredIncidentsData] = useState(null);
- const theme = useTheme();
- if (isLoading) {
- return ;
- }
-
- const noIncidentsMessage = "Great. No Incidents, yet!";
-
- return (
-
- }
- header="Uptime"
- isEmpty={monitorData?.groupedUpChecks?.length === 0}
- >
-
-
- Total Checks
-
- {hoveredUptimeData !== null
- ? hoveredUptimeData.totalChecks
- : (monitorData?.groupedUpChecks?.reduce((count, checkGroup) => {
- return count + checkGroup.totalChecks;
- }, 0) ?? 0)}
-
- {hoveredUptimeData !== null && hoveredUptimeData.time !== null && (
-
- {formatDateWithTz(hoveredUptimeData._id, dateFormat, uiTimezone)}
-
- )}
-
-
-
- {hoveredUptimeData !== null ? "Avg Response Time" : "Uptime Percentage"}
-
-
- {hoveredUptimeData !== null
- ? Math.floor(hoveredUptimeData?.originalAvgResponseTime ?? 0)
- : Math.floor((monitorData?.groupedUptimePercentage ?? 0) * 100)}
-
- {hoveredUptimeData !== null ? " ms" : " %"}
-
-
-
-
-
-
- }
- header="Incidents"
- noDataMessage={noIncidentsMessage}
- isEmpty={monitorData?.groupedDownChecks?.length === 0}
- >
-
-
-
- {hoveredIncidentsData !== null
- ? hoveredIncidentsData.totalChecks
- : (monitorData?.groupedDownChecks?.reduce((count, checkGroup) => {
- return count + checkGroup.totalChecks;
- }, 0) ?? 0)}
-
- {hoveredIncidentsData !== null && hoveredIncidentsData.time !== null && (
-
- {formatDateWithTz(hoveredIncidentsData._id, dateFormat, uiTimezone)}
-
- )}
-
-
-
-
- }
- header="Average Response Time"
- >
-
-
-
- );
-};
-
-export default ChartBoxes;
-
-ChartBoxes.propTypes = {
- isLoading: PropTypes.bool,
- monitorData: PropTypes.object,
- dateRange: PropTypes.string.isRequired,
- uiTimezone: PropTypes.string.isRequired,
- dateFormat: PropTypes.string.isRequired,
- hoveredUptimeData: PropTypes.object,
- setHoveredUptimeData: PropTypes.func,
- hoveredIncidentsData: PropTypes.object,
- setHoveredIncidentsData: PropTypes.func,
-};
diff --git a/client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/skeleton.jsx b/client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/skeleton.jsx
deleted file mode 100644
index 750b3b20a..000000000
--- a/client/src/Pages/v1/Uptime/Details/Components/ChartBoxes/skeleton.jsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { Skeleton, Stack } from "@mui/material";
-import { useTheme } from "@emotion/react";
-
-const SkeletonLayout = () => {
- const theme = useTheme();
- return (
-
-
-
-
-
- );
-};
-
-export default SkeletonLayout;
diff --git a/client/src/Pages/v1/Uptime/Details/Components/Charts/CustomLabels.jsx b/client/src/Pages/v1/Uptime/Details/Components/Charts/CustomLabels.jsx
deleted file mode 100644
index c9c1ff58d..000000000
--- a/client/src/Pages/v1/Uptime/Details/Components/Charts/CustomLabels.jsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import PropTypes from "prop-types";
-import { useSelector } from "react-redux";
-import { formatDateWithTz } from "../../../../../../Utils/timeUtils.js";
-
-const CustomLabels = ({ x, width, height, firstDataPoint, lastDataPoint, type }) => {
- const uiTimezone = useSelector((state) => state.ui.timezone);
- const dateFormat = type === "day" ? "MMM D, h:mm A" : "MMM D";
-
- return (
- <>
-
- {formatDateWithTz(firstDataPoint._id, dateFormat, uiTimezone)}
-
-
- {formatDateWithTz(lastDataPoint._id, dateFormat, uiTimezone)}
-
- >
- );
-};
-
-CustomLabels.propTypes = {
- x: PropTypes.number.isRequired,
- width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
- height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
- firstDataPoint: PropTypes.object,
- lastDataPoint: PropTypes.object,
- type: PropTypes.string.isRequired,
-};
-
-export default CustomLabels;
diff --git a/client/src/Pages/v1/Uptime/Details/Components/Charts/DownBarChart.jsx b/client/src/Pages/v1/Uptime/Details/Components/Charts/DownBarChart.jsx
deleted file mode 100644
index c5e08ecca..000000000
--- a/client/src/Pages/v1/Uptime/Details/Components/Charts/DownBarChart.jsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import { memo, useState } from "react";
-import { useTheme } from "@mui/material";
-import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts";
-import PropTypes from "prop-types";
-import CustomLabels from "./CustomLabels.jsx";
-
-const DownBarChart = memo(({ groupedDownChecks = [], type, onBarHover }) => {
- const theme = useTheme();
-
- const [chartHovered, setChartHovered] = useState(false);
- const [hoveredBarIndex, setHoveredBarIndex] = useState(null);
-
- return (
-
- {
- setChartHovered(true);
- onBarHover({ time: null, totalChecks: 0 });
- }}
- onMouseLeave={() => {
- setChartHovered(false);
- setHoveredBarIndex(null);
- onBarHover(null);
- }}
- >
-
- }
- />
-
- {groupedDownChecks?.map((entry, index) => {
- return (
- | {
- setHoveredBarIndex(index);
- onBarHover(entry);
- }}
- onMouseLeave={() => {
- setHoveredBarIndex(null);
- onBarHover({ time: null, totalChecks: 0 });
- }}
- />
- );
- })}
- |
-
-
- );
-});
-
-DownBarChart.displayName = "DownBarChart";
-DownBarChart.propTypes = {
- monitor: PropTypes.shape({
- groupedDownChecks: PropTypes.arrayOf(PropTypes.object),
- }),
- type: PropTypes.string,
- onBarHover: PropTypes.func,
-};
-export default DownBarChart;
diff --git a/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx b/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx
deleted file mode 100644
index 084a711c3..000000000
--- a/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx
+++ /dev/null
@@ -1,118 +0,0 @@
-import PropTypes from "prop-types";
-import { useTheme } from "@mui/material";
-import { ResponsiveContainer, RadialBarChart, RadialBar, Cell } from "recharts";
-import { useTranslation } from "react-i18next";
-
-const ResponseGaugeChart = ({ avgResponseTime }) => {
- const theme = useTheme();
- const { t } = useTranslation();
- let max = 1000; // max ms
-
- const data = [
- { response: max, fill: "transparent", background: false },
- { response: avgResponseTime, background: true },
- ];
- let responseTime = Math.floor(avgResponseTime);
- let responseProps =
- responseTime <= 200
- ? {
- category: "Excellent",
- main: theme.palette.success.main,
- bg: theme.palette.success.lowContrast,
- }
- : responseTime <= 500
- ? {
- category: "Fair",
- main: theme.palette.success.main,
- bg: theme.palette.success.lowContrast,
- }
- : responseTime <= 600
- ? {
- category: "Acceptable",
- main: theme.palette.warning.main,
- bg: theme.palette.warning.lowContrast,
- }
- : {
- category: "Poor",
- main: theme.palette.error.main,
- bg: theme.palette.error.lowContrast,
- };
-
- return (
-
-
-
- {t("low")}
-
-
- {t("high")}
-
-
- {responseProps.category}
-
-
- {responseTime} ms
-
-
- |
- |
-
-
-
- );
-};
-
-ResponseGaugeChart.propTypes = {
- avgResponseTime: PropTypes.number.isRequired,
-};
-
-export default ResponseGaugeChart;
diff --git a/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChart.jsx b/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChart.jsx
deleted file mode 100644
index daa7b4f53..000000000
--- a/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChart.jsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx";
-import MonitorDetailsAreaChart from "@/Components/v1/Charts/MonitorDetailsAreaChart/index.jsx";
-import ResponseTimeIcon from "../../../../../../assets/icons/response-time-icon.svg?react";
-import SkeletonLayout from "./ResponseTimeChartSkeleton.jsx";
-import PropTypes from "prop-types";
-
-const ResponseTImeChart = ({ isLoading = false, groupedChecks = [], dateRange }) => {
- if (isLoading) {
- return ;
- }
-
- return (
- }
- header="Response Times"
- >
-
-
- );
-};
-
-ResponseTImeChart.propTypes = {
- isLoading: PropTypes.bool,
- groupedChecks: PropTypes.array,
- dateRange: PropTypes.string,
-};
-
-export default ResponseTImeChart;
diff --git a/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx b/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx
deleted file mode 100644
index a70757e36..000000000
--- a/client/src/Pages/v1/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import { Skeleton } from "@mui/material";
-const ResponseTimeChartSkeleton = () => {
- return (
-
- );
-};
-
-export default ResponseTimeChartSkeleton;
diff --git a/client/src/Pages/v1/Uptime/Details/Components/Charts/UpBarChart.jsx b/client/src/Pages/v1/Uptime/Details/Components/Charts/UpBarChart.jsx
deleted file mode 100644
index 8f1af8912..000000000
--- a/client/src/Pages/v1/Uptime/Details/Components/Charts/UpBarChart.jsx
+++ /dev/null
@@ -1,107 +0,0 @@
-import { memo, useState } from "react";
-import { useTheme } from "@mui/material";
-import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts";
-import PropTypes from "prop-types";
-import CustomLabels from "./CustomLabels.jsx";
-
-const getThemeColor = (responseTime) => {
- if (responseTime < 200) {
- return "success";
- } else if (responseTime < 300) {
- return "warning";
- } else {
- return "error";
- }
-};
-
-const UpBarChart = memo(({ groupedUpChecks = [], type, onBarHover }) => {
- const theme = useTheme();
- const [chartHovered, setChartHovered] = useState(false);
- const [hoveredBarIndex, setHoveredBarIndex] = useState(null);
-
- return (
-
- {
- setChartHovered(true);
- onBarHover({ time: null, totalChecks: 0, originalAvgResponseTime: 0 });
- }}
- onMouseLeave={() => {
- setChartHovered(false);
- setHoveredBarIndex(null);
- onBarHover(null);
- }}
- >
-
- }
- />
-
- {groupedUpChecks?.map((entry, index) => {
- const themeColor = getThemeColor(entry.originalAvgResponseTime);
- return (
- | {
- setHoveredBarIndex(index);
- onBarHover(entry);
- }}
- onMouseLeave={() => {
- setHoveredBarIndex(null);
- onBarHover({
- time: null,
- totalChecks: 0,
- groupUptimePercentage: 0,
- });
- }}
- />
- );
- })}
- |
-
-
- );
-});
-
-// Add display name for the component
-UpBarChart.displayName = "UpBarChart";
-
-// Validate props using PropTypes
-UpBarChart.propTypes = {
- monitor: PropTypes.shape({
- groupedUpChecks: PropTypes.array,
- }),
- type: PropTypes.string,
- onBarHover: PropTypes.func,
-};
-export default UpBarChart;
diff --git a/client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx b/client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx
deleted file mode 100644
index 948489ac1..000000000
--- a/client/src/Pages/v1/Uptime/Details/Components/ResponseTable/index.jsx
+++ /dev/null
@@ -1,91 +0,0 @@
-import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx";
-import PropTypes from "prop-types";
-import HistoryIcon from "../../../../../../assets/icons/history-icon.svg?react";
-import Table from "@/Components/v1/Table/index.jsx";
-import TablePagination from "@/Components/v1/Table/TablePagination/index.jsx";
-import { StatusLabel } from "@/Components/v1/Label/index.jsx";
-import { useTranslation } from "react-i18next";
-import { formatDateWithTz } from "../../../../../../Utils/timeUtils.js";
-import SkeletonLayout from "./skeleton.jsx";
-const ResponseTable = ({
- isLoading = false,
- checks = [],
- checksCount,
- uiTimezone,
- page,
- setPage,
- rowsPerPage,
- setRowsPerPage,
-}) => {
- const { t } = useTranslation();
- if (isLoading) {
- return ;
- }
-
- const headers = [
- {
- id: "status",
- content: t("status"),
- render: (row) => {
- const status = row.status === true ? "up" : "down";
-
- return (
-
- );
- },
- },
- {
- id: "date",
- content: t("date&Time"),
- render: (row) =>
- formatDateWithTz(row.createdAt, "ddd, MMMM D, YYYY, HH:mm A", uiTimezone),
- },
- {
- id: "statusCode",
- content: t("statusCode"),
- render: (row) => (row.statusCode ? row.statusCode : "N/A"),
- },
- {
- id: "message",
- content: t("message"),
- render: (row) => row.message,
- },
- ];
-
- return (
- }
- header="Response Times"
- height="100%"
- >
-
-
-
- );
-};
-
-ResponseTable.propTypes = {
- isLoading: PropTypes.bool,
- checks: PropTypes.array,
- checksCount: PropTypes.number,
- uiTimezone: PropTypes.string.isRequired,
- page: PropTypes.number.isRequired,
- setPage: PropTypes.func.isRequired,
- rowsPerPage: PropTypes.number.isRequired,
- setRowsPerPage: PropTypes.func.isRequired,
-};
-
-export default ResponseTable;
diff --git a/client/src/Pages/v1/Uptime/Details/Components/ResponseTable/skeleton.jsx b/client/src/Pages/v1/Uptime/Details/Components/ResponseTable/skeleton.jsx
deleted file mode 100644
index 2665ffaf1..000000000
--- a/client/src/Pages/v1/Uptime/Details/Components/ResponseTable/skeleton.jsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { Skeleton } from "@mui/material";
-
-const SkeletonLayout = () => {
- return (
-
- );
-};
-
-export default SkeletonLayout;
diff --git a/client/src/Pages/v1/Uptime/Details/Components/UptimeStatusBoxes/index.jsx b/client/src/Pages/v1/Uptime/Details/Components/UptimeStatusBoxes/index.jsx
deleted file mode 100644
index a7b971839..000000000
--- a/client/src/Pages/v1/Uptime/Details/Components/UptimeStatusBoxes/index.jsx
+++ /dev/null
@@ -1,81 +0,0 @@
-import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx";
-import StatBox from "@/Components/v1/StatBox/index.jsx";
-
-import PropTypes from "prop-types";
-import { getHumanReadableDuration } from "../../../../../../Utils/timeUtils.js";
-import { useTheme } from "@mui/material/styles";
-import { Typography } from "@mui/material";
-import { useMonitorUtils } from "../../../../../../Hooks/v1/useMonitorUtils.js";
-
-const UptimeStatusBoxes = ({
- isLoading = false,
- monitor,
- monitorStats,
- certificateExpiry,
-}) => {
- const theme = useTheme();
- const { determineState } = useMonitorUtils();
-
- // Determine time since last failure
- const timeOfLastFailure = monitorStats?.timeOfLastFailure;
- const timeSinceLastFailure = timeOfLastFailure > 0 ? Date.now() - timeOfLastFailure : 0;
-
- // Determine time since last check
- const timeOfLastCheck = monitorStats?.lastCheckTimestamp;
- const timeSinceLastCheck = Date.now() - timeOfLastCheck;
-
- const streakTime = getHumanReadableDuration(timeSinceLastFailure);
-
- const lastCheckTime = getHumanReadableDuration(timeSinceLastCheck);
- return (
-
-
-
- {lastCheckTime}
- {"ago"}
- >
- }
- />
-
- {monitorStats?.lastResponseTime}
- {"ms"}
- >
- }
- />
- {monitor?.type === "http" && (
-
- {certificateExpiry}
-
- }
- />
- )}
-
- );
-};
-
-UptimeStatusBoxes.propTypes = {
- isLoading: PropTypes.bool,
- monitor: PropTypes.object,
- monitorStats: PropTypes.object,
- certificateExpiry: PropTypes.string,
-};
-
-export default UptimeStatusBoxes;
diff --git a/client/src/Pages/v2/Auth/Login.tsx b/client/src/Pages/v2/Auth/Login.tsx
deleted file mode 100644
index 79ed2329f..000000000
--- a/client/src/Pages/v2/Auth/Login.tsx
+++ /dev/null
@@ -1,136 +0,0 @@
-import { AuthBasePage } from "@/Components/v2/Auth";
-import { Button } from "@/Components/v2/Inputs";
-import Stack from "@mui/material/Stack";
-import { TextInput, TextLink } from "@/Components/v2/Inputs";
-
-import type { ApiResponse } from "@/Hooks/v2/UseApi";
-
-import { zodResolver } from "@hookform/resolvers/zod";
-import { usePost } from "@/Hooks/v2/UseApi";
-import { useNavigate } from "react-router";
-import { useTheme } from "@mui/material/styles";
-import { useDispatch } from "react-redux";
-import { setIsAuthenticated } from "@/Features/Auth/v2AuthSlice";
-import { useTranslation } from "react-i18next";
-import { z } from "zod";
-import { useForm, Controller } from "react-hook-form";
-
-const schema = z.object({
- email: z.email("Invalid email address"),
- password: z.string().min(6, "Password must be at least 6 characters"),
-});
-
-type FormData = z.infer;
-
-const Login = () => {
- const { t } = useTranslation();
- const dispatch = useDispatch();
- const theme = useTheme();
- const { post, loading } = usePost();
- const navigate = useNavigate();
-
- const {
- handleSubmit,
- control,
- formState: { errors },
- } = useForm({
- resolver: zodResolver(schema),
- defaultValues: {
- email: "",
- password: "",
- },
- });
-
- const onSubmit = async (data: FormData) => {
- const result = await post("/auth/login", data);
- if (result) {
- dispatch(setIsAuthenticated({ authenticated: true }));
- navigate("/v2/uptime");
- } else {
- dispatch(setIsAuthenticated({ authenticated: false }));
- }
- };
-
- return (
-
-
-
- (
-
- )}
- />
- (
-
- )}
- />
-
- Login
-
-
-
-
-
-
- );
-};
-
-export default Login;
diff --git a/client/src/Pages/v2/Auth/Register.tsx b/client/src/Pages/v2/Auth/Register.tsx
deleted file mode 100644
index d4565c66c..000000000
--- a/client/src/Pages/v2/Auth/Register.tsx
+++ /dev/null
@@ -1,177 +0,0 @@
-import { AuthBasePage } from "@/Components/v2/Auth";
-import { TextInput } from "@/Components/v2/Inputs";
-import { Button } from "@/Components/v2/Inputs";
-import Typography from "@mui/material/Typography";
-import Stack from "@mui/material/Stack";
-
-import type { ApiResponse } from "@/Hooks/v2/UseApi";
-import { zodResolver } from "@hookform/resolvers/zod";
-import { z } from "zod";
-import { useTranslation } from "react-i18next";
-import { useForm, Controller } from "react-hook-form";
-import { useTheme } from "@mui/material/styles";
-import { usePost } from "@/Hooks/v2/UseApi";
-import { useNavigate } from "react-router";
-
-const schema = z
- .object({
- email: z.email({ message: "Invalid email address" }),
- firstName: z.string().min(1, { message: "First Name is required" }),
- lastName: z.string().min(1, { message: "Last Name is required" }),
- password: z.string().min(6, { message: "Password must be at least 6 characters" }),
- confirmPassword: z
- .string()
- .min(6, { message: "Confirm Password must be at least 6 characters" }),
- })
- .refine((data) => data.password === data.confirmPassword, {
- path: ["confirmPassword"],
- message: "Passwords must match",
- });
-
-type FormData = z.infer;
-
-const Register = () => {
- const { t } = useTranslation();
- const theme = useTheme();
- const navigate = useNavigate();
- const { post, loading, error } = usePost();
-
- const {
- handleSubmit,
- control,
- formState: { errors },
- } = useForm({
- resolver: zodResolver(schema),
- defaultValues: {
- email: "",
- password: "",
- },
- });
-
- const onSubmit = async (data: FormData) => {
- const result = await post("/auth/register", data);
- if (result) {
- navigate("/v2/uptime");
- } else {
- console.error("Login failed:", error);
- }
- };
-
- return (
-
-
-
- (
-
- )}
- />
- (
-
- )}
- />
- (
-
- )}
- />
- (
-
- )}
- />
- (
-
- )}
- />
-
- Register
-
- {error && {error} }
-
-
-
- );
-};
-
-export default Register;
diff --git a/client/src/Pages/v2/Uptime/Configure.tsx b/client/src/Pages/v2/Uptime/Configure.tsx
deleted file mode 100644
index dc014669b..000000000
--- a/client/src/Pages/v2/Uptime/Configure.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import { monitorSchema } from "@/Validation/v2/zod";
-import { useGet, usePost } from "@/Hooks/v2/UseApi";
-import { UptimeForm } from "@/Pages/v2/Uptime/UptimeForm";
-
-import { useParams } from "react-router";
-import type { ApiResponse } from "@/Hooks/v2/UseApi";
-import humanInterval from "human-interval";
-import { z } from "zod";
-
-export const UptimeConfigurePage = () => {
- type FormValues = z.infer;
- type SubmitValues = Omit & { interval: number | undefined };
-
- const { id } = useParams();
- const { response } = useGet("/notification-channels");
- const { response: monitorResponse } = useGet(`/monitors/${id}`);
- const monitor = monitorResponse?.data || null;
- const notificationOptions = response?.data ?? [];
-
- const { post, loading, error } = usePost();
-
- const onSubmit = async (data: FormValues) => {
- let interval = humanInterval(data.interval);
- if (!interval) interval = 60000;
- const submitData = { ...data, interval };
- const result = await post("/monitors", submitData);
- if (result) {
- console.log(result);
- } else {
- console.error(error);
- }
- };
- return (
-
- );
-};
diff --git a/client/src/Pages/v2/Uptime/Create.tsx b/client/src/Pages/v2/Uptime/Create.tsx
deleted file mode 100644
index 38b1511f4..000000000
--- a/client/src/Pages/v2/Uptime/Create.tsx
+++ /dev/null
@@ -1,262 +0,0 @@
-import Stack from "@mui/material/Stack";
-import { TextInput } from "@/Components/v2/Inputs/TextInput";
-import { AutoCompleteInput } from "@/Components/v2/Inputs/AutoComplete";
-import { ConfigBox, BasePage } from "@/Components/v2/DesignElements";
-import RadioGroup from "@mui/material/RadioGroup";
-import FormControl from "@mui/material/FormControl";
-import { RadioWithDescription } from "@/Components/v2/Inputs/RadioInput";
-import { Button } from "@/Components/v2/Inputs";
-import DeleteOutlineRoundedIcon from "@mui/icons-material/DeleteOutlineRounded";
-import { Typography } from "@mui/material";
-import humanInterval from "human-interval";
-
-import { useTranslation } from "react-i18next";
-import { useForm, Controller, useWatch } from "react-hook-form";
-import { z } from "zod";
-import { monitorSchema } from "@/Validation/v2/zod";
-import { useTheme } from "@mui/material/styles";
-import { zodResolver } from "@hookform/resolvers/zod";
-import { useGet, usePost } from "@/Hooks/v2/UseApi";
-import type { ApiResponse } from "@/Hooks/v2/UseApi";
-
-const UptimeCreatePage = () => {
- const { t } = useTranslation();
- const theme = useTheme();
-
- type FormValues = z.infer;
- type SubmitValues = Omit & { interval: number | undefined };
-
- const {
- handleSubmit,
- control,
- setValue,
- formState: { errors },
- } = useForm({
- resolver: zodResolver(monitorSchema) as any,
- defaultValues: {
- type: "http",
- url: "",
- n: 3,
- notificationChannels: [],
- name: "",
- interval: "1 minute",
- },
- mode: "onChange",
- });
- const { response } = useGet("/notification-channels");
- const { post, loading, error } = usePost();
- const selectedType = useWatch({
- control,
- name: "type",
- });
- const notificationChannels = useWatch({
- control,
- name: "notificationChannels",
- });
-
- const onSubmit = async (data: FormValues) => {
- let interval = humanInterval(data.interval);
- if (!interval) interval = 60000;
- const submitData = { ...data, interval };
- const result = await post("/monitors", submitData);
- if (result) {
- console.log(result);
- } else {
- console.error(error);
- }
- };
-
- const notificationOptions = response?.data ?? [];
- return (
-
- }
- />
- (
- {
- const target = e.target as HTMLInputElement;
- field.onChange(target.valueAsNumber);
- }}
- />
- )}
- />
- }
- />
-
- (
- option.name}
- value={notificationOptions.filter((o: any) =>
- (field.value || []).includes(o._id)
- )}
- onChange={(_, newValue) => {
- field.onChange(newValue.map((o: any) => o._id));
- }}
- />
- )}
- />
-
- {notificationChannels.map((notificationId) => {
- const option = notificationOptions.find(
- (o: any) => o._id === notificationId
- );
- if (!option) return null;
- return (
-
- {option.name}
- {
- const updated = notificationChannels.filter(
- (id) => id !== notificationId
- );
- setValue("notificationChannels", updated);
- }}
- sx={{ cursor: "pointer" }}
- />
-
- );
- })}
-
-
- }
- />
- (
-
- )}
- />
- }
- />
-
-
- {t("settingsSave")}
-
-
-
-
- );
-};
-
-export default UptimeCreatePage;
diff --git a/client/src/Pages/v2/Uptime/Details.tsx b/client/src/Pages/v2/Uptime/Details.tsx
deleted file mode 100644
index 5515e7b32..000000000
--- a/client/src/Pages/v2/Uptime/Details.tsx
+++ /dev/null
@@ -1,157 +0,0 @@
-import { BasePage } from "@/Components/v2/DesignElements";
-import { HeaderControls } from "@/Components/v2/Monitors/HeaderControls";
-import Stack from "@mui/material/Stack";
-import { StatBox } from "@/Components/v2/DesignElements";
-import { HistogramStatus } from "@/Components/v2/Monitors/HistogramStatus";
-import { ChartAvgResponse } from "@/Components/v2/Monitors/ChartAvgResponse";
-import { ChartResponseTime } from "@/Components/v2/Monitors/ChartResponseTime";
-import { HeaderRange } from "@/Components/v2/Monitors/HeaderRange";
-import { CheckTable } from "@/Pages/v2/Uptime/CheckTable";
-
-import type { IMonitor } from "@/Types/Monitor";
-import { useMediaQuery } from "@mui/material";
-import { useTheme } from "@mui/material/styles";
-import { useParams } from "react-router";
-import { useGet, usePatch, type ApiResponse } from "@/Hooks/v2/UseApi";
-import { useState } from "react";
-import { getStatusPalette } from "@/Utils/v2/MonitorUtils";
-import prettyMilliseconds from "pretty-ms";
-
-const UptimeDetailsPage = () => {
- const { id } = useParams();
- const theme = useTheme();
- const isSmall = useMediaQuery(theme.breakpoints.down("md"));
-
- // Local state
- const [range, setRange] = useState("2h");
-
- const { response, isValidating, error, refetch } = useGet(
- `/monitors/${id}?embedChecks=true&range=${range}`,
-
- {},
- { refreshInterval: 30000, keepPreviousData: true }
- );
-
- const {
- response: upResponse,
- isValidating: upIsValidating,
- error: upError,
- } = useGet(
- `/monitors/${id}?embedChecks=true&range=${range}&status=up`,
- {},
- { keepPreviousData: true }
- );
-
- const {
- response: downResponse,
- error: downError,
- isValidating: downIsValidating,
- } = useGet(
- `/monitors/${id}?embedChecks=true&range=${range}&status=down`,
- {},
- { keepPreviousData: true }
- );
-
- const { patch, loading: isPatching, error: postError } = usePatch();
-
- const monitor: IMonitor = response?.data?.monitor;
-
- if (!monitor) {
- return null;
- }
-
- const stats = response?.data?.stats || null;
- const avgResponseTime = stats?.avgResponseTime || 0;
- const maxResponseTime = stats?.maxResponseTime || 0;
-
- const streakDuration = stats?.currentStreakStartedAt
- ? Date.now() - stats?.currentStreakStartedAt
- : 0;
-
- const lastChecked = stats?.lastCheckTimestamp
- ? Date.now() - stats?.lastCheckTimestamp
- : -1;
-
- const checks = response?.data?.checks || [];
- const upChecks = upResponse?.data?.checks ? [...upResponse.data.checks].reverse() : [];
- const downChecks = downResponse?.data?.checks
- ? [...downResponse.data.checks].reverse()
- : [];
-
- const palette = getStatusPalette(monitor?.status);
-
- if (error || upError || downError || postError) {
- console.error("Error fetching monitor data:", {
- error,
- upError,
- downError,
- postError,
- });
- }
-
- return (
-
-
-
-
- = 0
- ? `${prettyMilliseconds(lastChecked, { secondsDecimalDigits: 0 })} ago`
- : "N/A"
- }
- />
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-export default UptimeDetailsPage;
diff --git a/client/src/Pages/v2/Uptime/MonitorTable.tsx b/client/src/Pages/v2/Uptime/MonitorTable.tsx
deleted file mode 100644
index 209784309..000000000
--- a/client/src/Pages/v2/Uptime/MonitorTable.tsx
+++ /dev/null
@@ -1,158 +0,0 @@
-import Stack from "@mui/material/Stack";
-import Typography from "@mui/material/Typography";
-import { Table } from "@/Components/v2/DesignElements";
-import { HistogramResponseTime } from "@/Components/v2/Monitors/HistogramResponseTime";
-import type { Header } from "@/Components/v2/DesignElements/Table";
-import { ActionsMenu } from "@/Components/v2/ActionsMenu";
-import { StatusLabel } from "@/Components/v2/DesignElements";
-
-import { useTranslation } from "react-i18next";
-import { useMediaQuery } from "@mui/material";
-import { useTheme } from "@mui/material/styles";
-import { useNavigate } from "react-router-dom";
-import { usePatch } from "@/Hooks/v2/UseApi";
-import type { ApiResponse } from "@/Hooks/v2/UseApi";
-
-import type { IMonitor } from "@/Types/Monitor";
-import type { ActionMenuItem } from "@/Components/v2/ActionsMenu";
-
-export const MonitorTable = ({
- monitors,
- refetch,
-}: {
- monitors: IMonitor[];
- refetch: Function;
-}) => {
- const { t } = useTranslation();
- const theme = useTheme();
- const isSmall = useMediaQuery(theme.breakpoints.down("md"));
- const navigate = useNavigate();
- const {
- patch,
- // loading: isPatching,
- // error: postError,
- } = usePatch();
-
- const getActions = (monitor: IMonitor): ActionMenuItem[] => {
- return [
- {
- id: 1,
- label: "Open site",
- action: () => {
- window.open(monitor.url, "_blank", "noreferrer");
- },
- closeMenu: true,
- },
- {
- id: 2,
- label: "Details",
- action: () => {
- navigate(`${monitor._id}`);
- },
- },
- {
- id: 3,
- label: "Incidents",
- action: () => {
- navigate(`/v2/incidents/${monitor._id}`);
- },
- },
- {
- id: 4,
- label: "Configure",
- action: () => {
- console.log("Open configure");
- },
- },
- {
- id: 5,
- label: "Clone",
- action: () => {
- console.log("Open clone");
- },
- },
- {
- id: 6,
- label: monitor.isActive ? "Pause" : "Resume",
- action: async () => {
- await patch(`/monitors/${monitor._id}/active`);
- refetch();
- },
- closeMenu: true,
- },
- {
- id: 7,
- label: Remove ,
- action: () => {
- console.log("Open delete");
- },
- closeMenu: true,
- },
- ];
- };
-
- const getHeaders = () => {
- const headers: Header[] = [
- {
- id: "name",
- content: t("host"),
- render: (row) => {
- return row.name;
- },
- },
- {
- id: "status",
- content: t("status"),
- render: (row) => {
- return (
-
- );
- },
- },
- {
- id: "histogram",
- content: t("responseTime"),
- render: (row) => {
- return (
-
-
-
- );
- },
- },
- {
- id: "type",
- content: t("type"),
- render: (row) => {
- return row.type;
- },
- },
- {
- id: "actions",
- content: t("actions"),
- render: (row) => {
- return ;
- },
- },
- ];
- return headers;
- };
-
- let headers = getHeaders();
-
- if (isSmall) {
- headers = headers.filter((h) => h.id !== "histogram");
- }
- return (
- {
- navigate(row._id);
- }}
- />
- );
-};
diff --git a/client/src/Pages/v2/Uptime/UptimeForm.tsx b/client/src/Pages/v2/Uptime/UptimeForm.tsx
deleted file mode 100644
index e9636e5dd..000000000
--- a/client/src/Pages/v2/Uptime/UptimeForm.tsx
+++ /dev/null
@@ -1,247 +0,0 @@
-import Stack from "@mui/material/Stack";
-import { TextInput } from "@/Components/v2/Inputs/TextInput";
-import { AutoCompleteInput } from "@/Components/v2/Inputs/AutoComplete";
-import { ConfigBox, BasePage } from "@/Components/v2/DesignElements";
-import RadioGroup from "@mui/material/RadioGroup";
-import FormControl from "@mui/material/FormControl";
-import { RadioWithDescription } from "@/Components/v2/Inputs/RadioInput";
-import { Button } from "@/Components/v2/Inputs";
-import DeleteOutlineRoundedIcon from "@mui/icons-material/DeleteOutlineRounded";
-import { Typography } from "@mui/material";
-
-import { useTranslation } from "react-i18next";
-import { monitorSchema } from "@/Validation/v2/zod";
-import { z } from "zod";
-import { zodResolver } from "@hookform/resolvers/zod";
-import { useForm, Controller, useWatch, type SubmitHandler } from "react-hook-form";
-import { useTheme } from "@mui/material/styles";
-import { useInitForm } from "@/Hooks/v2/useInitMonitorForm";
-
-type FormValues = z.infer;
-
-export const UptimeForm = ({
- initialData,
- onSubmit,
- notificationOptions,
- loading,
-}: {
- initialData?: Partial;
- onSubmit: SubmitHandler;
- notificationOptions: any[];
- loading: boolean;
-}) => {
- const { t } = useTranslation();
- const theme = useTheme();
- const { defaults } = useInitForm({ initialData: initialData });
- const {
- handleSubmit,
- control,
- setValue,
- formState: { errors },
- } = useForm({
- resolver: zodResolver(monitorSchema) as any,
- defaultValues: defaults,
- mode: "onChange",
- });
-
- const selectedType = useWatch({
- control,
- name: "type",
- });
- const notificationChannels = useWatch({
- control,
- name: "notificationChannels",
- });
-
- return (
-
- (
-
-
-
-
-
-
-
- )}
- />
- }
- />
-
- (
-
- )}
- />
- (
-
- )}
- />
-
- }
- />
- (
- {
- const target = e.target as HTMLInputElement;
- field.onChange(target.valueAsNumber);
- }}
- />
- )}
- />
- }
- />
-
- (
- option.name}
- value={notificationOptions.filter((o: any) =>
- (field.value || []).includes(o._id)
- )}
- onChange={(_, newValue) => {
- field.onChange(newValue.map((o: any) => o._id));
- }}
- />
- )}
- />
-
- {notificationChannels.map((notificationId) => {
- const option = notificationOptions.find(
- (o: any) => o._id === notificationId
- );
- if (!option) return null;
- return (
-
- {option.name}
- {
- const updated = notificationChannels.filter(
- (id) => id !== notificationId
- );
- setValue("notificationChannels", updated);
- }}
- sx={{ cursor: "pointer" }}
- />
-
- );
- })}
-
-
- }
- />
- (
-
- )}
- />
- }
- />
-
-
- {t("settingsSave")}
-
-
-
- );
-};
diff --git a/client/src/Pages/v2/Uptime/UptimeMonitors.tsx b/client/src/Pages/v2/Uptime/UptimeMonitors.tsx
deleted file mode 100644
index e5a151eb1..000000000
--- a/client/src/Pages/v2/Uptime/UptimeMonitors.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import {
- BasePageWithStates,
- UpStatusBox,
- DownStatusBox,
- PausedStatusBox,
-} from "@/Components/v2/DesignElements";
-import { HeaderCreate } from "@/Components/v2/Monitors";
-import Stack from "@mui/material/Stack";
-import { MonitorTable } from "@/Pages/v2/Uptime/MonitorTable";
-
-import { useTheme } from "@mui/material/styles";
-import { useGet } from "@/Hooks/v2/UseApi";
-import type { ApiResponse } from "@/Hooks/v2/UseApi";
-import type { IMonitor } from "@/Types/Monitor";
-import { useMediaQuery } from "@mui/material";
-
-const UptimeMonitors = () => {
- const theme = useTheme();
- const isSmall = useMediaQuery(theme.breakpoints.down("md"));
-
- const { response, isValidating, error, refetch } = useGet(
- "/monitors?embedChecks=true",
- {},
- { refreshInterval: 30000, keepPreviousData: true }
- );
- const monitors: IMonitor[] = response?.data ?? ([] as IMonitor[]);
-
- const monitorStatuses = monitors.reduce(
- (acc, monitor) => {
- if (monitor.status === "up") {
- acc.up += 1;
- } else if (monitor.status === "down") {
- acc.down += 1;
- } else if (monitor.isActive === false) {
- acc.paused += 1;
- }
- return acc;
- },
- {
- up: 0,
- down: 0,
- paused: 0,
- }
- );
-
- return (
-
-
-
-
-
-
-
-
-
- );
-};
-
-export default UptimeMonitors;
diff --git a/client/src/Routes/index.jsx b/client/src/Routes/index.jsx
index 90dcaa3f5..45715a488 100644
--- a/client/src/Routes/index.jsx
+++ b/client/src/Routes/index.jsx
@@ -1,71 +1,71 @@
+// Temp v2 theme
+import { ThemeProvider } from "@mui/material";
+import { lightTheme, darkTheme } from "@/Utils/Theme/v2Theme";
+
import { useSelector } from "react-redux";
-import { lightTheme, darkTheme } from "@/Utils/Theme/v2/theme";
import { Navigate, Route, Routes as LibRoutes } from "react-router";
import HomeLayout from "@/Components/v1/Layouts/HomeLayout";
-import NotFound from "../Pages/v1/NotFound";
+import NotFound from "../Pages/NotFound/index.jsx";
// Auth
-import AuthLogin from "../Pages/v1/Auth/Login";
-import AuthRegister from "../Pages/v1/Auth/Register/";
-import AuthForgotPassword from "../Pages/v1/Auth/ForgotPassword";
-import AuthCheckEmail from "../Pages/v1/Auth/CheckEmail";
-import AuthSetNewPassword from "../Pages/v1/Auth/SetNewPassword";
-import AuthNewPasswordConfirmed from "../Pages/v1/Auth/NewPasswordConfirmed";
+import AuthLogin from "../Pages/Auth/Login/index.jsx";
+import AuthRegister from "../Pages/Auth/Register/index.jsx";
+import AuthForgotPassword from "../Pages/Auth/ForgotPassword.jsx";
+import AuthCheckEmail from "../Pages/Auth/CheckEmail.jsx";
+import AuthSetNewPassword from "../Pages/Auth/SetNewPassword.jsx";
+import AuthNewPasswordConfirmed from "../Pages/Auth/NewPasswordConfirmed.jsx";
// Uptime
-import Uptime from "../Pages/v1/Uptime/Monitors";
-import UptimeDetails from "../Pages/v1/Uptime/Details";
-import UptimeCreate from "../Pages/v1/Uptime/Create";
+import Uptime from "../Pages/Uptime/Monitors";
+import UptimeDetails from "../Pages/Uptime/Details";
+import UptimeCreate from "../Pages/Uptime/Create/index.jsx";
// PageSpeed
-import PageSpeed from "../Pages/v1/PageSpeed/Monitors";
-import PageSpeedDetails from "../Pages/v1/PageSpeed/Details";
-import PageSpeedCreate from "../Pages/v1/PageSpeed/Create";
+import PageSpeed from "../Pages/PageSpeed/Monitors/index";
+import PageSpeedDetails from "../Pages/PageSpeed/Details/";
+import PageSpeedCreate from "../Pages/PageSpeed/Create/index.jsx";
// Infrastructure
-import Infrastructure from "../Pages/v1/Infrastructure/Monitors";
-import InfrastructureCreate from "../Pages/v1/Infrastructure/Create";
-import InfrastructureDetails from "../Pages/v1/Infrastructure/Details";
+import Infrastructure from "../Pages/Infrastructure/Monitors/index.jsx";
+import InfrastructureCreate from "../Pages/Infrastructure/Create/index.jsx";
+import InfrastructureDetails from "../Pages/Infrastructure/Details/index.jsx";
// Server Status
-import ServerUnreachable from "../Pages/v1/ServerUnreachable.jsx";
+import ServerUnreachable from "../Pages/ServerUnreachable.jsx";
+
+// Checks
+import Checks from "../Pages/Checks/index.jsx";
// Incidents
-import Incidents from "../Pages/v1/Incidents";
+import Incidents from "../Pages/Incidents/index.jsx";
// Status pages
-import CreateStatus from "../Pages/v1/StatusPage/Create";
-import StatusPages from "../Pages/v1/StatusPage/StatusPages";
-import Status from "../Pages/v1/StatusPage/Status";
+import CreateStatus from "../Pages/StatusPage/Create/index.jsx";
+import StatusPages from "../Pages/StatusPage/StatusPages/index.jsx";
+import Status from "../Pages/StatusPage/Status/index.jsx";
-import Notifications from "../Pages/v1/Notifications";
-import CreateNotifications from "../Pages/v1/Notifications/create";
+import Notifications from "../Pages/Notifications/index.jsx";
+import CreateNotifications from "../Pages/Notifications/create/index.jsx";
// Settings
-import Account from "../Pages/v1/Account";
-import EditUser from "../Pages/v1/Account/EditUser";
-import Settings from "../Pages/v1/Settings";
+import Account from "../Pages/Account/index.jsx";
+import EditUser from "../Pages/Account/EditUser/index.jsx";
+import Settings from "../Pages/Settings";
-import Maintenance from "../Pages/v1/Maintenance";
+import Maintenance from "../Pages/Maintenance/index.jsx";
import ProtectedRoute from "../Components/v1/ProtectedRoute";
import RoleProtectedRoute from "../Components/v1/RoleProtectedRoute";
-import CreateNewMaintenanceWindow from "../Pages/v1/Maintenance/CreateMaintenance";
+import CreateNewMaintenanceWindow from "../Pages/Maintenance/CreateMaintenance/index.jsx";
import withAdminCheck from "@/Components/v1/HOC/withAdminCheck";
-import BulkImport from "../Pages/v1/Uptime/BulkImport";
-import Logs from "../Pages/v1/Logs";
-
-import V2Routes from "@/Routes/v2router";
+import BulkImport from "../Pages/Uptime/BulkImport/index.jsx";
+import Logs from "../Pages/Logs/index.jsx";
const Routes = () => {
const mode = useSelector((state) => state.ui.mode);
const AdminCheckedRegister = withAdminCheck(AuthRegister);
+ const v2theme = mode === "light" ? lightTheme : darkTheme;
return (
- }
- />
-
{
/>
}
+ element={
+ <>
+
+
+
+ >
+ }
/>
{
/>
}
+ element={
+ <>
+
+
+
+ >
+ }
/>
{
}
+ element={
+ <>
+
+
+
+ >
+ }
/>
{
/>
}
+ element={
+ <>
+
+
+
+ >
+ }
/>
{
path="infrastructure/:monitorId"
element={ }
/>
+ }
+ />
}
diff --git a/client/src/Routes/v2router.tsx b/client/src/Routes/v2router.tsx
deleted file mode 100644
index 0e53deead..000000000
--- a/client/src/Routes/v2router.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import { Routes, Route } from "react-router";
-import { ThemeProvider } from "@emotion/react";
-import { lightTheme, darkTheme } from "@/Utils/Theme/v2/theme";
-
-import AuthLoginV2 from "@/Pages/v2/Auth/Login";
-import AuthRegisterV2 from "@/Pages/v2/Auth/Register";
-import UptimeMonitorsPage from "@/Pages/v2/Uptime/UptimeMonitors";
-import UptimeCreatePage from "@/Pages/v2/Uptime/Create";
-import UptimeDetailsPage from "@/Pages/v2/Uptime/Details";
-import RootLayout from "@/Components/v2/Layouts/RootLayout";
-
-const V2Routes = ({ mode = "light" }) => {
- const v2Theme = mode === "light" ? lightTheme : darkTheme;
-
- return (
-
-
- }
- />
- }
- />
- }
- >
- }
- />
- }
- />
- }
- />
- }
- />
-
-
-
- );
-};
-
-export default V2Routes;
diff --git a/client/src/Types/Check.ts b/client/src/Types/Check.ts
index cccc56f3d..05063291f 100644
--- a/client/src/Types/Check.ts
+++ b/client/src/Types/Check.ts
@@ -1,50 +1,245 @@
-export interface CheckTimingPhases {
- wait: number;
- dns: number;
- tcp: number;
- tls: number;
- request: number;
- firstByte: number;
- download: number;
- total: number;
+export interface CheckMetadata {
+ monitorId: string;
+ teamId: string;
+ type:
+ | "http"
+ | "ping"
+ | "pagespeed"
+ | "hardware"
+ | "docker"
+ | "port"
+ | "game"
+ | "unknown";
+}
+
+export interface CheckCpuInfo {
+ physical_core?: number;
+ logical_core?: number;
+ frequency?: number;
+ temperature?: number[];
+ free_percent?: number;
+ usage_percent?: number;
+}
+
+export interface CheckMemoryInfo {
+ total_bytes?: number;
+ available_bytes?: number;
+ used_bytes?: number;
+ usage_percent?: number;
+}
+
+export interface CheckHostInfo {
+ os?: string;
+ platform?: string;
+ kernel_version?: string;
+}
+
+export interface CheckCaptureInfo {
+ version?: string;
+ mode?: string;
+}
+
+export interface CheckDiskInfo {
+ device?: string;
+ mountpoint?: string;
+ read_speed_bytes?: number;
+ write_speed_bytes?: number;
+ total_bytes?: number;
+ free_bytes?: number;
+ usage_percent?: number;
+}
+
+export interface CheckErrorInfo {
+ metric: string[];
+ err: string;
+}
+
+export interface CheckNetworkInterfaceInfo {
+ name: string;
+ bytes_sent: number;
+ bytes_recv: number;
+ packets_sent: number;
+ packets_recv: number;
+ err_in: number;
+ err_out: number;
+ drop_in: number;
+ drop_out: number;
+ fifo_in: number;
+ fifo_out: number;
+}
+
+export interface CheckAudits {
+ cls?: ILighthouseAudit;
+ si?: ILighthouseAudit;
+ fcp?: ILighthouseAudit;
+ lcp?: ILighthouseAudit;
+ tbt?: ILighthouseAudit;
+}
+
+export interface ILighthouseAudit {
+ id?: string;
+ title?: string;
+ score?: number | null;
+ displayValue?: string;
+ numericValue?: number;
+ numericUnit?: string;
}
export interface CheckTimings {
- start: string;
- socket: string;
- lookup: string;
- connect: string;
- secureConnect: string;
- response: string;
- end: string;
- phases: CheckTimingPhases;
+ start?: number;
+ socket?: number;
+ lookup?: number;
+ connect?: number;
+ secureConnect?: number;
+ upload?: number;
+ response?: number;
+ end?: number;
+ abort?: number;
+ error?: number;
+ phases?: {
+ wait?: number;
+ dns?: number;
+ tcp?: number;
+ tls?: number;
+ request?: number;
+ firstByte?: number;
+ download?: number;
+ total?: number;
+ };
}
export interface Check {
- _id: string;
- monitorId: string;
- type: string;
- status: string;
- message: string;
+ id: string;
+ metadata: CheckMetadata;
+ status: boolean;
responseTime: number;
- normalResponseTime?: number;
- httpStatusCode: number;
+ timings?: CheckTimings;
+ statusCode: number;
+ message: string;
ack: boolean;
+ ackAt?: string | null;
expiry: string;
+ cpu?: CheckCpuInfo;
+ memory?: CheckMemoryInfo;
+ disk?: CheckDiskInfo[];
+ host?: CheckHostInfo;
+ errors?: CheckErrorInfo[];
+ capture?: CheckCaptureInfo;
+ net?: CheckNetworkInterfaceInfo[];
+ accessibility?: number;
+ bestPractices?: number;
+ seo?: number;
+ performance?: number;
+ audits?: CheckAudits;
+ __v: number;
createdAt: string;
updatedAt: string;
- timings: CheckTimings;
}
export interface GroupedCheck {
- _id: string;
+ bucketDate: string;
avgResponseTime: number;
- count: number;
+ totalChecks: number;
}
export interface LatestCheck {
- status: string;
+ status: boolean;
responseTime: number;
checkedAt: string;
- _id: string;
+ id: string;
}
+
+export interface ChecksResponse {
+ checks: Check[];
+ checksCount: number;
+}
+
+export type MonitorType =
+ | "http"
+ | "ping"
+ | "pagespeed"
+ | "hardware"
+ | "docker"
+ | "port"
+ | "game"
+ | "unknown";
+
+export interface ChecksQueryResult {
+ checksCount: number;
+ checks: Check[];
+}
+
+export interface PageSpeedChecksResult {
+ monitorType: "pagespeed";
+ checks: Check[];
+}
+
+export interface HardwareChecksResult {
+ monitorType: "hardware";
+ aggregateData: {
+ totalChecks: number;
+ };
+ upChecks: {
+ totalChecks: number;
+ };
+ checks: Array<{
+ _id: string;
+ avgCpuUsage: number;
+ avgMemoryUsage: number;
+ avgTemperature: number[];
+ disks: Array<{
+ name: string;
+ readSpeed: number;
+ writeSpeed: number;
+ totalBytes: number;
+ freeBytes: number;
+ usagePercent: number;
+ }>;
+ net: Array<{
+ name: string;
+ bytesSentPerSecond: number;
+ deltaBytesRecv: number;
+ deltaPacketsSent: number;
+ deltaPacketsRecv: number;
+ deltaErrIn: number;
+ deltaErrOut: number;
+ deltaDropIn: number;
+ deltaDropOut: number;
+ deltaFifoIn: number;
+ deltaFifoOut: number;
+ }>;
+ }>;
+}
+
+export interface UptimeChecksResult {
+ monitorType: Exclude;
+ groupedChecks: GroupedCheck[];
+ groupedUpChecks: GroupedCheck[];
+ groupedDownChecks: GroupedCheck[];
+ uptimePercentage: number;
+ avgResponseTime: number;
+}
+
+export interface ChecksSummary {
+ totalChecks: number;
+ resolvedChecks: number;
+ downChecks: number;
+ cannotResolveChecks: number;
+}
+
+export type CheckSnapshot = Omit<
+ Check,
+ "metadata" | "ack" | "ackAt" | "expiry" | "__v" | "updatedAt"
+>;
+
+export interface HasResponseTime {
+ responseTime: number;
+}
+
+export type NormalizedCheck = T & {
+ originalResponseTime: number;
+};
+
+export type NormalizedUptimeCheck = T & {
+ originalAvgResponseTime: number;
+};
diff --git a/client/src/Types/Monitor.ts b/client/src/Types/Monitor.ts
index 05f7d7d6f..d4dcf5a38 100644
--- a/client/src/Types/Monitor.ts
+++ b/client/src/Types/Monitor.ts
@@ -1,20 +1,109 @@
-import type { Check } from "@/Types/Check";
-export type MonitorStatus = "up" | "down" | "initializing";
+import type { GroupedCheck } from "@/Types/Check";
+import type { CheckSnapshot } from "@/Types/Check";
+export type MonitorStatus = boolean | undefined;
-export interface IMonitor {
- checks: Check[];
- createdAt: string;
- createdBy: string;
- interval: number;
- isActive: boolean;
- latestChecks: Check[];
- n: number;
- name: string;
- status: MonitorStatus;
- type: string;
- updatedAt: string;
- updatedBy: string;
- url: string;
- __v: number;
- _id: string;
+export const MonitorTypes = [
+ "http",
+ "ping",
+ "pagespeed",
+ "hardware",
+ "docker",
+ "port",
+ "game",
+ "unknown",
+] as const;
+export type MonitorType = (typeof MonitorTypes)[number];
+
+export interface MonitorThresholds {
+ usage_cpu?: number;
+ usage_memory?: number;
+ usage_disk?: number;
+ usage_temperature?: number;
+}
+
+export type MonitorMatchMethod = "equal" | "include" | "regex" | "";
+
+export interface Monitor {
+ id: string;
+ userId: string;
+ teamId: string;
+ name: string;
+ description?: string;
+ status?: boolean;
+ statusWindow: boolean[];
+ statusWindowSize: number;
+ statusWindowThreshold: number;
+ type: MonitorType;
+ ignoreTlsErrors: boolean;
+ jsonPath?: string;
+ expectedValue?: string;
+ matchMethod?: MonitorMatchMethod;
+ url: string;
+ port?: number;
+ isActive: boolean;
+ interval: number;
+ uptimePercentage?: number;
+ notifications: string[];
+ secret?: string;
+ thresholds?: MonitorThresholds;
+ alertThreshold: number;
+ cpuAlertThreshold: number;
+ memoryAlertThreshold: number;
+ diskAlertThreshold: number;
+ tempAlertThreshold: number;
+ selectedDisks: string[];
+ gameId?: string;
+ group: string | null;
+ recentChecks: CheckSnapshot[];
+ createdAt: string;
+ updatedAt: string;
+}
+
+export type MonitorWithChecks = Monitor;
+
+export interface MonitorsSummary {
+ totalMonitors: number;
+ upMonitors: number;
+ downMonitors: number;
+ pausedMonitors: number;
+}
+
+export interface MonitorsWithChecksResponse {
+ count: number;
+ monitors: MonitorWithChecks[];
+ summary: MonitorsSummary | null;
+}
+
+export interface MonitorStats {
+ id: string;
+ monitorId: string;
+ avgResponseTime: number;
+ totalChecks: number;
+ totalUpChecks: number;
+ totalDownChecks: number;
+ uptimePercentage: number;
+ lastCheckTimestamp: number;
+ lastResponseTime: number;
+ timeOfLastFailure?: number;
+ createdAt: string;
+ updatedAt: string;
+}
+
+export interface MonitorData {
+ monitor: Monitor;
+ groupedChecks: GroupedCheck[];
+ groupedUpChecks: GroupedCheck[];
+ groupedDownChecks: GroupedCheck[];
+ groupedAvgResponseTime: number;
+ groupedUptimePercentage: number;
+}
+
+export interface MonitorDetailsResponse {
+ monitorData: MonitorData;
+ monitorStats: MonitorStats | null;
+}
+
+export interface PageSpeedDetailsResponse {
+ monitor: MonitorWithChecks;
+ monitorStats: MonitorStats | null;
}
diff --git a/client/src/Types/Settings.ts b/client/src/Types/Settings.ts
new file mode 100644
index 000000000..28f7bf5ed
--- /dev/null
+++ b/client/src/Types/Settings.ts
@@ -0,0 +1,34 @@
+export interface SettingsThresholds {
+ cpu?: number;
+ memory?: number;
+ disk?: number;
+ temperature?: number;
+}
+
+export interface Settings {
+ id: string;
+ checkTTL: number;
+ language: string;
+ systemEmailHost?: string;
+ systemEmailPort?: number;
+ systemEmailAddress?: string;
+ systemEmailUser?: string;
+ systemEmailConnectionHost?: string;
+ systemEmailTLSServername?: string;
+ systemEmailSecure: boolean;
+ systemEmailPool: boolean;
+ systemEmailIgnoreTLS: boolean;
+ systemEmailRequireTLS: boolean;
+ systemEmailRejectUnauthorized: boolean;
+ showURL: boolean;
+ singleton: boolean;
+ globalThresholds?: SettingsThresholds;
+ createdAt: string;
+ updatedAt: string;
+}
+
+export interface AppSettingsResponse {
+ pagespeedKeySet: boolean;
+ emailPasswordSet: boolean;
+ settings: Settings;
+}
diff --git a/client/src/Types/mui.d.ts b/client/src/Types/mui.d.ts
index 84bcd799f..3878f9391 100644
--- a/client/src/Types/mui.d.ts
+++ b/client/src/Types/mui.d.ts
@@ -1,24 +1,16 @@
import "@mui/material/Button";
declare module "@mui/material/styles" {
- interface Palette {
- accent: Palette["primary"];
- tertiary: Palette["primary"];
- }
- interface PaletteOptions {
- accent?: PaletteOptions["primary"];
- tertiary?: PaletteOptions["primary"];
- }
-
- interface PaletteColor {
- contrastTextSecondary?: string;
- contrastTextTertiary?: string;
- lowContrast?: string;
- }
+ interface Palette {}
+ interface PaletteOptions {}
}
declare module "@mui/material/Button" {
interface ButtonPropsColorOverrides {
- accent: true;
+ neutral: true;
}
}
+
+declare module "@mui/material/CircularProgress" {
+ interface CircularProgressPropsColorOverrides {}
+}
diff --git a/client/src/Types/state.ts b/client/src/Types/state.ts
new file mode 100644
index 000000000..137d67b66
--- /dev/null
+++ b/client/src/Types/state.ts
@@ -0,0 +1,41 @@
+export interface AuthState {
+ isLoading: boolean;
+ authToken: string;
+ user: string;
+ success: boolean | null;
+ msg: string | null;
+}
+
+export interface UIState {
+ monitors: {
+ rowsPerPage: number;
+ };
+ team: {
+ rowsPerPage: number;
+ };
+ maintenance: {
+ rowsPerPage: number;
+ };
+ infrastructure: {
+ rowsPerPage: number;
+ };
+ sidebar: {
+ collapsed: boolean;
+ };
+ mode: "light" | "dark";
+ showURL: boolean;
+ greeting: {
+ index: number;
+ lastUpdate: string | null;
+ };
+ timezone: string;
+ distributedUptimeEnabled: boolean;
+ language: string;
+ starPromptOpen: boolean;
+ chartType: string;
+}
+
+export interface RootState {
+ auth: AuthState;
+ ui: UIState;
+}
diff --git a/client/src/Utils/ApiClient.ts b/client/src/Utils/ApiClient.ts
new file mode 100644
index 000000000..e90bc9f01
--- /dev/null
+++ b/client/src/Utils/ApiClient.ts
@@ -0,0 +1,75 @@
+import axios from "axios";
+import type { AxiosError } from "axios";
+import type { AxiosRequestConfig, AxiosResponse } from "axios";
+
+const BASE_URL = import.meta.env.VITE_APP_API_BASE_URL;
+
+const api = axios.create({
+ baseURL: BASE_URL,
+});
+
+type StoreType = {
+ getState: () => { auth?: { authToken?: string } };
+};
+
+let storeInstance: StoreType | null = null;
+let interceptorsInitialized = false;
+
+export const initApiClient = (store: StoreType): void => {
+ storeInstance = store;
+
+ if (interceptorsInitialized) {
+ return;
+ }
+ interceptorsInitialized = true;
+
+ api.interceptors.request.use(
+ (config) => {
+ const authToken = storeInstance?.getState()?.auth?.authToken ?? "";
+
+ config.headers.set("Authorization", `Bearer ${authToken}`);
+ if (!config.headers.has("Accept-Language")) {
+ config.headers.set("Accept-Language", "en");
+ }
+
+ return config;
+ },
+ (error) => {
+ return Promise.reject(error);
+ }
+ );
+
+ const onSuccess = (response: AxiosResponse) => response;
+ const onError = (error: AxiosError) => {
+ if (error.response?.status === 401) {
+ window.location.href = "/login";
+ }
+ return Promise.reject(error);
+ };
+
+ api.interceptors.response.use(onSuccess, onError);
+};
+
+export const get = (
+ url: string,
+ config: AxiosRequestConfig = {}
+): Promise> => api.get(url, config);
+
+export const post = (
+ url: string,
+ data: unknown,
+ config: AxiosRequestConfig = {}
+): Promise> => api.post(url, data, config);
+
+export const patch = (
+ url: string,
+ data: unknown,
+ config: AxiosRequestConfig = {}
+): Promise> => api.patch(url, data, config);
+
+export const deleteOp = (
+ url: string,
+ config: AxiosRequestConfig = {}
+): Promise> => api.delete(url, config);
+
+export default api;
diff --git a/client/src/Utils/DataUtils.ts b/client/src/Utils/DataUtils.ts
new file mode 100644
index 000000000..c0bbb2d2a
--- /dev/null
+++ b/client/src/Utils/DataUtils.ts
@@ -0,0 +1,129 @@
+const MIN_OUT = 10;
+const MAX_OUT = 100;
+
+export const normalizeResponseTimes = (
+ checks: T[],
+ key: K
+): (T & { normalResponseTime: number })[] => {
+ if (!Array.isArray(checks) || checks.length === 0)
+ return checks as (T & {
+ normalResponseTime: number;
+ })[];
+
+ if (checks.length === 1) {
+ return [
+ {
+ ...checks[0],
+ normalResponseTime: 50,
+ },
+ ];
+ }
+
+ const getVal = (check: T): number => {
+ const v = check[key] as unknown;
+ return typeof v === "number" && Number.isFinite(v) ? v : 0;
+ };
+
+ const { min, max } = checks.reduce(
+ (acc, check) => {
+ const v = getVal(check);
+ if (v > acc.max) acc.max = v;
+ if (v < acc.min) acc.min = v;
+ return acc;
+ },
+ { max: -Infinity, min: Infinity }
+ );
+
+ const range = max - min || 1;
+
+ return checks.map((check) => ({
+ ...check,
+ normalResponseTime: MIN_OUT + ((getVal(check) - min) * (MAX_OUT - MIN_OUT)) / range,
+ }));
+};
+
+// Interpolate color between three theme colors over 0-100 range.
+// 0-50 => start (success.light), 50-75 => mid (warning.light), 75-100 => end (error.light)
+export const getResponseColor = (
+ ms: number,
+ colors: {
+ start: string | undefined;
+ mid: string | undefined;
+ end: string | undefined;
+ }
+): string => {
+ // New ranges with open high end:
+ // 0–300ms: interpolate start (good) -> mid (warning)
+ // 300–600ms: interpolate mid (warning) -> end (error)
+ // >600ms: solid end (error)
+ const safe = { ...colors };
+ if (!safe.start) safe.start = "#4caf50"; // fallback green
+ if (!safe.mid) safe.mid = "#ff9800"; // fallback orange
+ if (!safe.end) safe.end = "#f44336"; // fallback red
+
+ const toHex = (c: number) => c.toString(16).padStart(2, "0");
+ const clamp = (n: number) => Math.min(255, Math.max(0, Math.round(n)));
+ const rgbToHex = (r: number, g: number, b: number) =>
+ `#${toHex(clamp(r))}${toHex(clamp(g))}${toHex(clamp(b))}`;
+
+ const normalizeToHex = (value: string) => {
+ const v = value.trim();
+ if (v.startsWith("#")) {
+ const h = v.slice(1);
+ const full =
+ h.length === 3
+ ? h
+ .split("")
+ .map((c) => c + c)
+ .join("")
+ : h.substring(0, 6);
+ return `#${full.toLowerCase()}`;
+ }
+ const m = v
+ .replace(/\s+/g, "")
+ .match(/^rgba?\((\d{1,3}),(\d{1,3}),(\d{1,3})(?:,(0|0?\.\d+|1))?\)$/i);
+ if (m) {
+ const r = parseInt(m[1], 10);
+ const g = parseInt(m[2], 10);
+ const b = parseInt(m[3], 10);
+ return rgbToHex(r, g, b);
+ }
+ // Fallback neutral
+ return "#7f7f7f";
+ };
+
+ const parseHex = (hex: string) => {
+ const h = hex.replace("#", "");
+ const full =
+ h.length === 3
+ ? h
+ .split("")
+ .map((c) => c + c)
+ .join("")
+ : h;
+ const n = parseInt(full, 16);
+ return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 };
+ };
+ const mix = (a: number, b: number, t: number) => Math.round(a + (b - a) * t);
+
+ const v = Math.max(0, ms);
+ if (v <= 300) {
+ const t = v / 300; // 0..1 from start->mid
+ const s = parseHex(normalizeToHex(safe.start));
+ const m = parseHex(normalizeToHex(safe.mid));
+ const r = mix(s.r, m.r, t);
+ const g = mix(s.g, m.g, t);
+ const b = mix(s.b, m.b, t);
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
+ }
+ if (v <= 600) {
+ const t = (v - 300) / 300; // 0..1 from mid->end
+ const m = parseHex(normalizeToHex(safe.mid));
+ const e = parseHex(normalizeToHex(safe.end));
+ const r = mix(m.r, e.r, t);
+ const g = mix(m.g, e.g, t);
+ const b = mix(m.b, e.b, t);
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
+ }
+ return normalizeToHex(safe.end);
+};
diff --git a/client/src/Utils/Logger.js b/client/src/Utils/Logger.js
index f132ad408..849e10854 100644
--- a/client/src/Utils/Logger.js
+++ b/client/src/Utils/Logger.js
@@ -1,14 +1,25 @@
-import store from "../store";
const LOG_LEVEL = import.meta.env.VITE_APP_LOG_LEVEL || "debug";
const NO_OP = () => {};
class Logger {
constructor() {
let logLevel = LOG_LEVEL;
- this.unsubscribe = store.subscribe(() => {
- logLevel = "debug";
- this.updateLogLevel(logLevel);
- });
+ this.updateLogLevel(logLevel);
+ // Defer store subscription to avoid circular dependency during HMR
+ setTimeout(() => {
+ try {
+ import("../store").then(({ default: store }) => {
+ if (store) {
+ this.unsubscribe = store.subscribe(() => {
+ logLevel = "debug";
+ this.updateLogLevel(logLevel);
+ });
+ }
+ });
+ } catch (e) {
+ // Store not ready yet, ignore
+ }
+ }, 0);
}
updateLogLevel(logLevel) {
diff --git a/client/src/Utils/MonitorUtils.ts b/client/src/Utils/MonitorUtils.ts
new file mode 100644
index 000000000..ed4e2894e
--- /dev/null
+++ b/client/src/Utils/MonitorUtils.ts
@@ -0,0 +1,120 @@
+import type { Monitor, MonitorStatus, MonitorType } from "@/Types/Monitor";
+import type { PaletteKey } from "@/Utils/Theme/v2Theme";
+import type { ValueType } from "@/Components/v2/design-elements/StatusLabel";
+
+export const getMonitorPath = (type: MonitorType): string => {
+ const pathMap: Record = {
+ http: "uptime",
+ port: "uptime",
+ ping: "uptime",
+ hardware: "hardware",
+ pagespeed: "pagespeed",
+ docker: "docker",
+ game: "game-servers",
+ unknown: "uptime",
+ };
+ return pathMap[type];
+};
+
+export const getStatusPalette = (status: MonitorStatus): PaletteKey => {
+ if (status === true) {
+ return "success";
+ }
+ if (status === false) {
+ return "error";
+ }
+ return "warning";
+};
+
+export const getValuePalette = (value: ValueType): PaletteKey => {
+ const paletteMap: Record = {
+ positive: "success",
+ negative: "error",
+ neutral: "warning",
+ };
+ return paletteMap[value];
+};
+
+export const getStatusColor = (status: MonitorStatus, theme: any): string => {
+ if (status === true) {
+ return theme.palette.success.light;
+ }
+
+ if (status === false) {
+ return theme.palette.error.light;
+ }
+
+ return theme.palette.warning.light;
+};
+
+export const getResponseTimeColor = (responseTime: number): PaletteKey => {
+ if (responseTime < 200) {
+ return "success";
+ } else if (responseTime < 300) {
+ return "warning";
+ } else {
+ return "error";
+ }
+};
+
+export const getInfraGaugeColor = (val: number, theme: any) => {
+ if (val < 50) {
+ return theme.palette.success.main;
+ } else if (val < 80) {
+ return theme.palette.warning.light;
+ } else {
+ return theme.palette.error.light;
+ }
+};
+
+export const getPageSpeedPalette = (score: number): PaletteKey => {
+ if (score >= 90) return "success";
+ else if (score >= 50) return "warning";
+ else return "error";
+};
+
+export const formatUrl = (url: string, maxLength: number = 55) => {
+ if (!url) return "";
+
+ const strippedUrl = url.replace(/^https?:\/\//, "");
+ return strippedUrl.length > maxLength
+ ? `${strippedUrl.slice(0, maxLength)}…`
+ : strippedUrl;
+};
+
+export interface IStatusPageHeaderConfig {
+ paletteKey: PaletteKey;
+ message: string;
+}
+export const getStatusPageHeaderConfig = (
+ monitors: Monitor[],
+ t: any
+): IStatusPageHeaderConfig => {
+ if (!monitors || monitors.length === 0) {
+ return { paletteKey: "error", message: "No monitors available" };
+ }
+
+ const allUp = monitors.every((monitor) => monitor.status === true);
+ const anyDown = monitors.some((monitor) => monitor.status === false);
+ const allDown = monitors.every((monitor) => monitor.status === false);
+
+ if (allUp)
+ return {
+ paletteKey: "success",
+ message: t("statusPage.details.statusHeader.allUp"),
+ };
+ if (allDown)
+ return {
+ paletteKey: "error",
+ message: t("statusPage.details.statusHeader.allDown"),
+ };
+ if (anyDown)
+ return {
+ paletteKey: "warning",
+ message: t("statusPage.details.statusHeader.anyDown"),
+ };
+ return {
+ paletteKey: "warning",
+ message: t("statusPage.details.statusHeader.anyDown"),
+ };
+};
diff --git a/client/src/Utils/NetworkService.js b/client/src/Utils/NetworkService.js
index 6372be646..f703b1043 100644
--- a/client/src/Utils/NetworkService.js
+++ b/client/src/Utils/NetworkService.js
@@ -144,20 +144,16 @@ class NetworkService {
*/
async getMonitorsByTeamId(config) {
- const { limit, types, page, rowsPerPage, filter, field, order } = config;
+ const { types, filter } = config ?? {};
const params = new URLSearchParams();
- if (limit) params.append("limit", limit);
if (types) {
types.forEach((type) => {
params.append("type", type);
});
}
- if (page) params.append("page", page);
- if (rowsPerPage) params.append("rowsPerPage", rowsPerPage);
- if (filter) params.append("filter", filter);
- if (field) params.append("field", field);
- if (order) params.append("order", order);
+ if (filter !== undefined && filter !== null && filter !== "")
+ params.append("filter", filter);
return this.axiosInstance.get(`/monitors/team?${params.toString()}`, {
headers: {
@@ -202,6 +198,14 @@ class NetworkService {
`/monitors/hardware/details/${config.monitorId}?${params.toString()}`
);
}
+ async getPageSpeedDetailsByMonitorId(config) {
+ const params = new URLSearchParams();
+ if (config.dateRange) params.append("dateRange", config.dateRange);
+
+ return this.axiosInstance.get(
+ `/monitors/pagespeed/details/${config.monitorId}?${params.toString()}`
+ );
+ }
async getUptimeDetailsById(config) {
const params = new URLSearchParams();
if (config.dateRange) params.append("dateRange", config.dateRange);
@@ -343,17 +347,8 @@ class NetworkService {
return this.axiosInstance.get(`/monitors/certificate/${config.monitorId}`);
}
- /**
- * ************************************
- * Registers a new user
- * ************************************
- *
- * @async
- * @param {Object} form - The form data for the new user to be registered.
- * @returns {Promise} The response from the axios POST request.
- */
- async registerUser(form) {
- return this.axiosInstance.post(`/auth/register`, form);
+ async registerUser(user, token) {
+ return this.axiosInstance.post(`/auth/register`, { user, token });
}
/**
@@ -599,8 +594,8 @@ class NetworkService {
* @async
* @returns {Promise} The response from the axios GET request.
*/
- getChecksAndSummaryByTeamId = async () => {
- return this.axiosInstance.get(`/checks/team/summary`);
+ getChecksAndSummaryByTeamId = async ({ dateRange }) => {
+ return this.axiosInstance.get(`/checks/team/summary?dateRange=${dateRange}`);
};
/**
@@ -914,7 +909,7 @@ class NetworkService {
}
async createStatusPage(config) {
- const { form, isCreate } = config;
+ const { form, isCreate, id } = config;
const fd = new FormData();
fd.append("type", form.type);
@@ -960,7 +955,7 @@ class NetworkService {
return this.axiosInstance.post(`/status-page`, fd, {});
}
- return this.axiosInstance.put(`/status-page`, fd, {});
+ return this.axiosInstance.put(`/status-page/${id}`, fd, {});
}
async deleteStatusPage(config) {
@@ -1145,6 +1140,41 @@ class NetworkService {
},
});
}
+
+ async fetchJson() {
+ return this.axiosInstance.get("/monitors/export/json");
+ }
+
+ getIncidentsByTeam = async (config) => {
+ const params = new URLSearchParams();
+ if (config.monitorId) params.append("monitorId", config.monitorId);
+ if (config.status !== undefined) params.append("status", config.status);
+ if (config.resolutionType) params.append("resolutionType", config.resolutionType);
+ if (config.sortOrder) params.append("sortOrder", config.sortOrder);
+ if (config.dateRange) params.append("dateRange", config.dateRange);
+ if (config.page) params.append("page", config.page);
+ if (config.rowsPerPage) params.append("rowsPerPage", config.rowsPerPage);
+ return this.axiosInstance.get(`/incidents/team?${params.toString()}`);
+ };
+
+ getIncidentById = async (incidentId) => {
+ return this.axiosInstance.get(`/incidents/${incidentId}`);
+ };
+
+ resolveIncidentManually = async (incidentId, options = {}) => {
+ const body = {};
+ if (options.comment) body.comment = options.comment;
+ return this.axiosInstance.put(`/incidents/${incidentId}/resolve`, body);
+ };
+
+ getIncidentSummary = async (config = {}) => {
+ const params = new URLSearchParams();
+ if (config.limit) params.append("limit", config.limit);
+ const queryString = params.toString();
+ return this.axiosInstance.get(
+ `/incidents/team/summary${queryString ? `?${queryString}` : ""}`
+ );
+ };
}
export default NetworkService;
diff --git a/client/src/Utils/Theme/constants.js b/client/src/Utils/Theme/constants.js
index 45217fdeb..2664c6126 100644
--- a/client/src/Utils/Theme/constants.js
+++ b/client/src/Utils/Theme/constants.js
@@ -1,4 +1,4 @@
-import { lighten, darken } from "@mui/material/styles"; // CAIO_REVIEW
+import { lighten } from "@mui/material/styles"; // CAIO_REVIEW
const typographyBase = 13;
@@ -133,12 +133,6 @@ const newSemanticColors = {
light: lighten(newColors.blueBlueWave, 0.2), //CAIO_REVIEW
dark: lighten(newColors.blueBlueWave, 0.2), //CAIO_REVIEW
},
- darker: {
- // CAIO_REVIEW
- light: darken(newColors.blueBlueWave, 0.2), // CAIO_REVIEW
- dark: darken(newColors.blueBlueWave, 0.2), // CAIO_REVIEW
- },
-
contrastText: {
light: newColors.offWhite,
dark: newColors.offWhite,
@@ -165,14 +159,6 @@ const newSemanticColors = {
light: newColors.blueGray500,
dark: newColors.gray500,
},
- contrastBorder: {
- light: newColors.gray500,
- dark: newColors.blueGray600,
- },
- contrastBorderDisabled: {
- light: newColors.gray100,
- dark: newColors.blueGray800,
- },
lowContrast: {
light: newColors.gray250,
dark: newColors.blueGray600,
@@ -207,18 +193,6 @@ const newSemanticColors = {
light: newColors.blueGray800,
dark: newColors.gray100,
},
- background: {
- light: newColors.gray50,
- dark: newColors.offBlack,
- },
- border: {
- light: newColors.gray500,
- dark: newColors.blueGray600,
- },
- cardShadow: {
- light: "0 0 0 1px rgba(0, 0, 0, 0.04), 0 12px 24px rgba(0, 0, 0, 0.08)",
- dark: "0 2px 10px rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(255, 255, 255, 0.04)",
- },
},
success: {
main: {
@@ -234,20 +208,6 @@ const newSemanticColors = {
dark: newColors.green200,
},
},
- successSecondary: {
- main: {
- light: undefined,
- dark: "#073C2D",
- },
- contrastText: {
- light: undefined,
- dark: "#34D399",
- },
- lowContrast: {
- light: undefined,
- dark: "#094434",
- },
- },
warning: {
main: {
light: newColors.orange700,
@@ -316,30 +276,6 @@ const newSemanticColors = {
},
},
- map: {
- main: {
- light: newColors.offWhite,
- dark: undefined,
- },
- lowContrast: {
- light: newColors.gray200,
- dark: undefined,
- },
- highContrast: {
- light: newColors.gray500,
- dark: undefined,
- },
- },
- chatbot: {
- background: {
- light: newColors.gray200,
- dark: "#112B2B",
- },
- textAccent: {
- light: "#C084FC",
- dark: "#C084FC",
- },
- },
alert: {
main: {
light: newColors.gray200,
diff --git a/client/src/Utils/Theme/globalTheme.js b/client/src/Utils/Theme/globalTheme.js
index e65baf4c0..37143d730 100644
--- a/client/src/Utils/Theme/globalTheme.js
+++ b/client/src/Utils/Theme/globalTheme.js
@@ -64,7 +64,7 @@ const baseTheme = (palette) => ({
letterSpacing: "0.5px",
textShadow: "0 0 1px rgba(0, 0, 0, 0.15)",
"&:hover": {
- backgroundColor: darken(theme.palette.accent.darker, 0.05),
+ backgroundColor: darken(theme.palette.accent.main, 0.25),
boxShadow: `0 2px 6px rgba(0, 0, 0, 0.1)`,
transition: "all 0.2s ease-in-out",
},
@@ -74,7 +74,7 @@ const baseTheme = (palette) => ({
props: (props) => props.color === "accent",
style: {
"&:hover": {
- backgroundColor: theme.palette.accent.darker,
+ backgroundColor: darken(theme.palette.accent.main, 0.2),
},
},
},
@@ -687,6 +687,58 @@ const baseTheme = (palette) => ({
}),
},
},
+ // DatePicker calendar popup styling
+ MuiPickersPopper: {
+ styleOverrides: {
+ paper: ({ theme }) => ({
+ backgroundColor: theme.palette.primary.main,
+ }),
+ },
+ },
+ MuiDateCalendar: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ backgroundColor: theme.palette.primary.main,
+ }),
+ },
+ },
+ MuiPickersCalendarHeader: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ "& .MuiPickersCalendarHeader-label": {
+ color: theme.palette.primary.contrastText,
+ },
+ }),
+ },
+ },
+ MuiDayCalendar: {
+ styleOverrides: {
+ weekDayLabel: ({ theme }) => ({
+ color: theme.palette.primary.contrastTextTertiary,
+ }),
+ },
+ },
+ MuiPickersDay: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ color: theme.palette.primary.contrastText,
+ "&:hover": {
+ backgroundColor: theme.palette.tertiary.main,
+ },
+ "&.Mui-selected": {
+ backgroundColor: theme.palette.accent.main,
+ color: theme.palette.accent.contrastText,
+ "&:hover": {
+ backgroundColor: theme.palette.accent.main,
+ },
+ },
+ "&.Mui-disabled": {
+ color: theme.palette.primary.contrastTextTertiary,
+ opacity: 0.5,
+ },
+ }),
+ },
+ },
// code ends here.
// For labels of input fields
diff --git a/client/src/Utils/Theme/v2/palette.ts b/client/src/Utils/Theme/v2/palette.ts
deleted file mode 100644
index 43370fe8b..000000000
--- a/client/src/Utils/Theme/v2/palette.ts
+++ /dev/null
@@ -1,124 +0,0 @@
-import { lighten, darken } from "@mui/material/styles";
-
-const typographyBase = 13;
-
-export const typographyLevels = {
- base: typographyBase,
- xs: `${(typographyBase - 4) / 16}rem`,
- s: `${(typographyBase - 2) / 16}rem`,
- m: `${typographyBase / 16}rem`,
- l: `${(typographyBase + 2) / 16}rem`,
- xl: `${(typographyBase + 10) / 16}rem`,
-};
-
-const colors = {
- offWhite: "#FEFEFE",
- offBlack: "#131315",
- gray0: "#FDFDFD",
- gray10: "#F4F4FF",
- gray50: "#F9F9F9",
- gray100: "#F3F3F3",
- gray200: "#EFEFEF",
- gray250: "#DADADA",
- gray500: "#A2A3A3",
- gray900: "#1c1c1c",
- blueGray50: "#E8F0FE",
- blueGray500: "#475467",
- blueGray600: "#344054",
- blueGray800: "#1C2130",
- blueGray900: "#515151",
- blueBlueWave: "#1570EF",
- lightBlueWave: "#CDE2FF",
- green100: "#67cd78",
- green200: "#4B9B77",
- green400: "#079455",
- green700: "#026513",
- orange100: "#FD8F22",
- orange200: "#D69A5D",
- orange600: "#9B734B",
- orange700: "#884605",
- red100: "#F27C7C",
- red400: "#D92020",
- red600: "#9B4B4B",
- red700: "#980303",
-};
-
-export const lightPalette = {
- accent: {
- main: colors.blueBlueWave,
- light: lighten(colors.blueBlueWave, 0.2),
- dark: darken(colors.blueBlueWave, 0.2),
- contrastText: colors.offWhite,
- },
- primary: {
- main: colors.offWhite,
- contrastText: colors.blueGray800,
- contrastTextSecondary: colors.blueGray600,
- contrastTextTertiary: colors.blueGray500,
- lowContrast: colors.gray250,
- },
- secondary: {
- main: colors.gray200,
- light: colors.lightBlueWave,
- contrastText: colors.blueGray600,
- },
- tertiary: {
- main: colors.gray100,
- contrastText: colors.blueGray800,
- },
- success: {
- main: colors.green700,
- contrastText: colors.offWhite,
- lowContrast: colors.green400,
- },
- warning: {
- main: colors.orange700,
- contrastText: colors.offWhite,
- lowContrast: colors.orange100,
- },
- error: {
- main: colors.red700,
- contrastText: colors.offWhite,
- lowContrast: colors.red400,
- },
-};
-
-export const darkPalette = {
- accent: {
- main: colors.blueBlueWave,
- light: lighten(colors.blueBlueWave, 0.2),
- dark: darken(colors.blueBlueWave, 0.2),
- contrastText: colors.offWhite,
- },
- primary: {
- main: colors.offBlack,
- contrastText: colors.blueGray50,
- contrastTextSecondary: colors.gray200,
- contrastTextTertiary: colors.gray500,
- lowContrast: colors.blueGray600,
- },
- secondary: {
- main: "#313131",
- light: colors.lightBlueWave,
- contrastText: colors.gray200,
- },
- tertiary: {
- main: colors.blueGray800,
- contrastText: colors.gray100,
- },
- success: {
- main: colors.green100,
- contrastText: colors.offBlack,
- lowContrast: colors.green200,
- },
- warning: {
- main: colors.orange200,
- contrastText: colors.offBlack,
- lowContrast: colors.orange600,
- },
- error: {
- main: colors.red100,
- contrastText: colors.offBlack,
- lowContrast: colors.red600,
- },
-};
diff --git a/client/src/Utils/Theme/v2Palette.ts b/client/src/Utils/Theme/v2Palette.ts
new file mode 100644
index 000000000..d314c8e27
--- /dev/null
+++ b/client/src/Utils/Theme/v2Palette.ts
@@ -0,0 +1,34 @@
+const typographyBase = 13;
+
+export const typographyLevels = {
+ base: typographyBase,
+ xs: `${(typographyBase - 4) / 16}rem`,
+ s: `${(typographyBase - 2) / 16}rem`,
+ m: `${typographyBase / 16}rem`,
+ l: `${(typographyBase + 2) / 16}rem`,
+ xl: `${(typographyBase + 10) / 16}rem`,
+};
+
+export const colors = {
+ gray200: "#EFEFEF",
+ gray700: "#313131",
+ blueBlueWave: "#1570EF",
+};
+
+export const lightPalette = {
+ primary: {
+ main: colors.blueBlueWave,
+ },
+ secondary: {
+ main: colors.gray200,
+ },
+};
+
+export const darkPalette = {
+ primary: {
+ main: colors.blueBlueWave,
+ },
+ secondary: {
+ main: colors.gray700,
+ },
+};
diff --git a/client/src/Utils/Theme/v2/theme.ts b/client/src/Utils/Theme/v2Theme.ts
similarity index 61%
rename from client/src/Utils/Theme/v2/theme.ts
rename to client/src/Utils/Theme/v2Theme.ts
index d2581e236..57ca76669 100644
--- a/client/src/Utils/Theme/v2/theme.ts
+++ b/client/src/Utils/Theme/v2Theme.ts
@@ -1,5 +1,5 @@
import { createTheme } from "@mui/material";
-import { lightPalette, darkPalette, typographyLevels } from "./palette";
+import { lightPalette, darkPalette, typographyLevels } from "@/Utils/Theme/v2Palette";
import type { Theme } from "@mui/material/styles";
@@ -7,12 +7,21 @@ export type PaletteKey = {
[K in keyof Theme["palette"]]: Theme["palette"][K] extends { main: any } ? K : never;
}[keyof Theme["palette"]];
-const fontFamilyPrimary = '"Inter" , sans-serif';
+const fontFamilyPrimary = "system-ui, sans-serif";
const shadow =
"0px 4px 24px -4px rgba(16, 24, 40, 0.08), 0px 3px 3px -3px rgba(16, 24, 40, 0.03)";
export const theme = (mode: string, palette: any) =>
createTheme({
+ breakpoints: {
+ values: {
+ xs: 300,
+ sm: 600,
+ md: 900,
+ lg: 1200,
+ xl: 1536,
+ },
+ },
spacing: 2,
palette: {
mode: mode,
@@ -23,28 +32,64 @@ export const theme = (mode: string, palette: any) =>
fontSize: typographyLevels.base,
h1: {
fontSize: typographyLevels.xl,
- color: palette.primary.contrastText,
fontWeight: 500,
},
h2: {
fontSize: typographyLevels.l,
- color: palette.primary.contrastTextSecondary,
fontWeight: 400,
},
-
body1: {
fontSize: typographyLevels.m,
- color: palette.primary.contrastTextTertiary,
fontWeight: 400,
},
body2: {
fontSize: typographyLevels.s,
- color: palette.primary.contrastTextTertiary,
fontWeight: 400,
},
},
components: {
+ MuiButtonBase: {
+ defaultProps: {
+ disableRipple: true,
+ disableTouchRipple: true,
+ },
+ },
+ MuiButton: {
+ defaultProps: {
+ disableRipple: true,
+ disableTouchRipple: true,
+ disableFocusRipple: true,
+ },
+ },
+ MuiIconButton: {
+ defaultProps: {
+ disableRipple: true,
+ disableTouchRipple: true,
+ disableFocusRipple: true,
+ },
+ },
+ MuiListItemButton: {
+ defaultProps: {
+ disableRipple: true,
+ disableTouchRipple: true,
+ },
+ },
+ MuiTab: {
+ defaultProps: {
+ disableRipple: true,
+ disableTouchRipple: true,
+ disableFocusRipple: true,
+ },
+ },
+ MuiToggleButton: {
+ defaultProps: {
+ disableRipple: true,
+ disableTouchRipple: true,
+ disableFocusRipple: true,
+ },
+ },
+
MuiFormLabel: {
styleOverrides: {
root: ({ theme }) => ({
@@ -70,7 +115,7 @@ export const theme = (mode: string, palette: any) =>
styleOverrides: {
root: {
"&.Mui-focused .MuiOutlinedInput-notchedOutline": {
- borderColor: palette.accent.main,
+ borderColor: palette.primary.main,
},
},
},
@@ -84,10 +129,10 @@ export const theme = (mode: string, palette: any) =>
padding: 0,
border: 1,
borderStyle: "solid",
- borderColor: theme.palette.primary.lowContrast,
+ borderColor: theme.palette.divider,
borderRadius: 4,
boxShadow: shadow,
- backgroundColor: theme.palette.primary.main,
+ backgroundColor: theme.palette.background.paper,
backgroundImage: "none",
};
},
diff --git a/client/src/Utils/v2/TimeUtils.ts b/client/src/Utils/TimeUtils.ts
similarity index 84%
rename from client/src/Utils/v2/TimeUtils.ts
rename to client/src/Utils/TimeUtils.ts
index 618d9d1bc..a19a0bf8b 100644
--- a/client/src/Utils/v2/TimeUtils.ts
+++ b/client/src/Utils/TimeUtils.ts
@@ -19,17 +19,16 @@ export const formatDateWithTz = (timestamp: string, format: string, timezone: st
if (!timestamp) {
return "Unknown time";
}
-
const formattedDate = dayjs(timestamp).tz(timezone).format(format);
return formattedDate;
};
export const tickDateFormatLookup = (range: string) => {
const tickFormatLookup: Record = {
- "2h": "h:mm A",
- "24h": "h:mm A",
- "7d": "MM/D, h:mm A",
- "30d": "ddd. M/D",
+ recent: "h:mm A",
+ day: "h:mm A",
+ week: "MM/D, h:mm A",
+ month: "ddd. M/D",
};
const format = tickFormatLookup[range];
if (format === undefined) {
@@ -40,10 +39,10 @@ export const tickDateFormatLookup = (range: string) => {
export const tooltipDateFormatLookup = (range: string) => {
const dateFormatLookup: Record = {
- "2h": "ddd. MMMM D, YYYY, hh:mm A",
- "24h": "ddd. MMMM D, YYYY, hh:mm A",
- "7d": "ddd. MMMM D, YYYY, hh:mm A",
- "30d": "ddd. MMMM D, YYYY",
+ recent: "ddd. MMMM D, YYYY, hh:mm A",
+ day: "ddd. MMMM D, YYYY, hh:mm A",
+ week: "ddd. MMMM D, YYYY, hh:mm A",
+ month: "ddd. MMMM D, YYYY",
};
const format = dateFormatLookup[range];
if (format === undefined) {
diff --git a/client/src/Utils/greeting.jsx b/client/src/Utils/greeting.jsx
index d2b7fa033..c4afbf88c 100644
--- a/client/src/Utils/greeting.jsx
+++ b/client/src/Utils/greeting.jsx
@@ -136,8 +136,8 @@ const Greeting = ({ type = "" }) => {
const dispatch = useDispatch();
const { t } = useTranslation();
const { firstName } = useSelector((state) => state.auth.user);
- const index = useSelector((state) => state.ui.greeting.index);
- const lastUpdate = useSelector((state) => state.ui.greeting.lastUpdate);
+ const index = useSelector((state) => state.ui.greeting?.index ?? 0);
+ const lastUpdate = useSelector((state) => state.ui.greeting?.lastUpdate ?? null);
const now = new Date();
const hour = now.getHours();
diff --git a/client/src/Utils/monitorUtils.js b/client/src/Utils/monitorUtilsLegacy.js
similarity index 100%
rename from client/src/Utils/monitorUtils.js
rename to client/src/Utils/monitorUtilsLegacy.js
diff --git a/client/src/Utils/timeUtils.js b/client/src/Utils/timeUtilsLegacy.js
similarity index 87%
rename from client/src/Utils/timeUtils.js
rename to client/src/Utils/timeUtilsLegacy.js
index f6e2dcec0..365572f6b 100644
--- a/client/src/Utils/timeUtils.js
+++ b/client/src/Utils/timeUtilsLegacy.js
@@ -135,3 +135,33 @@ export const formatDateWithTz = (timestamp, format, timezone) => {
const formattedDate = dayjs(timestamp).tz(timezone).format(format);
return formattedDate;
};
+
+export const tickDateFormatLookup = (range) => {
+ switch (range) {
+ case "recent":
+ return "h:mm A";
+ case "day":
+ return "h A";
+ case "week":
+ return "MMM D";
+ case "month":
+ return "MMM D";
+ default:
+ return "MMM D, h A";
+ }
+};
+
+export const tooltipDateFormatLookup = (range) => {
+ switch (range) {
+ case "recent":
+ return "MMM D, h:mm A";
+ case "day":
+ return "MMM D, h:mm A";
+ case "week":
+ return "ddd, MMM D";
+ case "month":
+ return "ddd, MMM D";
+ default:
+ return "MMM D, h:mm A";
+ }
+};
diff --git a/client/src/Utils/v2/ApiClient.ts b/client/src/Utils/v2/ApiClient.ts
deleted file mode 100644
index f41407b8f..000000000
--- a/client/src/Utils/v2/ApiClient.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import axios from "axios";
-import type { AxiosRequestConfig, AxiosResponse } from "axios";
-const BASE_URL = import.meta.env.VITE_APP_API_V2_BASE_URL;
-const api = axios.create({
- baseURL: BASE_URL || "http://localhost:55555/api/v2",
- withCredentials: true,
-});
-
-export const get = (
- url: string,
- config: AxiosRequestConfig = {}
-): Promise> => api.get(url, config);
-
-export const post = (
- url: string,
- data: any,
- config: AxiosRequestConfig = {}
-): Promise> => api.post(url, data, config);
-
-export const patch = (
- url: string,
- data: any,
- config: AxiosRequestConfig = {}
-): Promise> => api.patch(url, data, config);
-
-export default api;
diff --git a/client/src/Utils/v2/DataUtils.ts b/client/src/Utils/v2/DataUtils.ts
deleted file mode 100644
index 3878d7d6a..000000000
--- a/client/src/Utils/v2/DataUtils.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-const MIN_OUT = 10;
-const MAX_OUT = 100;
-
-export const normalizeResponseTimes = , K extends keyof T>(
- checks: T[],
- key: K
-): (T & { normalResponseTime: number })[] => {
- if (!Array.isArray(checks) || checks.length === 0)
- return checks as (T & {
- normalResponseTime: number;
- })[];
-
- if (checks.length === 1) {
- return [
- {
- ...checks[0],
- normalResponseTime: 50,
- },
- ];
- }
-
- const { min, max } = checks.reduce(
- (acc, check) => {
- if (check[key] > acc.max) acc.max = check[key];
- if (check[key] < acc.min) acc.min = check[key];
- return acc;
- },
- { max: -Infinity, min: Infinity }
- );
-
- const range = max - min || 1;
-
- return checks.map((check) => ({
- ...check,
- normalResponseTime: MIN_OUT + ((check[key] - min) * (MAX_OUT - MIN_OUT)) / range,
- }));
-};
diff --git a/client/src/Utils/v2/MonitorUtils.ts b/client/src/Utils/v2/MonitorUtils.ts
deleted file mode 100644
index c47b7a82e..000000000
--- a/client/src/Utils/v2/MonitorUtils.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import type { MonitorStatus } from "@/Types/Monitor";
-import type { PaletteKey } from "@/Utils/Theme/v2/theme";
-export const getStatusPalette = (status: MonitorStatus): PaletteKey => {
- const paletteMap: Record = {
- up: "success",
- down: "error",
- initializing: "warning",
- };
- return paletteMap[status];
-};
-
-export const getStatusColor = (status: MonitorStatus, theme: any): string => {
- const statusColors: Record = {
- up: theme.palette.success.lowContrast,
- down: theme.palette.error.lowContrast,
- initializing: theme.palette.warning.lowContrast,
- };
- return statusColors[status];
-};
-
-export const getResponseTimeColor = (responseTime: number): PaletteKey => {
- if (responseTime < 200) {
- return "success";
- } else if (responseTime < 300) {
- return "warning";
- } else {
- return "error";
- }
-};
-
-export const formatUrl = (url: string, maxLength: number = 55) => {
- if (!url) return "";
-
- const strippedUrl = url.replace(/^https?:\/\//, "");
- return strippedUrl.length > maxLength
- ? `${strippedUrl.slice(0, maxLength)}…`
- : strippedUrl;
-};
diff --git a/client/src/Validation/v2/zod.ts b/client/src/Validation/v2/zod.ts
deleted file mode 100644
index 6f0dbb951..000000000
--- a/client/src/Validation/v2/zod.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { z } from "zod";
-import humanInterval from "human-interval";
-const urlRegex =
- /^(?:https?:\/\/)?([a-zA-Z0-9.-]+|\d{1,3}(\.\d{1,3}){3}|\[[0-9a-fA-F:]+\])(:\d{1,5})?$/;
-
-const durationSchema = z
- .string()
- .optional()
- .superRefine((val, ctx) => {
- if (!val || val.trim() === "") return;
- const ms = humanInterval(val);
-
- if (!ms || isNaN(ms)) {
- ctx.addIssue({
- code: "custom",
- message: "Invalid duration format",
- });
- } else if (ms < 10000) {
- ctx.addIssue({
- code: "custom",
- message: "Minimum duration is 10 seconds",
- });
- }
- });
-
-export const monitorSchema = z.object({
- type: z.string().min(1, "You must select an option"),
- url: z.string().min(1, "URL is required").regex(urlRegex, "Invalid URL"),
- n: z.coerce
- .number({ message: "Number required" })
- .min(1, "Minimum value is 1")
- .max(25, "Maximum value is 25"),
- notificationChannels: z.array(z.string()).optional().default([]),
- name: z.string().min(1, "Display name is required"),
- interval: durationSchema,
-});
diff --git a/client/src/Validation/validation.js b/client/src/Validation/validation.js
index ce50d96c1..27d585074 100644
--- a/client/src/Validation/validation.js
+++ b/client/src/Validation/validation.js
@@ -112,7 +112,7 @@ const loginCredentials = joi.object({
});
const monitorValidation = joi.object({
- _id: joi.string(),
+ id: joi.string(),
userId: joi.string(),
teamId: joi.string(),
statusWindowSize: joi.number().min(1).max(20).default(5).messages({
@@ -328,6 +328,7 @@ const settingsValidation = joi.object({
systemEmailIgnoreTLS: joi.boolean(),
systemEmailRequireTLS: joi.boolean(),
systemEmailRejectUnauthorized: joi.boolean(),
+ showURL: joi.boolean().optional(),
globalThresholds: joi
.object({
cpu: joi.number().min(1).max(100).allow("").optional(),
@@ -448,6 +449,7 @@ const infrastructureMonitorValidation = joi.object({
"number.max": "Status window threshold cannot exceed 100%.",
}),
notifications: joi.array().items(joi.string()),
+ selectedDisks: joi.array().items(joi.string()).optional(),
});
const notificationValidation = joi.object({
diff --git a/client/src/assets/icons/alert-icon.svg b/client/src/assets/icons/alert-icon.svg
deleted file mode 100644
index 1dc7fc2b7..000000000
--- a/client/src/assets/icons/alert-icon.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/average-response-icon.svg b/client/src/assets/icons/average-response-icon.svg
deleted file mode 100644
index 572e2002f..000000000
--- a/client/src/assets/icons/average-response-icon.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/bwu-icon.svg b/client/src/assets/icons/bwu-icon.svg
deleted file mode 100644
index 80ab50ad9..000000000
--- a/client/src/assets/icons/bwu-icon.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/client/src/assets/icons/calendar-check.svg b/client/src/assets/icons/calendar-check.svg
deleted file mode 100644
index 07d96dad9..000000000
--- a/client/src/assets/icons/calendar-check.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/calendar.svg b/client/src/assets/icons/calendar.svg
deleted file mode 100644
index be023a4de..000000000
--- a/client/src/assets/icons/calendar.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/certificate.svg b/client/src/assets/icons/certificate.svg
deleted file mode 100644
index 2bc6c7e74..000000000
--- a/client/src/assets/icons/certificate.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/client/src/assets/icons/changeLog.svg b/client/src/assets/icons/changeLog.svg
deleted file mode 100644
index d12f6c10e..000000000
--- a/client/src/assets/icons/changeLog.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/check-icon.svg b/client/src/assets/icons/check-icon.svg
deleted file mode 100644
index 9a2cfac5a..000000000
--- a/client/src/assets/icons/check-icon.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/check-outlined.svg b/client/src/assets/icons/check-outlined.svg
deleted file mode 100644
index 1e779d547..000000000
--- a/client/src/assets/icons/check-outlined.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/check.svg b/client/src/assets/icons/check.svg
deleted file mode 100644
index 357378f40..000000000
--- a/client/src/assets/icons/check.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/client/src/assets/icons/checkbox-filled.svg b/client/src/assets/icons/checkbox-filled.svg
deleted file mode 100644
index 8a5be81f0..000000000
--- a/client/src/assets/icons/checkbox-filled.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/client/src/assets/icons/checkbox-green.svg b/client/src/assets/icons/checkbox-green.svg
deleted file mode 100644
index dd5676298..000000000
--- a/client/src/assets/icons/checkbox-green.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/client/src/assets/icons/checkbox-outline.svg b/client/src/assets/icons/checkbox-outline.svg
deleted file mode 100644
index bf5a7218c..000000000
--- a/client/src/assets/icons/checkbox-outline.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/checkbox-red.svg b/client/src/assets/icons/checkbox-red.svg
deleted file mode 100644
index 3c43a2042..000000000
--- a/client/src/assets/icons/checkbox-red.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/client/src/assets/icons/clock-snooze.svg b/client/src/assets/icons/clock-snooze.svg
deleted file mode 100644
index 708e7ba91..000000000
--- a/client/src/assets/icons/clock-snooze.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/close-icon.svg b/client/src/assets/icons/close-icon.svg
deleted file mode 100644
index e1b6b690a..000000000
--- a/client/src/assets/icons/close-icon.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/cpu-chip.svg b/client/src/assets/icons/cpu-chip.svg
deleted file mode 100644
index 5645840b1..000000000
--- a/client/src/assets/icons/cpu-chip.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/dashboard.svg b/client/src/assets/icons/dashboard.svg
deleted file mode 100644
index 8958e90ed..000000000
--- a/client/src/assets/icons/dashboard.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/discussions.svg b/client/src/assets/icons/discussions.svg
deleted file mode 100644
index 293cd51f2..000000000
--- a/client/src/assets/icons/discussions.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
diff --git a/client/src/assets/icons/distributed-uptime.svg b/client/src/assets/icons/distributed-uptime.svg
deleted file mode 100644
index e612081e4..000000000
--- a/client/src/assets/icons/distributed-uptime.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/docs.svg b/client/src/assets/icons/docs.svg
deleted file mode 100644
index d36458de2..000000000
--- a/client/src/assets/icons/docs.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/dots-vertical.svg b/client/src/assets/icons/dots-vertical.svg
deleted file mode 100644
index e720066c4..000000000
--- a/client/src/assets/icons/dots-vertical.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/client/src/assets/icons/down-arrow.svg b/client/src/assets/icons/down-arrow.svg
deleted file mode 100644
index 59b4b6bc5..000000000
--- a/client/src/assets/icons/down-arrow.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/edit.svg b/client/src/assets/icons/edit.svg
deleted file mode 100644
index be2c78408..000000000
--- a/client/src/assets/icons/edit.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/email.svg b/client/src/assets/icons/email.svg
deleted file mode 100644
index 01f55e961..000000000
--- a/client/src/assets/icons/email.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/folder.svg b/client/src/assets/icons/folder.svg
deleted file mode 100644
index 15941e5b5..000000000
--- a/client/src/assets/icons/folder.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/client/src/assets/icons/groups.svg b/client/src/assets/icons/groups.svg
deleted file mode 100644
index fa2cae03c..000000000
--- a/client/src/assets/icons/groups.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/client/src/assets/icons/history-icon.svg b/client/src/assets/icons/history-icon.svg
deleted file mode 100644
index 7343455ec..000000000
--- a/client/src/assets/icons/history-icon.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/incidents.svg b/client/src/assets/icons/incidents.svg
deleted file mode 100644
index ffe04d09f..000000000
--- a/client/src/assets/icons/incidents.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/integrations.svg b/client/src/assets/icons/integrations.svg
deleted file mode 100644
index 50e817f00..000000000
--- a/client/src/assets/icons/integrations.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/interval-check.svg b/client/src/assets/icons/interval-check.svg
deleted file mode 100644
index 923475c8b..000000000
--- a/client/src/assets/icons/interval-check.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/jupiter_ag_logo.svg b/client/src/assets/icons/jupiter_ag_logo.svg
deleted file mode 100644
index b344961c8..000000000
--- a/client/src/assets/icons/jupiter_ag_logo.svg
+++ /dev/null
@@ -1,52 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/client/src/assets/icons/jupiter_logo_banner_dark.svg b/client/src/assets/icons/jupiter_logo_banner_dark.svg
deleted file mode 100644
index 3d3e23b14..000000000
--- a/client/src/assets/icons/jupiter_logo_banner_dark.svg
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/client/src/assets/icons/jupiter_logo_banner_light.svg b/client/src/assets/icons/jupiter_logo_banner_light.svg
deleted file mode 100644
index 4744e0bcb..000000000
--- a/client/src/assets/icons/jupiter_logo_banner_light.svg
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/client/src/assets/icons/key.svg b/client/src/assets/icons/key.svg
deleted file mode 100644
index 5db8d5f16..000000000
--- a/client/src/assets/icons/key.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/left-arrow-double.svg b/client/src/assets/icons/left-arrow-double.svg
deleted file mode 100644
index 4cb2a5186..000000000
--- a/client/src/assets/icons/left-arrow-double.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/client/src/assets/icons/left-arrow-long.svg b/client/src/assets/icons/left-arrow-long.svg
deleted file mode 100644
index 56a774730..000000000
--- a/client/src/assets/icons/left-arrow-long.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/client/src/assets/icons/left-arrow.svg b/client/src/assets/icons/left-arrow.svg
deleted file mode 100644
index 649d1681c..000000000
--- a/client/src/assets/icons/left-arrow.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/client/src/assets/icons/lock.svg b/client/src/assets/icons/lock.svg
deleted file mode 100644
index a87535d54..000000000
--- a/client/src/assets/icons/lock.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/logout.svg b/client/src/assets/icons/logout.svg
deleted file mode 100644
index 3f3b69c49..000000000
--- a/client/src/assets/icons/logout.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/logs.svg b/client/src/assets/icons/logs.svg
deleted file mode 100644
index 556c3ba33..000000000
--- a/client/src/assets/icons/logs.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/client/src/assets/icons/mail.svg b/client/src/assets/icons/mail.svg
deleted file mode 100644
index 75c22b111..000000000
--- a/client/src/assets/icons/mail.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/client/src/assets/icons/maintenance.svg b/client/src/assets/icons/maintenance.svg
deleted file mode 100644
index 9486f6086..000000000
--- a/client/src/assets/icons/maintenance.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/monitor-graph-line.svg b/client/src/assets/icons/monitor-graph-line.svg
deleted file mode 100644
index 86049eb9e..000000000
--- a/client/src/assets/icons/monitor-graph-line.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/monitors.svg b/client/src/assets/icons/monitors.svg
deleted file mode 100644
index f40f36433..000000000
--- a/client/src/assets/icons/monitors.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/notifications.svg b/client/src/assets/icons/notifications.svg
deleted file mode 100644
index c09ac5746..000000000
--- a/client/src/assets/icons/notifications.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/client/src/assets/icons/open-in-new-page.svg b/client/src/assets/icons/open-in-new-page.svg
deleted file mode 100644
index 70599f390..000000000
--- a/client/src/assets/icons/open-in-new-page.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/page-speed.svg b/client/src/assets/icons/page-speed.svg
deleted file mode 100644
index 30a1c8a53..000000000
--- a/client/src/assets/icons/page-speed.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/pause-icon.svg b/client/src/assets/icons/pause-icon.svg
deleted file mode 100644
index c73abfc1d..000000000
--- a/client/src/assets/icons/pause-icon.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/performance-report.svg b/client/src/assets/icons/performance-report.svg
deleted file mode 100644
index 8b7073ac3..000000000
--- a/client/src/assets/icons/performance-report.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/radio-checked.svg b/client/src/assets/icons/radio-checked.svg
deleted file mode 100644
index 1c584b79a..000000000
--- a/client/src/assets/icons/radio-checked.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/client/src/assets/icons/response-time-icon.svg b/client/src/assets/icons/response-time-icon.svg
deleted file mode 100644
index db5b2af38..000000000
--- a/client/src/assets/icons/response-time-icon.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/resume-icon.svg b/client/src/assets/icons/resume-icon.svg
deleted file mode 100644
index 1da787081..000000000
--- a/client/src/assets/icons/resume-icon.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/client/src/assets/icons/right-arrow-double.svg b/client/src/assets/icons/right-arrow-double.svg
deleted file mode 100644
index 6ad4882ad..000000000
--- a/client/src/assets/icons/right-arrow-double.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/client/src/assets/icons/right-arrow.svg b/client/src/assets/icons/right-arrow.svg
deleted file mode 100644
index 77d022798..000000000
--- a/client/src/assets/icons/right-arrow.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/client/src/assets/icons/ruler-icon.svg b/client/src/assets/icons/ruler-icon.svg
deleted file mode 100644
index 7266d2876..000000000
--- a/client/src/assets/icons/ruler-icon.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/search.svg b/client/src/assets/icons/search.svg
deleted file mode 100644
index a1bc74224..000000000
--- a/client/src/assets/icons/search.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/selector-vertical.svg b/client/src/assets/icons/selector-vertical.svg
deleted file mode 100644
index 29d9a4998..000000000
--- a/client/src/assets/icons/selector-vertical.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/settings-bold.svg b/client/src/assets/icons/settings-bold.svg
deleted file mode 100644
index d8057286c..000000000
--- a/client/src/assets/icons/settings-bold.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/client/src/assets/icons/settings.svg b/client/src/assets/icons/settings.svg
deleted file mode 100644
index eb86f1d19..000000000
--- a/client/src/assets/icons/settings.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/client/src/assets/icons/solana_logo.svg b/client/src/assets/icons/solana_logo.svg
deleted file mode 100644
index bd349af26..000000000
--- a/client/src/assets/icons/solana_logo.svg
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/client/src/assets/icons/solana_logo_banner.svg b/client/src/assets/icons/solana_logo_banner.svg
deleted file mode 100644
index 08ec87cb1..000000000
--- a/client/src/assets/icons/solana_logo_banner.svg
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/client/src/assets/icons/spedometer-icon.svg b/client/src/assets/icons/spedometer-icon.svg
deleted file mode 100644
index 19b13f44e..000000000
--- a/client/src/assets/icons/spedometer-icon.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/speedometer-icon.svg b/client/src/assets/icons/speedometer-icon.svg
deleted file mode 100644
index 19b13f44e..000000000
--- a/client/src/assets/icons/speedometer-icon.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/status-pages.svg b/client/src/assets/icons/status-pages.svg
deleted file mode 100644
index 97cea88fb..000000000
--- a/client/src/assets/icons/status-pages.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/support.svg b/client/src/assets/icons/support.svg
deleted file mode 100644
index 7de16379a..000000000
--- a/client/src/assets/icons/support.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/top-right-arrow.svg b/client/src/assets/icons/top-right-arrow.svg
deleted file mode 100644
index d569cdd7e..000000000
--- a/client/src/assets/icons/top-right-arrow.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/trash-bin.svg b/client/src/assets/icons/trash-bin.svg
deleted file mode 100644
index 83bca0c6f..000000000
--- a/client/src/assets/icons/trash-bin.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/up-arrow.svg b/client/src/assets/icons/up-arrow.svg
deleted file mode 100644
index 0f8f3bc34..000000000
--- a/client/src/assets/icons/up-arrow.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/uptime-icon.svg b/client/src/assets/icons/uptime-icon.svg
deleted file mode 100644
index 55706ae71..000000000
--- a/client/src/assets/icons/uptime-icon.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/user-edit.svg b/client/src/assets/icons/user-edit.svg
deleted file mode 100644
index cbd1a252f..000000000
--- a/client/src/assets/icons/user-edit.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/user-two.svg b/client/src/assets/icons/user-two.svg
deleted file mode 100644
index 031b22787..000000000
--- a/client/src/assets/icons/user-two.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/user.svg b/client/src/assets/icons/user.svg
deleted file mode 100644
index 231c89d40..000000000
--- a/client/src/assets/icons/user.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/assets/icons/warning-icon.svg b/client/src/assets/icons/warning-icon.svg
deleted file mode 100644
index 468611758..000000000
--- a/client/src/assets/icons/warning-icon.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/client/src/index.css b/client/src/index.css
index d58a7e872..48737d4bb 100644
--- a/client/src/index.css
+++ b/client/src/index.css
@@ -4,6 +4,11 @@
box-sizing: border-box;
}
+html,
+body {
+ overscroll-behavior: none;
+}
+
html {
scroll-behavior: smooth;
}
@@ -45,7 +50,8 @@ html {
cursor: not-allowed;
}
-.Toastify__toast-container {
+/* .Toastify__toast-container {
+ min-width: 300px;
width: auto;
}
.Toastify__toast-body .alert {
@@ -63,7 +69,7 @@ html {
}
.Toastify [class*="Toastify__toast-theme"] {
background-color: transparent;
-}
+} */
.MuiTouchRipple-root {
display: none;
diff --git a/client/src/locales/de.json b/client/src/locales/de.json
index da5a3ee61..fd3918378 100644
--- a/client/src/locales/de.json
+++ b/client/src/locales/de.json
@@ -1129,5 +1129,12 @@
"packetsReceivedRate": "Rate empfangener Pakete",
"packetsSent": "Gesendete Pakete",
"rate": "Rate",
- "selectInterface": "Schnittstelle auswählen"
+ "selectInterface": "Schnittstelle auswählen",
+ "v1": {
+ "infrastructure": {
+ "disk_selection_title": "Festplattenauswahl",
+ "disk_selection_info": "Momentan wurde keine Festplatte erkannt.",
+ "disk_selection_description": "Wählen Sie die spezifischen Festplatten aus, die Sie überwachen möchten."
+ }
+ }
}
diff --git a/client/src/locales/en.json b/client/src/locales/en.json
index 13d4a7731..7386ced7d 100644
--- a/client/src/locales/en.json
+++ b/client/src/locales/en.json
@@ -1,8 +1,61 @@
{
+ "common": {
+ "appName": "Checkmate",
+ "monitoringAgentName": "Capture",
+ "labels": {
+ "responseTime": "Response time"
+ },
+ "buttons": {
+ "toggleTheme": "Toggles light & dark",
+ "testNotifications": "Test notifications",
+ "create": "Create",
+ "incidents": "Incidents",
+ "pause": "Pause",
+ "resume": "Resume",
+ "delete": "Delete",
+ "cancel": "Cancel",
+ "confirm": "Confirm"
+ },
+ "alerts": {
+ "pageSpeedApiKey": {
+ "content": "Warning: You haven't added a Google PageSpeed API key yet. Visit Settings to add one. Without it, the PageSpeed monitor won't function."
+ }
+ },
+ "dialogs": {
+ "delete": {
+ "description": "This action cannot be undone.",
+ "title": "Are you sure you want to delete this?"
+ }
+ },
+ "toasts": {
+ "networkError": "Network error",
+ "checkConnection": "Please check your connection",
+ "unknownError": "Unknown error"
+ },
+ "table": {
+ "headers": {
+ "actions": "Actions",
+ "active": "Active",
+ "message": "Message",
+ "monitor": "Monitor",
+ "name": "Name",
+ "status": "Status",
+ "type": "Type"
+ },
+ "empty": "Nothing here"
+ },
+ "charts": {
+ "labels": {
+ "uptime": "Uptime",
+ "downtime": "Downtime",
+ "averageResponseTime": "Average response time",
+ "low": "Low",
+ "high": "High"
+ }
+ }
+ },
"submit": "Submit",
"title": "Title",
- "distributedStatusHeaderText": "Real-time, real-device coverage",
- "distributedStatusSubHeaderText": "Powered by millions devices worldwide, view a system performance by global region, country or city",
"settingsDisabled": "Disabled",
"settingsSuccessSaved": "Settings saved successfully",
"settingsFailedToSave": "Failed to save settings",
@@ -20,6 +73,13 @@
"delete": "Delete",
"configure": "Configure",
"responseTime": "Response time",
+ "responseTimeBreakdown": "Response time breakdown",
+ "dnsLookup": "DNS lookup",
+ "tcpConnection": "TCP connection",
+ "tlsHandshake": "TLS handshake",
+ "waitTime": "Wait time",
+ "timeToFirstByte": "Time to first byte",
+ "download": "Download",
"ms": "ms",
"bar": "Bar",
"area": "Area",
@@ -31,18 +91,11 @@
"webhookSendSuccess": "Webhook notification sent successfully",
"webhookSendError": "Error sending webhook notification to {platform}",
"webhookUnsupportedPlatform": "Unsupported platform: {platform}",
- "distributedRightCategoryTitle": "Monitor",
- "distributedStatusServerMonitors": "Server Monitors",
- "distributedStatusServerMonitorsDescription": "Monitor status of related servers",
"distributedUptimeCreateSelectURL": "Here you can select the URL of the host, together with the type of monitor.",
"distributedUptimeCreateChecks": "Checks to perform",
"distributedUptimeCreateChecksDescription": "You can always add or remove checks after adding your site.",
- "distributedUptimeCreateIncidentNotification": "Incident notifications",
- "distributedUptimeCreateIncidentDescription": "When there is an incident, notify users.",
"distributedUptimeCreateAdvancedSettings": "Advanced settings",
"distributedUptimeDetailsNoMonitorHistory": "There is no check history for this monitor yet.",
- "distributedUptimeDetailsStatusHeaderUptime": "Uptime:",
- "distributedUptimeDetailsStatusHeaderLastUpdate": "Last updated",
"notifications": {
"enableNotifications": "Enable {{platform}} notifications",
"testNotification": "Test notification",
@@ -115,37 +168,87 @@
"failed": "Failed to send test notification"
}
},
- "testLocale": "testLocale",
"add": "Add",
"monitors": "monitors",
- "distributedUptimeStatusCreateStatusPage": "status page",
- "distributedUptimeStatusCreateStatusPageAccess": "Access",
- "distributedUptimeStatusCreateStatusPageReady": "If your status page is ready, you can mark it as published.",
- "distributedUptimeStatusBasicInfoHeader": "Basic Information",
- "distributedUptimeStatusBasicInfoDescription": "Define company name and the subdomain that your status page points to.",
- "distributedUptimeStatusLogoHeader": "Logo",
- "distributedUptimeStatusLogoDescription": "Upload a logo for your status page",
- "distributedUptimeStatusLogoUploadButton": "Upload logo",
- "distributedUptimeStatusStandardMonitorsHeader": "Standard Monitors",
- "distributedUptimeStatusStandardMonitorsDescription": "Attach standard monitors to your status page.",
- "distributedUptimeStatusCreateYour": "Create your",
- "distributedUptimeStatusEditYour": "Edit your",
- "distributedUptimeStatusPublishedLabel": "Published and visible to the public",
- "distributedUptimeStatusCompanyNameLabel": "Company name",
- "distributedUptimeStatusPageAddressLabel": "Your status page address",
- "distributedUptimeStatus30Days": "30 days",
- "distributedUptimeStatus60Days": "60 days",
- "distributedUptimeStatus90Days": "90 days",
- "distributedUptimeStatusPageNotSetUp": "A status page is not set up.",
- "distributedUptimeStatusContactAdmin": "Please contact your administrator",
- "distributedUptimeStatusPageNotPublic": "This status page is not public.",
- "distributedUptimeStatusPageDeleteDialog": "Do you want to delete this status page?",
- "distributedUptimeStatusPageDeleteConfirm": "Yes, delete status page",
- "distributedUptimeStatusPageDeleteDescription": "Once deleted, your status page cannot be retrieved.",
- "distributedUptimeStatusDevices": "Devices",
- "distributedUptimeStatusUpt": "UPT",
- "distributedUptimeStatusUptBurned": "UPT Burned",
- "distributedUptimeStatusUptLogo": "Upt Logo",
+ "pages": {
+ "common": {
+ "monitors": {
+ "status": {
+ "up": "up",
+ "down": "down",
+ "paused": "paused",
+ "initializing": "initializing"
+ },
+ "actions": {
+ "openSite": "Open site",
+ "details": "Details",
+ "incidents": "Incidents",
+ "configure": "Configure",
+ "pause": "Pause",
+ "resume": "Resume",
+ "delete": "Delete"
+ },
+ "statBoxes": {
+ "activeFor": "Active for",
+ "lastCheck": "Last check",
+ "lastResponseTime": "Last response time",
+ "certificateExpiry": "Certificate expiry"
+ }
+ }
+ },
+ "uptime": {
+ "table": {
+ "headers": {
+ "responseTime": "Response time"
+ }
+ },
+ "fallback": {
+ "title": "An uptime monitor is used to:",
+ "actionButton": "Create a monitor!",
+ "checks": [
+ "Check if websites or servers are online & responsive",
+ "Alert teams about downtime or performance issues",
+ "Monitor HTTP endpoints, pings, containers & ports",
+ "Track historical uptime and reliability trends"
+ ]
+ },
+ "filters": {
+ "search": {
+ "placeholder": "Search monitors..."
+ }
+ }
+ },
+ "checks": {
+ "table": {
+ "headers": {
+ "dateTime": "Date & time",
+ "statusCode": "Status code"
+ }
+ }
+ },
+ "pageSpeed": {
+ "table": {
+ "headers": {
+ "pageSpeedScore": "PageSpeed score"
+ }
+ },
+ "charts": {
+ "common": {
+ "cls": "Cumulative Layout Shift (CLS)",
+ "fcp": "First Contentful Paint (FCP)",
+ "lcp": "Largest Contentful Paint (LCP)",
+ "si": "Speed Index (SI)",
+ "tbt": "Total Blocking Time (TBT)"
+ },
+ "pie": { "title": "Performance report" },
+ "legend": {
+ "title": "PageSpeed report",
+
+ "weight": "Weight"
+ }
+ }
+ }
+ },
"incidentsTableNoIncidents": "No incidents recorded",
"incidentsTablePaginationLabel": "incidents",
"incidentsTableMonitorName": "Monitor Name",
@@ -185,6 +288,8 @@
"total": "Total",
"cores": "Cores",
"frequency": "Frequency",
+ "device": "Device",
+ "mountedOn": "Mounted on",
"status": "Status",
"cpuPhysical": "CPU (Physical)",
"cpuLogical": "CPU (Logical)",
@@ -239,6 +344,7 @@
"addMonitors": "Add monitors",
"window": "window",
"cancel": "Cancel",
+ "close": "Close",
"message": "Message",
"low": "low",
"high": "high",
@@ -456,7 +562,8 @@
"team": "Team",
"logOut": "Log out",
"notifications": "Notifications",
- "logs": "Logs"
+ "logs": "Logs",
+ "checks": "Checks"
},
"settingsEmailUser": "Email user - Username for authentication, overrides email address if specified",
"state": "State",
@@ -465,6 +572,7 @@
"commonSaving": "Saving...",
"navControls": "Controls",
"incidentsPageTitle": "Incidents",
+ "checksPageTitle": "Checks",
"passwordPanel": {
"passwordChangedSuccess": "Your password was changed successfully.",
"passwordInputIncorrect": "Your password input was incorrect.",
@@ -473,7 +581,7 @@
"newPassword": "New password",
"enterNewPassword": "Enter your new password",
"confirmNewPassword": "Confirm new password",
- "passwordRequirements": "New password must contain at least 8 characters and must have at least one uppercase letter, one lowercase letter, one number and one special character.",
+ "passwordRequirements": "New password must contain at least 8 characters and must have at least one uppercase letter, one lowercase letter, one number and one special character (!?@#$%^&*()-_=+[]{}|;:'\",./\\~`).",
"saving": "Saving..."
},
"emailSent": "Email sent successfully",
@@ -496,18 +604,6 @@
"game": "Enter the IP address or hostname and the port number to ping (e.g., 192.168.1.100 or example.com) and choose game type.",
"https": "Enter the URL or IP to monitor (e.g., https://example.com/ or 192.168.1.100) and add a clear display name that appears on the dashboard."
},
- "common": {
- "appName": "Checkmate",
- "monitoringAgentName": "Capture",
- "buttons": {
- "toggleTheme": "Toggles light & dark"
- },
- "toasts": {
- "networkError": "Network error",
- "checkConnection": "Please check your connection",
- "unknownError": "Unknown error"
- }
- },
"auth": {
"common": {
"navigation": {
@@ -532,7 +628,7 @@
},
"special": {
"beginning": "Must contain at least",
- "highlighted": "one special character"
+ "highlighted": "one special character (!?@#$%^&*()-_=+[]{}|;:'\",./\\~`)"
},
"number": {
"beginning": "Must contain at least",
@@ -557,7 +653,7 @@
"uppercase": "Password must contain at least 1 uppercase letter",
"lowercase": "Password must contain at least 1 lowercase letter",
"number": "Password must contain at least 1 number",
- "special": "Password must contain at least 1 special character",
+ "special": "Password must contain at least 1 special character (!?@#$%^&*()-_=+[]{}|;:'\",./\\~`)",
"incorrect": "The password you provided does not match our records"
}
},
@@ -781,7 +877,16 @@
"error": "Error",
"debug": "Debug"
}
- }
+ },
+ "table": {
+ "timestamp": "Timestamp",
+ "level": "Level",
+ "service": "Service",
+ "method": "Method",
+ "message": "Message",
+ "logs": "logs"
+ },
+ "noLogs": "No logs found"
},
"queuePage": {
"title": "Queue",
@@ -897,6 +1002,9 @@
"description": "Switch between light and dark mode, or change user interface language.",
"labelLanguage": "Language",
"labelTheme": "Theme mode",
+ "labelChartType": "Chart type",
+ "chartTypeHistogram": "Histogram",
+ "chartTypeHeatmap": "Heatmap",
"title": "Appearance"
},
"urlSettings": {
@@ -975,6 +1083,10 @@
},
"pageSpeedLighthouseAPI": "Use the Lighthouse PageSpeed API to monitor your website",
"time": {
+ "fifteenSeconds": "15 seconds",
+ "thirtySeconds": "30 seconds",
+ "fifteenMinutes": "15 minutes",
+ "thirtyMinutes": "30 minutes",
"threeMinutes": "3 minutes",
"fiveMinutes": "5 minutes",
"tenMinutes": "10 minutes",
@@ -984,9 +1096,7 @@
"oneWeek": "1 week",
"fourMinutes": "4 minutes",
"oneMinute": "1 minute",
- "twoMinutes": "2 minutes",
- "fifteenSeconds": "15 seconds",
- "thirtySeconds": "30 seconds"
+ "twoMinutes": "2 minutes"
},
"general": {
"noOptionsFound": "No {{unit}} found"
@@ -1055,7 +1165,9 @@
}
},
"incidentsPageActionResolveMonitor": "Resolve monitor incidents",
+ "checksPageActionResolveMonitor": "Resolve monitor checks",
"incidentsPageActionResolveAll": "Resolve all incidents",
+ "checksPageActionResolveAllMonitor": "Resolve all checks",
"matchMethodOptions": {
"equal": "Equal",
"equalPlaceholder": "success",
@@ -1129,5 +1241,88 @@
"packetsReceivedRate": "Packets Received Rate",
"packetsSent": "Packets Sent",
"rate": "Rate",
- "selectInterface": "Select Interface"
+ "selectInterface": "Select Interface",
+ "v1": {
+ "infrastructure": {
+ "disk_selection_title": "Disk Selection",
+ "disk_selection_info": "No disk detected for the moment.",
+ "disk_selection_description": "Select the specific disks you want to monitor."
+ }
+ },
+ "incidentsPage": {
+ "title": "Incidents",
+ "description": "Manage incidents for your monitors. You can resolve incidents here.",
+ "resolved": "Resolved",
+ "active": "Active",
+ "startTime": "Start Time",
+ "endTime": "End Time",
+ "resolutionType": "Resolution Type",
+ "unresolved": "Unresolved",
+ "all": "All",
+ "filterByMonitor": "Filter by monitor",
+ "filterByStatus": "Filter by status",
+ "filterByType": "Filter by type",
+ "filterByDate": "Filter by date",
+ "filterByPriority": "Filter by priority",
+ "incidentsActivePanelError": "Error loading active incidents",
+ "allSystemsAreOperational": "All Systems Are Operational",
+ "incidentsActivePanelTitle": "Total Active Incidents",
+ "incidentsLatestPanelError": "Error loading latest incidents",
+ "incidentsLatestPanelTitle": "Latest Incidents",
+ "incidentsLatestPanelEmpty": "No recent incidents",
+ "incidentItemMonitor": "Monitor",
+ "incidentItemStatus": "Status",
+ "unknownMonitor": "Unknown Monitor",
+ "incidentsStatisticsPanelError": "Error loading statistics",
+ "incidentsStatisticsPanelTitle": "General Statistics",
+ "totalIncidents": "Total Incidents",
+ "mostAffectedMonitor": "Most Affected Monitor",
+ "avgResolutionTime": "Avg. Resolution Time",
+ "resolutions": "Resolutions",
+ "automatic": "Automatic",
+ "manual": "Manual",
+ "incidentResolvedSuccessfully": "Incident resolved successfully",
+ "incidentResolvedFailed": "Error resolving incident",
+ "errorFetchingIncidents": "Error fetching incidents",
+ "errorFetchingIncident": "Error fetching incident",
+ "noIncidentIdProvided": "No incident ID provided",
+ "errorFetchingIncidentSummary": "Error fetching incident summary",
+ "incidentsOptionsHeaderFilterManual": "Manual",
+ "incidentsOptionsHeaderFilterAutomatic": "Automatic",
+ "incidentsOptionsHeaderFilterAll": "All",
+ "incidentsOptionsHeaderFilterActive": "Active",
+ "incidentsOptionsHeaderFilterResolved": "Resolved",
+ "incidentsTableResolved": "Closed",
+ "incidentsTableActionResolveManually": "Resolve Manually",
+ "incidentsTableActionDetails": "Details",
+ "incidentsTableActionGoToMonitor": "Go to monitor",
+ "resolveIncidentDialogTitle": "Resolve Incident",
+ "resolveIncidentDialogDescription": "Are you sure you want to resolve this incident manually?",
+ "resolveIncidentDialogCommentLabel": "Comment (optional)",
+ "resolveIncidentDialogCommentPlaceholder": "Add a comment about the resolution...",
+ "resolveIncidentDialogConfirm": "Resolve",
+ "hours": "hours",
+ "loading": "Loading incident details...",
+ "incidentNotFound": "Incident not found",
+ "incidentDetails": "INCIDENT DETAILS",
+ "activeIncident": "ACTIVE INCIDENT",
+ "resolvedIncident": "RESOLVED INCIDENT",
+ "overview": "OVERVIEW",
+ "URL": "URL",
+ "incidentAnalysis": "INCIDENT ANALYSIS",
+ "timeline": "Timeline",
+ "startedAt": "Started at",
+ "endedAt": "Ended at",
+ "downtime": "Downtime",
+ "errorDetails": "Error details",
+ "resolutionDetails": "Resolution details",
+ "statusCode": "Status code",
+ "resolutionMethod": "Resolution method",
+ "resolvedBy": "Resolved by",
+ "comment": "Comment",
+ "noCommentProvided": "No comment provided",
+ "loadingIncidentDetails": "Loading incident details...",
+ "none": "None",
+ "message": "Message"
+ }
}
diff --git a/client/src/locales/fr.json b/client/src/locales/fr.json
index 1ebc8bad4..9e76440f0 100644
--- a/client/src/locales/fr.json
+++ b/client/src/locales/fr.json
@@ -1129,5 +1129,12 @@
"packetsReceivedRate": "",
"packetsSent": "",
"rate": "",
- "selectInterface": ""
+ "selectInterface": "",
+ "v1": {
+ "infrastructure": {
+ "disk_selection_title": "Sélection des disques",
+ "disk_selection_info": "Aucun disque détecté pour le moment.",
+ "disk_selection_description": "Sélectionnez les disques spécifiques que vous souhaitez surveiller."
+ }
+ }
}
diff --git a/client/src/locales/ja.json b/client/src/locales/ja.json
index f458b8ed0..68d94139f 100644
--- a/client/src/locales/ja.json
+++ b/client/src/locales/ja.json
@@ -1108,5 +1108,12 @@
"packetsReceivedRate": "",
"packetsSent": "",
"rate": "",
- "selectInterface": ""
+ "selectInterface": "",
+ "v1": {
+ "infrastructure": {
+ "disk_selection_title": "ディスク選択",
+ "disk_selection_info": "現在ディスクが検出されていません。",
+ "disk_selection_description": "監視したい特定のディスクを選択してください。"
+ }
+ }
}
diff --git a/client/src/locales/pt-BR.json b/client/src/locales/pt-BR.json
index 1a6d9a62f..193c53788 100644
--- a/client/src/locales/pt-BR.json
+++ b/client/src/locales/pt-BR.json
@@ -1129,5 +1129,12 @@
"packetsReceivedRate": "",
"packetsSent": "",
"rate": "",
- "selectInterface": ""
+ "selectInterface": "",
+ "v1": {
+ "infrastructure": {
+ "disk_selection_title": "Seleção de Discos",
+ "disk_selection_info": "Nenhum disco detectado no momento.",
+ "disk_selection_description": "Selecione os discos específicos que você deseja monitorar."
+ }
+ }
}
diff --git a/client/src/locales/ru.json b/client/src/locales/ru.json
index cdb4d235d..ab40ecb0d 100644
--- a/client/src/locales/ru.json
+++ b/client/src/locales/ru.json
@@ -1129,5 +1129,12 @@
"packetsReceivedRate": "Скорость получения пакетов",
"packetsSent": "Отправлено пакетов",
"rate": "коэффициент",
- "selectInterface": "Выберите интерфейс"
+ "selectInterface": "Выберите интерфейс",
+ "v1": {
+ "infrastructure": {
+ "disk_selection_title": "Выбор дисков",
+ "disk_selection_info": "На данный момент диск не обнаружен.",
+ "disk_selection_description": "Выберите конкретные диски, которые вы хотите отслеживать."
+ }
+ }
}
diff --git a/client/src/locales/th.json b/client/src/locales/th.json
index c32849afe..d5f9ad137 100644
--- a/client/src/locales/th.json
+++ b/client/src/locales/th.json
@@ -1129,5 +1129,12 @@
"packetsReceivedRate": "อัตราการรับแพ็กเก็ต",
"packetsSent": "แพ็กเก็ตที่ส่ง",
"rate": "อัตรา",
- "selectInterface": "เลือกอินเทอร์เฟซ"
+ "selectInterface": "เลือกอินเทอร์เฟซ",
+ "v1": {
+ "infrastructure": {
+ "disk_selection_title": "การเลือกดิสก์",
+ "disk_selection_info": "ไม่พบดิสก์ในขณะนี้",
+ "disk_selection_description": "เลือกดิสก์ที่คุณต้องการตรวจสอบ"
+ }
+ }
}
diff --git a/client/src/locales/zh-CN.json b/client/src/locales/zh-CN.json
index 305cefe87..67b419445 100644
--- a/client/src/locales/zh-CN.json
+++ b/client/src/locales/zh-CN.json
@@ -1121,5 +1121,12 @@
"packetsReceivedRate": "数据包接收率",
"packetsSent": "已发送数据包",
"rate": "速度",
- "selectInterface": "选择接口"
+ "selectInterface": "选择接口",
+ "v1": {
+ "infrastructure": {
+ "disk_selection_title": "磁盘选择",
+ "disk_selection_info": "目前没有检测到磁盘。",
+ "disk_selection_description": "选择您要监控的特定磁盘。"
+ }
+ }
}
diff --git a/client/src/main.jsx b/client/src/main.jsx
index dd42587af..5cfbe6f33 100644
--- a/client/src/main.jsx
+++ b/client/src/main.jsx
@@ -9,6 +9,9 @@ import NetworkServiceProvider from "./Utils/NetworkServiceProvider.jsx";
import { networkService } from "./Utils/NetworkService";
export { networkService };
import I18nLoader from "./Components/v1/I18nLoader";
+import { initApiClient } from "./Utils/ApiClient";
+
+initApiClient(store);
ReactDOM.createRoot(document.getElementById("root")).render(
diff --git a/client/src/store.js b/client/src/store.js
index 565d68519..69aa3d50b 100644
--- a/client/src/store.js
+++ b/client/src/store.js
@@ -1,7 +1,6 @@
import { configureStore, combineReducers } from "@reduxjs/toolkit";
import authReducer from "./Features/Auth/authSlice";
-import v2AuthReducer from "./Features/Auth/v2AuthSlice";
import uiReducer from "./Features/UI/uiSlice";
import storage from "redux-persist/lib/storage";
import { persistReducer, persistStore, createTransform } from "redux-persist";
@@ -20,13 +19,12 @@ const authTransform = createTransform(
const persistConfig = {
key: "root",
storage,
- whitelist: ["auth", "v2Auth", "ui"],
+ whitelist: ["auth", "ui"],
transforms: [authTransform],
};
const rootReducer = combineReducers({
auth: authReducer,
- v2Auth: v2AuthReducer,
ui: uiReducer,
});
diff --git a/client/vite.config.ts b/client/vite.config.ts
index 5a6b428c7..3d8d66335 100644
--- a/client/vite.config.ts
+++ b/client/vite.config.ts
@@ -12,6 +12,9 @@ export default defineConfig(({}) => {
return {
base: "/",
plugins: [svgr(), react()],
+ server: {
+ host: true,
+ },
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),
diff --git a/docker/coolify/server.Dockerfile b/docker/coolify/server.Dockerfile
index 238bd404c..225b47695 100755
--- a/docker/coolify/server.Dockerfile
+++ b/docker/coolify/server.Dockerfile
@@ -13,6 +13,8 @@ RUN npm install
COPY ../../ ./
+RUN npm run build
+
EXPOSE 5000
-CMD ["node", "index.js"]
\ No newline at end of file
+CMD ["node", "dist/index.js"]
diff --git a/docker/dist-arm/docker-compose.yaml b/docker/dist-arm/docker-compose.yaml
index 0a5825b3e..9a87aa59b 100644
--- a/docker/dist-arm/docker-compose.yaml
+++ b/docker/dist-arm/docker-compose.yaml
@@ -15,7 +15,7 @@ services:
depends_on:
- mongodb
mongodb:
- image: mongo:4.4.18
+ image: mongo:4.4.30
restart: always
command: ["mongod", "--quiet", "--bind_ip_all"]
ports:
diff --git a/docker/prod/server.Dockerfile b/docker/prod/server.Dockerfile
index 9d9800e7b..7341b1e9b 100755
--- a/docker/prod/server.Dockerfile
+++ b/docker/prod/server.Dockerfile
@@ -19,4 +19,4 @@ RUN npm run build
EXPOSE 52345
-CMD ["node", "src/index.js"]
\ No newline at end of file
+CMD ["node", "dist/index.js"]
\ No newline at end of file
diff --git a/docs/infrastructure-disk-selection.md b/docs/infrastructure-disk-selection.md
new file mode 100644
index 000000000..500e3be1e
--- /dev/null
+++ b/docs/infrastructure-disk-selection.md
@@ -0,0 +1,74 @@
+# Infrastructure Monitoring - Disk Selection
+
+This guide explains how to use the selective disk monitoring feature in Checkmate's infrastructure monitoring.
+
+## Overview
+
+By default, Checkmate monitors all detected disks on your server. The disk selection feature allows you to choose specific disks/mountpoints to monitor, giving you more control over what gets tracked and displayed.
+
+## Prerequisites
+
+- Checkmate server running with [Capture agent](https://github.com/bluewave-labs/capture) installed on target server
+- An existing infrastructure monitor already created and configured
+- At least one disk detected by the Capture agent
+
+## Using Disk Selection
+
+### Accessing the Feature
+
+1. Navigate to your Infrastructure monitors page
+2. Click on an existing infrastructure monitor to view details
+3. Click the "Configure" or "Edit" button
+4. Scroll down to the **Disk Selection** section
+
+> **Note:** The disk selection feature is only available when editing existing monitors that have already detected disks.
+
+### Selecting Disks to Monitor
+
+1. **View Available Disks**: The system automatically detects all disks/mountpoints from your server
+2. **Select Disks**: Check the boxes next to the disks you want to monitor
+3. **Apply Changes**: Click "Save" to update your monitor configuration
+
+### Understanding Disk Identifiers
+
+Disks are identified by their mountpoint or device name:
+- **Mountpoint**: `/` (root), `/home`, `/var`, etc.
+- **Device**: `/dev/sda1`, `/dev/nvme0n1p1`, etc.
+
+### Behavior
+
+- **All disks selected**: Shows all detected disks in gauges and charts
+- **Specific disks selected**: Only shows selected disks in the monitoring interface
+- **No disks detected**: Displays "No disk detected for the moment" message
+
+## What Gets Filtered
+
+When you select specific disks, the following elements are filtered:
+
+- **Disk Usage Gauges**: Only selected disks appear in the dashboard
+- **Disk Usage Charts**: Time-series charts show only selected disks
+- **Monitor Table**: Summary view reflects only selected disk usage
+
+## Example Use Cases
+
+- **Server with multiple drives**: Monitor only critical system disks, ignore temporary storage
+- **Database servers**: Focus monitoring on database partition while ignoring logs partition
+- **Web servers**: Monitor web content disk separately from system disk
+- **Development environments**: Track only project-specific mountpoints
+
+## Troubleshooting
+
+### No Disks Appearing
+- Verify the Capture agent is running on the target server
+- Check that the agent has proper permissions to read disk information
+- Ensure the monitor has been running long enough to collect at least one check
+
+### Missing Disk Information
+- Some disks may not be accessible to the Capture agent due to permissions
+- Virtual or network-mounted disks may not appear in the list
+- Check the Capture agent logs for any disk detection errors
+
+## Related Documentation
+
+- [Infrastructure Monitoring Setup](https://docs.checkmate.so/checkmate-2.1)
+- [Capture Agent Installation](https://github.com/bluewave-labs/capture)
\ No newline at end of file
diff --git a/server/jest.config.ts b/server/jest.config.ts
new file mode 100644
index 000000000..fd0c87ad7
--- /dev/null
+++ b/server/jest.config.ts
@@ -0,0 +1,25 @@
+import type { Config } from "jest";
+
+const config: Config = {
+ rootDir: ".",
+ testEnvironment: "node",
+ extensionsToTreatAsEsm: [".ts"],
+ transform: {
+ "^.+\\.(t|j)sx?$": ["ts-jest", { useESM: true, tsconfig: "./tsconfig.jest.json" }],
+ },
+ moduleNameMapper: {
+ "^@/validation/(.*)\\.js$": "/src/validation/$1.js",
+ "^@/utils/(AppError)\\.js$": "/src/utils/$1.ts",
+ "^@/utils/(.*)\\.js$": "/src/utils/$1.js",
+ "^@/(.*)\\.ts$": "/src/$1.ts",
+ "^@/(.*)\\.js$": "/src/$1.ts",
+ "^@/(.*)$": "/src/$1",
+ },
+ testMatch: ["/test/**/*.test.ts"],
+ setupFilesAfterEnv: [],
+ collectCoverageFrom: ["src/**/*.ts"],
+ coveragePathIgnorePatterns: ["/node_modules/", "/test/"],
+ clearMocks: true,
+};
+
+export default config;
diff --git a/server/nodemon.json b/server/nodemon.json
index 389209fa7..3faf346d3 100755
--- a/server/nodemon.json
+++ b/server/nodemon.json
@@ -1,5 +1,5 @@
{
- "ignore": ["src/locales/*", "*.log", "node_modules/*"],
+ "ignore": ["node_modules/*"],
"watch": ["src/**/*.ts", "src/**/*.js", "*.json"],
"ext": "ts,js,json"
}
diff --git a/server/openapi.json b/server/openapi.json
index 6cc2e7de0..d56c619bf 100644
--- a/server/openapi.json
+++ b/server/openapi.json
@@ -21,13 +21,9 @@
"description": "Local Development Server",
"variables": {
"PORT": {
- "description": "API Port",
- "enum": ["52345"],
"default": "52345"
},
"API_PATH": {
- "description": "API Base Path",
- "enum": ["api/v1"],
"default": "api/v1"
}
}
@@ -37,8 +33,6 @@
"description": "Checkmate Demo Server",
"variables": {
"API_PATH": {
- "description": "API Base Path",
- "enum": ["api/v1"],
"default": "api/v1"
}
}
@@ -88,6 +82,10 @@
{
"name": "diagnostic",
"description": "System health and performance diagnostics"
+ },
+ {
+ "name": "incidents",
+ "description": "Incident management and resolution"
}
],
"paths": {
@@ -164,51 +162,6 @@
}
}
},
- "/auth/refresh": {
- "post": {
- "tags": ["auth"],
- "summary": "Refresh access token",
- "description": "Generate new access token using refresh token",
- "parameters": [
- {
- "name": "x-refresh-token",
- "in": "header",
- "description": "Refresh token",
- "required": true,
- "schema": {
- "type": "string"
- }
- },
- {
- "name": "authorization",
- "in": "header",
- "description": "Bearer token (old access token)",
- "required": true,
- "schema": {
- "type": "string"
- }
- }
- ],
- "responses": {
- "200": {
- "description": "Token refreshed successfully",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/AuthResponse"
- }
- }
- }
- },
- "401": {
- "$ref": "#/components/responses/Unauthorized"
- },
- "500": {
- "$ref": "#/components/responses/InternalServerError"
- }
- }
- }
- },
"/auth/user/{userId}": {
"put": {
"tags": ["auth"],
@@ -731,28 +684,54 @@
]
}
},
- "/monitors/team/summary": {
+ "/monitors/team/groups": {
"get": {
"tags": ["monitors"],
- "summary": "Get monitors summary",
- "description": "Get team monitors with summary statistics",
- "parameters": [
- {
- "name": "type",
- "in": "query",
- "description": "Filter by monitor type",
- "schema": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port"]
- }
- }
- }
- ],
+ "summary": "Get monitor groups by team",
+ "description": "Get all monitor groups for the authenticated user's team",
"responses": {
"200": {
- "description": "Monitor summary retrieved successfully"
+ "description": "Monitor groups retrieved successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "example": true
+ },
+ "msg": {
+ "type": "string",
+ "example": "Monitor groups retrieved successfully"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "_id": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "monitors": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
},
"500": {
"$ref": "#/components/responses/InternalServerError"
@@ -992,24 +971,78 @@
]
}
},
- "/monitors/stats/{monitorId}": {
+ "/monitors/pagespeed/details/{monitorId}": {
"get": {
"tags": ["monitors"],
- "summary": "Get monitor statistics",
- "description": "Get comprehensive statistics for a monitor",
+ "summary": "Get PageSpeed monitor details",
+ "description": "Get detailed PageSpeed monitoring data including performance metrics and Core Web Vitals",
"parameters": [
{
"name": "monitorId",
"in": "path",
"required": true,
+ "description": "The PageSpeed monitor ID",
"schema": {
"type": "string"
}
+ },
+ {
+ "name": "sortOrder",
+ "in": "query",
+ "description": "Sort order for results",
+ "schema": {
+ "type": "string",
+ "enum": ["asc", "desc"],
+ "default": "desc"
+ }
+ },
+ {
+ "name": "limit",
+ "in": "query",
+ "description": "Number of results to return",
+ "schema": {
+ "type": "integer",
+ "default": 50
+ }
}
],
"responses": {
"200": {
- "description": "Monitor statistics retrieved successfully"
+ "description": "PageSpeed details retrieved successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "example": true
+ },
+ "msg": {
+ "type": "string",
+ "example": "PageSpeed details retrieved successfully"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "monitor": {
+ "$ref": "#/components/schemas/Monitor"
+ },
+ "checks": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Check"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
},
"404": {
"$ref": "#/components/responses/NotFound"
@@ -1078,22 +1111,44 @@
]
}
},
- "/monitors/export": {
+ "/monitors/export/json": {
"get": {
"tags": ["monitors"],
- "summary": "Export monitors to CSV",
- "description": "Export all monitors to CSV format",
+ "summary": "Export monitors to JSON",
+ "description": "Export all monitors to JSON format (admin/superadmin only)",
"responses": {
"200": {
- "description": "CSV file generated successfully",
+ "description": "JSON file generated successfully",
"content": {
- "text/csv": {
+ "application/json": {
"schema": {
- "type": "string"
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "example": true
+ },
+ "msg": {
+ "type": "string",
+ "example": "Monitors exported successfully"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Monitor"
+ }
+ }
+ }
}
}
}
},
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "403": {
+ "$ref": "#/components/responses/Forbidden"
+ },
"500": {
"$ref": "#/components/responses/InternalServerError"
}
@@ -1262,6 +1317,39 @@
}
},
"/notifications": {
+ "post": {
+ "tags": ["notifications"],
+ "summary": "Create notification",
+ "description": "Create a new notification integration",
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateNotificationRequest"
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "Notification created successfully"
+ },
+ "422": {
+ "$ref": "#/components/responses/ValidationError"
+ },
+ "500": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ]
+ }
+ },
+ "/notifications/team": {
"get": {
"tags": ["notifications"],
"summary": "Get team notifications",
@@ -1301,37 +1389,6 @@
"bearerAuth": []
}
]
- },
- "post": {
- "tags": ["notifications"],
- "summary": "Create notification",
- "description": "Create a new notification integration",
- "requestBody": {
- "required": true,
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/CreateNotificationRequest"
- }
- }
- }
- },
- "responses": {
- "201": {
- "description": "Notification created successfully"
- },
- "422": {
- "$ref": "#/components/responses/ValidationError"
- },
- "500": {
- "$ref": "#/components/responses/InternalServerError"
- }
- },
- "security": [
- {
- "bearerAuth": []
- }
- ]
}
},
"/notifications/{id}": {
@@ -1722,6 +1779,64 @@
]
}
},
+ "/auth/users/{userId}/password": {
+ "put": {
+ "tags": ["auth"],
+ "summary": "Update user password by ID",
+ "description": "Update a specific user's password by ID (superadmin only)",
+ "parameters": [
+ {
+ "name": "userId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": ["password"],
+ "properties": {
+ "password": {
+ "type": "string",
+ "format": "password",
+ "minLength": 8,
+ "description": "New password for the user"
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "$ref": "#/components/responses/Success"
+ },
+ "404": {
+ "$ref": "#/components/responses/NotFound"
+ },
+ "403": {
+ "$ref": "#/components/responses/Forbidden"
+ },
+ "422": {
+ "$ref": "#/components/responses/ValidationError"
+ },
+ "500": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ]
+ }
+ },
"/invite": {
"post": {
"tags": ["invite"],
@@ -2030,6 +2145,21 @@
}
}
],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "ack": {
+ "type": "boolean",
+ "description": "Acknowledgement status"
+ }
+ }
+ }
+ }
+ }
+ },
"responses": {
"200": {
"$ref": "#/components/responses/Success"
@@ -2048,6 +2178,164 @@
]
}
},
+ "/checks/{action}": {
+ "put": {
+ "tags": ["checks"],
+ "summary": "Acknowledge all checks for all monitors",
+ "description": "Acknowledge all checks across all monitors for the team",
+ "parameters": [
+ {
+ "name": "action",
+ "in": "path",
+ "required": true,
+ "description": "Action to perform (e.g., 'ack')",
+ "schema": {
+ "type": "string",
+ "enum": ["ack"]
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": ["ack"],
+ "properties": {
+ "ack": {
+ "type": "boolean",
+ "description": "Acknowledgement status to set"
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Checks acknowledged successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "example": true
+ },
+ "msg": {
+ "type": "string",
+ "example": "All checks acknowledged successfully"
+ },
+ "data": {
+ "type": "object",
+ "description": "Updated checks information"
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "500": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ]
+ }
+ },
+ "/checks/{action}/{monitorId}": {
+ "put": {
+ "tags": ["checks"],
+ "summary": "Acknowledge all checks for a specific monitor",
+ "description": "Acknowledge all checks for a specific monitor",
+ "parameters": [
+ {
+ "name": "action",
+ "in": "path",
+ "required": true,
+ "description": "Action to perform (e.g., 'ack')",
+ "schema": {
+ "type": "string",
+ "enum": ["ack"]
+ }
+ },
+ {
+ "name": "monitorId",
+ "in": "path",
+ "required": true,
+ "description": "Monitor ID to acknowledge checks for",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": ["ack"],
+ "properties": {
+ "ack": {
+ "type": "boolean",
+ "description": "Acknowledgement status to set"
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Checks acknowledged successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "example": true
+ },
+ "msg": {
+ "type": "string",
+ "example": "All checks acknowledged successfully"
+ },
+ "data": {
+ "type": "object",
+ "description": "Updated checks information"
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "404": {
+ "$ref": "#/components/responses/NotFound"
+ },
+ "500": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ]
+ }
+ },
"/maintenance-window": {
"post": {
"tags": ["maintenance-window"],
@@ -2291,6 +2579,142 @@
]
}
},
+ "/queue/jobs": {
+ "get": {
+ "tags": ["queue"],
+ "summary": "Get queue jobs",
+ "description": "Retrieve all jobs in the queue (admin/superadmin only)",
+ "responses": {
+ "200": {
+ "description": "Queue jobs retrieved successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "example": true
+ },
+ "msg": {
+ "type": "string",
+ "example": "Queue jobs fetched successfully"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/QueueJob"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "403": {
+ "$ref": "#/components/responses/Forbidden"
+ },
+ "500": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ]
+ },
+ "post": {
+ "tags": ["queue"],
+ "summary": "Add job to queue",
+ "description": "Add a new job to the queue (admin/superadmin only)",
+ "responses": {
+ "200": {
+ "description": "Job added successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "example": true
+ },
+ "msg": {
+ "type": "string",
+ "example": "Job added to queue successfully"
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "403": {
+ "$ref": "#/components/responses/Forbidden"
+ },
+ "500": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ]
+ }
+ },
+ "/queue/metrics": {
+ "get": {
+ "tags": ["queue"],
+ "summary": "Get queue metrics",
+ "description": "Retrieve queue metrics (admin/superadmin only)",
+ "responses": {
+ "200": {
+ "description": "Queue metrics retrieved successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "example": true
+ },
+ "msg": {
+ "type": "string",
+ "example": "Queue metrics fetched successfully"
+ },
+ "data": {
+ "$ref": "#/components/schemas/QueueMetrics"
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "403": {
+ "$ref": "#/components/responses/Forbidden"
+ },
+ "500": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ]
+ }
+ },
"/queue/health": {
"get": {
"tags": ["queue"],
@@ -2618,6 +3042,286 @@
}
]
}
+ },
+ "/incidents/team": {
+ "get": {
+ "tags": ["incidents"],
+ "summary": "Get incidents by team",
+ "description": "Retrieve all incidents for the authenticated user's team with optional filtering",
+ "parameters": [
+ {
+ "name": "status",
+ "in": "query",
+ "description": "Filter by incident status (true = ongoing, false = resolved)",
+ "schema": {
+ "type": "boolean"
+ }
+ },
+ {
+ "name": "monitorId",
+ "in": "query",
+ "description": "Filter by monitor ID",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "page",
+ "in": "query",
+ "description": "Page number for pagination",
+ "schema": {
+ "type": "integer",
+ "default": 1
+ }
+ },
+ {
+ "name": "limit",
+ "in": "query",
+ "description": "Number of items per page",
+ "schema": {
+ "type": "integer",
+ "default": 10
+ }
+ },
+ {
+ "name": "sortOrder",
+ "in": "query",
+ "description": "Sort order for results",
+ "schema": {
+ "type": "string",
+ "enum": ["asc", "desc"],
+ "default": "desc"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Incidents retrieved successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "example": true
+ },
+ "msg": {
+ "type": "string",
+ "example": "Incidents retrieved successfully"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Incident"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "500": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ]
+ }
+ },
+ "/incidents/team/summary": {
+ "get": {
+ "tags": ["incidents"],
+ "summary": "Get incident summary",
+ "description": "Retrieve a summary of incidents for the authenticated user's team",
+ "parameters": [
+ {
+ "name": "dateRange",
+ "in": "query",
+ "description": "Date range for summary",
+ "schema": {
+ "type": "string",
+ "enum": ["day", "week", "month"],
+ "default": "week"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Incident summary retrieved successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "example": true
+ },
+ "msg": {
+ "type": "string",
+ "example": "Incident summary retrieved successfully"
+ },
+ "data": {
+ "$ref": "#/components/schemas/IncidentSummary"
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "500": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ]
+ }
+ },
+ "/incidents/{incidentId}": {
+ "get": {
+ "tags": ["incidents"],
+ "summary": "Get incident by ID",
+ "description": "Retrieve a specific incident by its ID",
+ "parameters": [
+ {
+ "name": "incidentId",
+ "in": "path",
+ "required": true,
+ "description": "The incident ID",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Incident retrieved successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "example": true
+ },
+ "msg": {
+ "type": "string",
+ "example": "Incident retrieved successfully"
+ },
+ "data": {
+ "$ref": "#/components/schemas/Incident"
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "404": {
+ "$ref": "#/components/responses/NotFound"
+ },
+ "500": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ]
+ }
+ },
+ "/incidents/{incidentId}/resolve": {
+ "put": {
+ "tags": ["incidents"],
+ "summary": "Resolve incident manually",
+ "description": "Manually resolve an ongoing incident. Requires admin or superadmin role.",
+ "parameters": [
+ {
+ "name": "incidentId",
+ "in": "path",
+ "required": true,
+ "description": "The incident ID to resolve",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "comment": {
+ "type": "string",
+ "description": "Optional comment about the resolution",
+ "example": "Issue resolved by restarting the service"
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Incident resolved successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "example": true
+ },
+ "msg": {
+ "type": "string",
+ "example": "Incident resolved successfully"
+ },
+ "data": {
+ "$ref": "#/components/schemas/Incident"
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "403": {
+ "$ref": "#/components/responses/Forbidden"
+ },
+ "404": {
+ "$ref": "#/components/responses/NotFound"
+ },
+ "500": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ]
+ }
}
},
"components": {
@@ -2680,6 +3384,16 @@
}
}
},
+ "BadRequest": {
+ "description": "Invalid request parameters",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ }
+ },
"InternalServerError": {
"description": "Internal server error",
"content": {
@@ -3375,6 +4089,557 @@
}
}
}
+ },
+ "Incident": {
+ "type": "object",
+ "properties": {
+ "_id": {
+ "type": "string",
+ "description": "Unique incident identifier"
+ },
+ "monitorId": {
+ "type": "string",
+ "description": "ID of the associated monitor"
+ },
+ "teamId": {
+ "type": "string",
+ "description": "ID of the team"
+ },
+ "startTime": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When the incident started"
+ },
+ "endTime": {
+ "type": ["string", "null"],
+ "format": "date-time",
+ "description": "When the incident was resolved (null if ongoing)"
+ },
+ "status": {
+ "type": "boolean",
+ "description": "Incident status (true = ongoing, false = resolved)"
+ },
+ "message": {
+ "type": ["string", "null"],
+ "description": "Error message or description"
+ },
+ "statusCode": {
+ "type": ["integer", "null"],
+ "description": "HTTP status code if applicable"
+ },
+ "resolutionType": {
+ "type": ["string", "null"],
+ "enum": ["automatic", "manual", null],
+ "description": "How the incident was resolved"
+ },
+ "resolvedBy": {
+ "type": ["string", "null"],
+ "description": "User ID who resolved the incident (for manual resolution)"
+ },
+ "comment": {
+ "type": ["string", "null"],
+ "description": "Resolution comment"
+ },
+ "checks": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Array of check IDs associated with this incident"
+ },
+ "createdAt": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "updatedAt": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ },
+ "IncidentSummary": {
+ "type": "object",
+ "properties": {
+ "totalIncidents": {
+ "type": "integer",
+ "description": "Total number of incidents"
+ },
+ "activeIncidents": {
+ "type": "integer",
+ "description": "Number of ongoing incidents"
+ },
+ "resolvedIncidents": {
+ "type": "integer",
+ "description": "Number of resolved incidents"
+ },
+ "averageResolutionTime": {
+ "type": "number",
+ "description": "Average time to resolve incidents in milliseconds"
+ },
+ "incidentsByMonitor": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "monitorId": {
+ "type": "string"
+ },
+ "monitorName": {
+ "type": "string"
+ },
+ "count": {
+ "type": "integer"
+ }
+ }
+ }
+ }
+ }
+ },
+ "QueueJob": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "description": "Job ID"
+ },
+ "name": {
+ "type": "string",
+ "description": "Job name"
+ },
+ "data": {
+ "type": "object",
+ "description": "Job payload data"
+ },
+ "status": {
+ "type": "string",
+ "enum": ["waiting", "active", "completed", "failed", "delayed"],
+ "description": "Current job status"
+ },
+ "progress": {
+ "type": "number",
+ "description": "Job progress percentage"
+ },
+ "attemptsMade": {
+ "type": "integer",
+ "description": "Number of attempts made"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When the job was created"
+ },
+ "processedOn": {
+ "type": ["string", "null"],
+ "format": "date-time",
+ "description": "When the job started processing"
+ },
+ "finishedOn": {
+ "type": ["string", "null"],
+ "format": "date-time",
+ "description": "When the job finished"
+ }
+ }
+ },
+ "QueueMetrics": {
+ "type": "object",
+ "properties": {
+ "waiting": {
+ "type": "integer",
+ "description": "Number of jobs waiting"
+ },
+ "active": {
+ "type": "integer",
+ "description": "Number of jobs currently processing"
+ },
+ "completed": {
+ "type": "integer",
+ "description": "Number of completed jobs"
+ },
+ "failed": {
+ "type": "integer",
+ "description": "Number of failed jobs"
+ },
+ "delayed": {
+ "type": "integer",
+ "description": "Number of delayed jobs"
+ }
+ }
+ },
+ "Check": {
+ "type": "object",
+ "description": "A monitoring check result",
+ "properties": {
+ "_id": {
+ "type": "string",
+ "description": "Check ID"
+ },
+ "metadata": {
+ "type": "object",
+ "properties": {
+ "monitorId": {
+ "type": "string",
+ "description": "Associated monitor ID"
+ },
+ "teamId": {
+ "type": "string",
+ "description": "Associated team ID"
+ },
+ "type": {
+ "type": "string",
+ "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port"],
+ "description": "Monitor type"
+ }
+ },
+ "required": ["monitorId", "teamId", "type"]
+ },
+ "status": {
+ "type": "boolean",
+ "description": "Check status (true = up, false = down)"
+ },
+ "responseTime": {
+ "type": "number",
+ "description": "Response time in milliseconds"
+ },
+ "statusCode": {
+ "type": "integer",
+ "description": "HTTP status code (for HTTP monitors)"
+ },
+ "message": {
+ "type": "string",
+ "description": "Status message or error description"
+ },
+ "timings": {
+ "$ref": "#/components/schemas/CheckTimings"
+ },
+ "cpu": {
+ "$ref": "#/components/schemas/CheckCpuInfo"
+ },
+ "memory": {
+ "$ref": "#/components/schemas/CheckMemoryInfo"
+ },
+ "disk": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/CheckDiskInfo"
+ }
+ },
+ "host": {
+ "$ref": "#/components/schemas/CheckHostInfo"
+ },
+ "errors": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/CheckErrorInfo"
+ }
+ },
+ "capture": {
+ "$ref": "#/components/schemas/CheckCaptureInfo"
+ },
+ "net": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/CheckNetworkInfo"
+ }
+ },
+ "accessibility": {
+ "type": "number",
+ "description": "PageSpeed accessibility score (0-100)"
+ },
+ "bestPractices": {
+ "type": "number",
+ "description": "PageSpeed best practices score (0-100)"
+ },
+ "seo": {
+ "type": "number",
+ "description": "PageSpeed SEO score (0-100)"
+ },
+ "performance": {
+ "type": "number",
+ "description": "PageSpeed performance score (0-100)"
+ },
+ "audits": {
+ "$ref": "#/components/schemas/CheckAudits"
+ },
+ "ack": {
+ "type": "boolean",
+ "description": "Whether the check has been acknowledged"
+ },
+ "ackAt": {
+ "type": ["string", "null"],
+ "format": "date-time",
+ "description": "When the check was acknowledged"
+ },
+ "expiry": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When the check expires"
+ },
+ "createdAt": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "updatedAt": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ },
+ "CheckTimings": {
+ "type": "object",
+ "description": "Detailed timing information for HTTP checks",
+ "properties": {
+ "start": {
+ "type": "number"
+ },
+ "socket": {
+ "type": "number"
+ },
+ "lookup": {
+ "type": "number"
+ },
+ "connect": {
+ "type": "number"
+ },
+ "secureConnect": {
+ "type": "number"
+ },
+ "upload": {
+ "type": "number"
+ },
+ "response": {
+ "type": "number"
+ },
+ "end": {
+ "type": "number"
+ },
+ "phases": {
+ "type": "object",
+ "properties": {
+ "wait": {
+ "type": "number"
+ },
+ "dns": {
+ "type": "number"
+ },
+ "tcp": {
+ "type": "number"
+ },
+ "tls": {
+ "type": "number"
+ },
+ "request": {
+ "type": "number"
+ },
+ "firstByte": {
+ "type": "number"
+ },
+ "download": {
+ "type": "number"
+ },
+ "total": {
+ "type": "number"
+ }
+ }
+ }
+ }
+ },
+ "CheckCpuInfo": {
+ "type": "object",
+ "description": "CPU information for infrastructure checks",
+ "properties": {
+ "physical_core": {
+ "type": "integer"
+ },
+ "logical_core": {
+ "type": "integer"
+ },
+ "frequency": {
+ "type": "number"
+ },
+ "temperature": {
+ "type": "array",
+ "items": {
+ "type": "number"
+ }
+ },
+ "free_percent": {
+ "type": "number"
+ },
+ "usage_percent": {
+ "type": "number"
+ }
+ }
+ },
+ "CheckMemoryInfo": {
+ "type": "object",
+ "description": "Memory information for infrastructure checks",
+ "properties": {
+ "total_bytes": {
+ "type": "number"
+ },
+ "available_bytes": {
+ "type": "number"
+ },
+ "used_bytes": {
+ "type": "number"
+ },
+ "usage_percent": {
+ "type": "number"
+ }
+ }
+ },
+ "CheckDiskInfo": {
+ "type": "object",
+ "description": "Disk information for infrastructure checks",
+ "properties": {
+ "device": {
+ "type": "string"
+ },
+ "mountpoint": {
+ "type": "string"
+ },
+ "read_speed_bytes": {
+ "type": "number"
+ },
+ "write_speed_bytes": {
+ "type": "number"
+ },
+ "total_bytes": {
+ "type": "number"
+ },
+ "free_bytes": {
+ "type": "number"
+ },
+ "usage_percent": {
+ "type": "number"
+ }
+ }
+ },
+ "CheckHostInfo": {
+ "type": "object",
+ "description": "Host information for infrastructure checks",
+ "properties": {
+ "os": {
+ "type": "string"
+ },
+ "platform": {
+ "type": "string"
+ },
+ "kernel_version": {
+ "type": "string"
+ }
+ }
+ },
+ "CheckErrorInfo": {
+ "type": "object",
+ "description": "Error information from checks",
+ "properties": {
+ "metric": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "err": {
+ "type": "string"
+ }
+ }
+ },
+ "CheckCaptureInfo": {
+ "type": "object",
+ "description": "Capture agent information",
+ "properties": {
+ "version": {
+ "type": "string"
+ },
+ "mode": {
+ "type": "string"
+ }
+ }
+ },
+ "CheckNetworkInfo": {
+ "type": "object",
+ "description": "Network interface information",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "bytes_sent": {
+ "type": "number"
+ },
+ "bytes_recv": {
+ "type": "number"
+ },
+ "packets_sent": {
+ "type": "number"
+ },
+ "packets_recv": {
+ "type": "number"
+ },
+ "err_in": {
+ "type": "number"
+ },
+ "err_out": {
+ "type": "number"
+ },
+ "drop_in": {
+ "type": "number"
+ },
+ "drop_out": {
+ "type": "number"
+ },
+ "fifo_in": {
+ "type": "number"
+ },
+ "fifo_out": {
+ "type": "number"
+ }
+ }
+ },
+ "CheckAudits": {
+ "type": "object",
+ "description": "PageSpeed audit results",
+ "properties": {
+ "cls": {
+ "$ref": "#/components/schemas/LighthouseAudit"
+ },
+ "si": {
+ "$ref": "#/components/schemas/LighthouseAudit"
+ },
+ "fcp": {
+ "$ref": "#/components/schemas/LighthouseAudit"
+ },
+ "lcp": {
+ "$ref": "#/components/schemas/LighthouseAudit"
+ },
+ "tbt": {
+ "$ref": "#/components/schemas/LighthouseAudit"
+ }
+ }
+ },
+ "LighthouseAudit": {
+ "type": "object",
+ "description": "Individual Lighthouse audit result",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "score": {
+ "type": "number"
+ },
+ "displayValue": {
+ "type": "string"
+ },
+ "numericValue": {
+ "type": "number"
+ },
+ "numericUnit": {
+ "type": "string"
+ }
+ }
}
}
}
diff --git a/server/package-lock.json b/server/package-lock.json
index 8de5cfc0c..3f5b824b0 100644
--- a/server/package-lock.json
+++ b/server/package-lock.json
@@ -13,6 +13,7 @@
"axios": "^1.7.2",
"bcryptjs": "3.0.2",
"bullmq": "5.41.2",
+ "cacheable-lookup": "7.0.0",
"compression": "1.8.1",
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
@@ -35,29 +36,42 @@
"mongoose": "^8.3.3",
"multer": "^1.4.5-lts.1",
"nodemailer": "^6.9.14",
- "papaparse": "^5.5.2",
"ping": "0.4.4",
"sharp": "0.33.5",
"ssl-checker": "2.0.10",
- "super-simple-scheduler": "1.4.4",
+ "super-simple-scheduler": "1.4.5",
"swagger-ui-express": "5.0.1",
"winston": "^3.13.0"
},
"devDependencies": {
"@eslint/js": "^9.17.0",
+ "@types/compression": "1.8.1",
+ "@types/cookie-parser": "1.4.10",
+ "@types/cors": "2.8.19",
+ "@types/dockerode": "^4.0.0",
"@types/express": "5.0.3",
+ "@types/gamedig": "^5.0.3",
+ "@types/jest": "^30.0.0",
+ "@types/jmespath": "^0.15.2",
+ "@types/jsdom": "^27.0.0",
"@types/jsonwebtoken": "9.0.10",
+ "@types/mjml": "^4.7.4",
+ "@types/multer": "^2.0.0",
"@types/nodemailer": "7.0.1",
+ "@types/papaparse": "^5.5.2",
"@types/ping": "0.4.4",
+ "@types/swagger-ui-express": "4.1.8",
"c8": "10.1.3",
- "chai": "5.2.0",
"eslint": "^9.17.0",
"eslint-plugin-mocha": "^10.5.0",
"esm": "3.2.25",
"globals": "^15.14.0",
- "mocha": "11.1.0",
+ "jest": "^30.2.0",
+ "nodemon": "^3.1.11",
"prettier": "^3.3.3",
- "sinon": "19.0.2",
+ "ts-jest": "^29.4.6",
+ "ts-node": "^10.9.2",
+ "tsc-alias": "1.8.16",
"tsx": "4.20.5",
"typescript": "5.9.2"
},
@@ -830,12 +844,12 @@
}
},
"node_modules/@babel/code-frame": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
- "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz",
+ "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==",
"license": "MIT",
"dependencies": {
- "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
},
@@ -843,6 +857,174 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@babel/compat-data": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz",
+ "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz",
+ "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/generator": "^7.28.6",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helpers": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/core/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz",
+ "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.28.6",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
+ "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/helper-validator-identifier": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
@@ -852,6 +1034,285 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
+ "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz",
+ "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.6"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-async-generators": {
+ "version": "7.8.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
+ "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-bigint": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz",
+ "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-class-properties": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
+ "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.12.13"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-class-static-block": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz",
+ "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-attributes": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz",
+ "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-meta": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
+ "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-json-strings": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
+ "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-jsx": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz",
+ "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-logical-assignment-operators": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz",
+ "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz",
+ "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-numeric-separator": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz",
+ "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-object-rest-spread": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz",
+ "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-catch-binding": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz",
+ "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-chaining": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
+ "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-private-property-in-object": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz",
+ "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-top-level-await": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz",
+ "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-typescript": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz",
+ "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
"node_modules/@babel/runtime": {
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
@@ -861,6 +1322,54 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@babel/template": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz",
+ "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/generator": "^7.28.6",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.6",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz",
+ "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@balena/dockerignore": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz",
@@ -886,6 +1395,30 @@
"node": ">=0.1.90"
}
},
+ "node_modules/@cspotcode/source-map-support": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+ "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "0.3.9"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+ "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
"node_modules/@csstools/color-helpers": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
@@ -970,6 +1503,7 @@
}
],
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=18"
},
@@ -1014,6 +1548,7 @@
}
],
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=18"
}
@@ -1029,6 +1564,18 @@
"kuler": "^2.0.0"
}
},
+ "node_modules/@emnapi/core": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz",
+ "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.1.0",
+ "tslib": "^2.4.0"
+ }
+ },
"node_modules/@emnapi/runtime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.6.0.tgz",
@@ -1039,6 +1586,17 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz",
+ "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.11",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz",
@@ -2138,6 +2696,113 @@
"node": ">=12"
}
},
+ "node_modules/@istanbuljs/load-nyc-config": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
+ "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "camelcase": "^5.3.1",
+ "find-up": "^4.1.0",
+ "get-package-type": "^0.1.0",
+ "js-yaml": "^3.13.1",
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
+ "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/@istanbuljs/schema": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
@@ -2148,6 +2813,377 @@
"node": ">=8"
}
},
+ "node_modules/@jest/console": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz",
+ "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "jest-message-util": "30.2.0",
+ "jest-util": "30.2.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/core": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.2.0.tgz",
+ "integrity": "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/console": "30.2.0",
+ "@jest/pattern": "30.0.1",
+ "@jest/reporters": "30.2.0",
+ "@jest/test-result": "30.2.0",
+ "@jest/transform": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "ansi-escapes": "^4.3.2",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "exit-x": "^0.2.2",
+ "graceful-fs": "^4.2.11",
+ "jest-changed-files": "30.2.0",
+ "jest-config": "30.2.0",
+ "jest-haste-map": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-regex-util": "30.0.1",
+ "jest-resolve": "30.2.0",
+ "jest-resolve-dependencies": "30.2.0",
+ "jest-runner": "30.2.0",
+ "jest-runtime": "30.2.0",
+ "jest-snapshot": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-validate": "30.2.0",
+ "jest-watcher": "30.2.0",
+ "micromatch": "^4.0.8",
+ "pretty-format": "30.2.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jest/diff-sequences": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz",
+ "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/environment": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz",
+ "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/fake-timers": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "jest-mock": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/expect": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz",
+ "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "expect": "30.2.0",
+ "jest-snapshot": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/expect-utils": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz",
+ "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/fake-timers": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz",
+ "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "@sinonjs/fake-timers": "^13.0.0",
+ "@types/node": "*",
+ "jest-message-util": "30.2.0",
+ "jest-mock": "30.2.0",
+ "jest-util": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/get-type": {
+ "version": "30.1.0",
+ "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz",
+ "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/globals": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz",
+ "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "30.2.0",
+ "@jest/expect": "30.2.0",
+ "@jest/types": "30.2.0",
+ "jest-mock": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/pattern": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz",
+ "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "jest-regex-util": "30.0.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/reporters": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz",
+ "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@bcoe/v8-coverage": "^0.2.3",
+ "@jest/console": "30.2.0",
+ "@jest/test-result": "30.2.0",
+ "@jest/transform": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "collect-v8-coverage": "^1.0.2",
+ "exit-x": "^0.2.2",
+ "glob": "^10.3.10",
+ "graceful-fs": "^4.2.11",
+ "istanbul-lib-coverage": "^3.0.0",
+ "istanbul-lib-instrument": "^6.0.0",
+ "istanbul-lib-report": "^3.0.0",
+ "istanbul-lib-source-maps": "^5.0.0",
+ "istanbul-reports": "^3.1.3",
+ "jest-message-util": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-worker": "30.2.0",
+ "slash": "^3.0.0",
+ "string-length": "^4.0.2",
+ "v8-to-istanbul": "^9.0.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jest/reporters/node_modules/@bcoe/v8-coverage": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
+ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jest/schemas": {
+ "version": "30.0.5",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz",
+ "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sinclair/typebox": "^0.34.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/snapshot-utils": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz",
+ "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "natural-compare": "^1.4.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/source-map": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz",
+ "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "callsites": "^3.1.0",
+ "graceful-fs": "^4.2.11"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/test-result": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz",
+ "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/console": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/istanbul-lib-coverage": "^2.0.6",
+ "collect-v8-coverage": "^1.0.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/test-sequencer": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz",
+ "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/test-result": "30.2.0",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.2.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/transform": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz",
+ "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.27.4",
+ "@jest/types": "30.2.0",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "babel-plugin-istanbul": "^7.0.1",
+ "chalk": "^4.1.2",
+ "convert-source-map": "^2.0.0",
+ "fast-json-stable-stringify": "^2.1.0",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.2.0",
+ "jest-regex-util": "30.0.1",
+ "jest-util": "30.2.0",
+ "micromatch": "^4.0.8",
+ "pirates": "^4.0.7",
+ "slash": "^3.0.0",
+ "write-file-atomic": "^5.0.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/types": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz",
+ "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/pattern": "30.0.1",
+ "@jest/schemas": "30.0.5",
+ "@types/istanbul-lib-coverage": "^2.0.6",
+ "@types/istanbul-reports": "^3.0.4",
+ "@types/node": "*",
+ "@types/yargs": "^17.0.33",
+ "chalk": "^4.1.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
@@ -2273,6 +3309,57 @@
"win32"
]
},
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "0.2.12",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
+ "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.4.3",
+ "@emnapi/runtime": "^1.4.3",
+ "@tybys/wasm-util": "^0.10.0"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -2283,6 +3370,19 @@
"node": ">=14"
}
},
+ "node_modules/@pkgr/core": {
+ "version": "0.2.9",
+ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
+ "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/pkgr"
+ }
+ },
"node_modules/@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
@@ -2395,6 +3495,13 @@
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==",
"license": "BSD-3-Clause"
},
+ "node_modules/@sinclair/typebox": {
+ "version": "0.34.47",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.47.tgz",
+ "integrity": "sha512-ZGIBQ+XDvO5JQku9wmwtabcVTHJsgSWAHYtVuM9pBNNR5E88v6Jcj/llpmsjivig5X8A8HHOb4/mbEKPS5EvAw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@sindresorhus/is": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.1.0.tgz",
@@ -2427,34 +3534,6 @@
"@sinonjs/commons": "^3.0.1"
}
},
- "node_modules/@sinonjs/samsam": {
- "version": "8.0.3",
- "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.3.tgz",
- "integrity": "sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@sinonjs/commons": "^3.0.1",
- "type-detect": "^4.1.0"
- }
- },
- "node_modules/@sinonjs/samsam/node_modules/type-detect": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz",
- "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/@sinonjs/text-encoding": {
- "version": "0.7.3",
- "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz",
- "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==",
- "dev": true,
- "license": "(Unlicense OR Apache-2.0)"
- },
"node_modules/@smithy/abort-controller": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.3.tgz",
@@ -3143,6 +4222,100 @@
"node": ">=14.16"
}
},
+ "node_modules/@trysound/sax": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
+ "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==",
+ "license": "ISC",
+ "optional": true,
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/@tsconfig/node10": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz",
+ "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tsconfig/node12": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+ "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tsconfig/node14": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+ "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tsconfig/node16": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
+ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
+ "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
"node_modules/@types/body-parser": {
"version": "1.19.6",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
@@ -3154,6 +4327,17 @@
"@types/node": "*"
}
},
+ "node_modules/@types/compression": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.8.1.tgz",
+ "integrity": "sha512-kCFuWS0ebDbmxs0AXYn6e2r2nrGAb5KwQhknjSPSPgJcGd8+HVSILlUyFhGqML2gk39HcG7D1ydW9/qpYkN00Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/express": "*",
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/connect": {
"version": "3.4.38",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
@@ -3164,6 +4348,49 @@
"@types/node": "*"
}
},
+ "node_modules/@types/cookie-parser": {
+ "version": "1.4.10",
+ "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.10.tgz",
+ "integrity": "sha512-B4xqkqfZ8Wek+rCOeRxsjMS9OgvzebEzzLYw7NHYuvzb7IdxOkI0ZHGgeEBX4PUM7QGVvNSK60T3OvWj3YfBRg==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/express": "*"
+ }
+ },
+ "node_modules/@types/cors": {
+ "version": "2.8.19",
+ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
+ "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/docker-modem": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.6.tgz",
+ "integrity": "sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/ssh2": "*"
+ }
+ },
+ "node_modules/@types/dockerode": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-4.0.0.tgz",
+ "integrity": "sha512-NBm/UIL1KYWivh9ihJELK4Jv0dzOQCleS0OT5JklErra8mZ/t0UQpHfTkMmPrAqtra7OyFius1EFLVs0DhiHQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/docker-modem": "*",
+ "@types/node": "*",
+ "@types/ssh2": "*"
+ }
+ },
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -3177,6 +4404,7 @@
"integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "^5.0.0",
@@ -3196,6 +4424,13 @@
"@types/send": "*"
}
},
+ "node_modules/@types/gamedig": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/@types/gamedig/-/gamedig-5.0.3.tgz",
+ "integrity": "sha512-VLrOVRf72wVE6P0DhFtRVodiONwL51uojn6dNpP/Ks/Vdm3CDH6dZTAw74WmCDLuyvpytNy8auKzlTKfBlO8jg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/http-cache-semantics": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz",
@@ -3216,6 +4451,56 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/istanbul-lib-report": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
+ "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/istanbul-lib-coverage": "*"
+ }
+ },
+ "node_modules/@types/istanbul-reports": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
+ "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/istanbul-lib-report": "*"
+ }
+ },
+ "node_modules/@types/jest": {
+ "version": "30.0.0",
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz",
+ "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "expect": "^30.0.0",
+ "pretty-format": "^30.0.0"
+ }
+ },
+ "node_modules/@types/jmespath": {
+ "version": "0.15.2",
+ "resolved": "https://registry.npmjs.org/@types/jmespath/-/jmespath-0.15.2.tgz",
+ "integrity": "sha512-pegh49FtNsC389Flyo9y8AfkVIZn9MMPE9yJrO9svhq6Fks2MwymULWjZqySuxmctd3ZH4/n7Mr98D+1Qo5vGA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/jsdom": {
+ "version": "27.0.0",
+ "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-27.0.0.tgz",
+ "integrity": "sha512-NZyFl/PViwKzdEkQg96gtnB8wm+1ljhdDay9ahn4hgb+SfVtPCbm3TlmDUFXTA+MGN3CijicnMhG18SI5H3rFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/tough-cookie": "*",
+ "parse5": "^7.0.0"
+ }
+ },
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -3241,6 +4526,23 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/mjml": {
+ "version": "4.7.4",
+ "resolved": "https://registry.npmjs.org/@types/mjml/-/mjml-4.7.4.tgz",
+ "integrity": "sha512-vyi1vzWgMzFMwZY7GSZYX0GU0dmtC8vLHwpgk+NWmwbwRSrlieVyJ9sn5elodwUfklJM7yGl0zQeet1brKTWaQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/mjml-core": "*"
+ }
+ },
+ "node_modules/@types/mjml-core": {
+ "version": "4.15.2",
+ "resolved": "https://registry.npmjs.org/@types/mjml-core/-/mjml-core-4.15.2.tgz",
+ "integrity": "sha512-Q7SxFXgoX979HP57DEVsRI50TV8x1V4lfCA4Up9AvfINDM5oD/X9ARgfoyX1qS987JCnDLv85JjkqAjt3hZSiQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/ms": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
@@ -3248,11 +4550,22 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/multer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.0.0.tgz",
+ "integrity": "sha512-C3Z9v9Evij2yST3RSBktxP9STm6OdMc5uR1xF1SGr98uv8dUlAL2hqwrZ3GVB3uyMyiegnscEK6PGtYvNrjTjw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/express": "*"
+ }
+ },
"node_modules/@types/node": {
"version": "24.9.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz",
"integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"undici-types": "~7.16.0"
}
@@ -3268,6 +4581,16 @@
"@types/node": "*"
}
},
+ "node_modules/@types/papaparse": {
+ "version": "5.5.2",
+ "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.5.2.tgz",
+ "integrity": "sha512-gFnFp/JMzLHCwRf7tQHrNnfhN4eYBVYYI897CGX4MY1tzY9l2aLkVyx2IlKZ/SAqDbB3I1AOZW5gTMGGsqWliA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/ping": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/@types/ping/-/ping-0.4.4.tgz",
@@ -3328,6 +4651,58 @@
"@types/node": "*"
}
},
+ "node_modules/@types/ssh2": {
+ "version": "1.15.5",
+ "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.5.tgz",
+ "integrity": "sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "^18.11.18"
+ }
+ },
+ "node_modules/@types/ssh2/node_modules/@types/node": {
+ "version": "18.19.130",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz",
+ "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~5.26.4"
+ }
+ },
+ "node_modules/@types/ssh2/node_modules/undici-types": {
+ "version": "5.26.5",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/stack-utils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
+ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/swagger-ui-express": {
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.8.tgz",
+ "integrity": "sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/express": "*",
+ "@types/serve-static": "*"
+ }
+ },
+ "node_modules/@types/tough-cookie": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
+ "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/triple-beam": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz",
@@ -3356,6 +4731,299 @@
"@types/webidl-conversions": "*"
}
},
+ "node_modules/@types/yargs": {
+ "version": "17.0.35",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz",
+ "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/yargs-parser": "*"
+ }
+ },
+ "node_modules/@types/yargs-parser": {
+ "version": "21.0.3",
+ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
+ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/@unrs/resolver-binding-android-arm-eabi": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz",
+ "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-android-arm64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz",
+ "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-darwin-arm64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz",
+ "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-darwin-x64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz",
+ "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-freebsd-x64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz",
+ "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz",
+ "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz",
+ "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz",
+ "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm64-musl": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz",
+ "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz",
+ "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz",
+ "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-riscv64-musl": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz",
+ "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-s390x-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz",
+ "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-x64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz",
+ "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-x64-musl": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz",
+ "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-wasm32-wasi": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz",
+ "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@napi-rs/wasm-runtime": "^0.2.11"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@unrs/resolver-binding-win32-arm64-msvc": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz",
+ "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-win32-ia32-msvc": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz",
+ "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-win32-x64-msvc": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz",
+ "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@@ -3384,6 +5052,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -3401,6 +5070,19 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
+ "node_modules/acorn-walk": {
+ "version": "8.3.4",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
+ "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.11.0"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
@@ -3436,6 +5118,35 @@
"node": ">=6"
}
},
+ "node_modules/ansi-escapes": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
+ "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^0.21.3"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ansi-escapes/node_modules/type-fest": {
+ "version": "0.21.3",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
+ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/ansi-regex": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
@@ -3488,6 +5199,13 @@
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
"license": "MIT"
},
+ "node_modules/arg": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -3500,6 +5218,16 @@
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT"
},
+ "node_modules/array-union": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/asn1": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
@@ -3509,16 +5237,6 @@
"safer-buffer": "~2.1.0"
}
},
- "node_modules/assertion-error": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
- "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/async": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
@@ -3542,6 +5260,142 @@
"proxy-from-env": "^1.1.0"
}
},
+ "node_modules/babel-jest": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz",
+ "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/transform": "30.2.0",
+ "@types/babel__core": "^7.20.5",
+ "babel-plugin-istanbul": "^7.0.1",
+ "babel-preset-jest": "30.2.0",
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.11.0 || ^8.0.0-0"
+ }
+ },
+ "node_modules/babel-plugin-istanbul": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz",
+ "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "workspaces": [
+ "test/babel-8"
+ ],
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@istanbuljs/load-nyc-config": "^1.0.0",
+ "@istanbuljs/schema": "^0.1.3",
+ "istanbul-lib-instrument": "^6.0.2",
+ "test-exclude": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/babel-plugin-istanbul/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/babel-plugin-istanbul/node_modules/test-exclude": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
+ "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@istanbuljs/schema": "^0.1.2",
+ "glob": "^7.1.4",
+ "minimatch": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/babel-plugin-jest-hoist": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz",
+ "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/babel__core": "^7.20.5"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/babel-preset-current-node-syntax": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz",
+ "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/plugin-syntax-async-generators": "^7.8.4",
+ "@babel/plugin-syntax-bigint": "^7.8.3",
+ "@babel/plugin-syntax-class-properties": "^7.12.13",
+ "@babel/plugin-syntax-class-static-block": "^7.14.5",
+ "@babel/plugin-syntax-import-attributes": "^7.24.7",
+ "@babel/plugin-syntax-import-meta": "^7.10.4",
+ "@babel/plugin-syntax-json-strings": "^7.8.3",
+ "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
+ "@babel/plugin-syntax-numeric-separator": "^7.10.4",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.3",
+ "@babel/plugin-syntax-private-property-in-object": "^7.14.5",
+ "@babel/plugin-syntax-top-level-await": "^7.14.5"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0 || ^8.0.0-0"
+ }
+ },
+ "node_modules/babel-preset-jest": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz",
+ "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "babel-plugin-jest-hoist": "30.2.0",
+ "babel-preset-current-node-syntax": "^1.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.11.0 || ^8.0.0-beta.1"
+ }
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -3655,23 +5509,23 @@
}
},
"node_modules/body-parser": {
- "version": "1.20.3",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
- "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
+ "version": "1.20.4",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
+ "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
"license": "MIT",
"dependencies": {
- "bytes": "3.1.2",
+ "bytes": "~3.1.2",
"content-type": "~1.0.5",
"debug": "2.6.9",
"depd": "2.0.0",
- "destroy": "1.2.0",
- "http-errors": "2.0.0",
- "iconv-lite": "0.4.24",
- "on-finished": "2.4.1",
- "qs": "6.13.0",
- "raw-body": "2.5.2",
+ "destroy": "~1.2.0",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "on-finished": "~2.4.1",
+ "qs": "~6.14.0",
+ "raw-body": "~2.5.3",
"type-is": "~1.6.18",
- "unpipe": "1.0.0"
+ "unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8",
@@ -3687,12 +5541,41 @@
"ms": "2.0.0"
}
},
+ "node_modules/body-parser/node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/body-parser/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
+ "node_modules/body-parser/node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@@ -3729,13 +5612,6 @@
"node": ">=8"
}
},
- "node_modules/browser-stdout": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
- "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/browserslist": {
"version": "4.27.0",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz",
@@ -3755,6 +5631,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.8.19",
"caniuse-lite": "^1.0.30001751",
@@ -3769,6 +5646,29 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
+ "node_modules/bs-logger": {
+ "version": "0.2.6",
+ "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz",
+ "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-json-stable-stringify": "2.x"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/bser": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
+ "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "node-int64": "^0.4.0"
+ }
+ },
"node_modules/bson": {
"version": "6.10.4",
"resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz",
@@ -3958,16 +5858,13 @@
}
},
"node_modules/camelcase": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
- "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "node": ">=6"
}
},
"node_modules/caniuse-api": {
@@ -4002,23 +5899,6 @@
],
"license": "CC-BY-4.0"
},
- "node_modules/chai": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz",
- "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "assertion-error": "^2.0.1",
- "check-error": "^2.1.1",
- "deep-eql": "^5.0.1",
- "loupe": "^3.1.0",
- "pathval": "^2.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -4036,14 +5916,14 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
- "node_modules/check-error": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
- "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
+ "node_modules/char-regex": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
+ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">= 16"
+ "node": ">=10"
}
},
"node_modules/cheerio": {
@@ -4126,6 +6006,29 @@
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"license": "ISC"
},
+ "node_modules/ci-info": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz",
+ "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cjs-module-lexer": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz",
+ "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
@@ -4207,6 +6110,24 @@
"node": ">=0.10.0"
}
},
+ "node_modules/co": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+ "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">= 1.0.0",
+ "node": ">= 0.12.0"
+ }
+ },
+ "node_modules/collect-v8-coverage": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz",
+ "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
@@ -4493,6 +6414,13 @@
"node": ">=10.0.0"
}
},
+ "node_modules/create-require": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/cron-parser": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz",
@@ -4589,6 +6517,7 @@
"resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.1.tgz",
"integrity": "sha512-fm4D8ti0dQmFPeF8DXSAA//btEmqCOgAc/9Oa3C1LW94h5usNrJEfrON7b4FkPZgnDEn6OUs5NdxiJZmAtGOpQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"cssnano-preset-default": "^7.0.9",
"lilconfig": "^3.1.3"
@@ -4769,19 +6698,6 @@
}
}
},
- "node_modules/decamelize": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
- "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/decimal.js": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
@@ -4815,14 +6731,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/deep-eql": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
- "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
+ "node_modules/dedent": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz",
+ "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=6"
+ "peerDependencies": {
+ "babel-plugin-macros": "^3.1.0"
+ },
+ "peerDependenciesMeta": {
+ "babel-plugin-macros": {
+ "optional": true
+ }
}
},
"node_modules/deep-is": {
@@ -4832,6 +6753,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/defer-to-connect": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
@@ -4887,6 +6818,16 @@
"node": ">=8"
}
},
+ "node_modules/detect-newline": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
+ "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/detect-node": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
@@ -4894,15 +6835,28 @@
"license": "MIT"
},
"node_modules/diff": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
- "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.3.1"
}
},
+ "node_modules/dir-glob": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/docker-modem": {
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.6.tgz",
@@ -5074,6 +7028,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/emittery": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz",
+ "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/emittery?sponsor=1"
+ }
+ },
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
@@ -5292,6 +7259,7 @@
"integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -5480,6 +7448,20 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/esquery": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
@@ -5542,40 +7524,126 @@
"deprecated": "Use promise-toolbox/fromEvent instead",
"license": "MIT"
},
- "node_modules/express": {
- "version": "4.21.2",
- "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
- "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
+ "node_modules/execa": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "dev": true,
"license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.0",
+ "human-signals": "^2.1.0",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.1",
+ "onetime": "^5.1.2",
+ "signal-exit": "^3.0.3",
+ "strip-final-newline": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/execa/node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/execa/node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/execa/node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/exit-x": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz",
+ "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/expect": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz",
+ "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/expect-utils": "30.2.0",
+ "@jest/get-type": "30.1.0",
+ "jest-matcher-utils": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-mock": "30.2.0",
+ "jest-util": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/express": {
+ "version": "4.22.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
+ "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
+ "license": "MIT",
+ "peer": true,
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
- "body-parser": "1.20.3",
- "content-disposition": "0.5.4",
+ "body-parser": "~1.20.3",
+ "content-disposition": "~0.5.4",
"content-type": "~1.0.4",
- "cookie": "0.7.1",
- "cookie-signature": "1.0.6",
+ "cookie": "~0.7.1",
+ "cookie-signature": "~1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
- "finalhandler": "1.3.1",
- "fresh": "0.5.2",
- "http-errors": "2.0.0",
+ "finalhandler": "~1.3.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.0",
"merge-descriptors": "1.0.3",
"methods": "~1.1.2",
- "on-finished": "2.4.1",
+ "on-finished": "~2.4.1",
"parseurl": "~1.3.3",
- "path-to-regexp": "0.1.12",
+ "path-to-regexp": "~0.1.12",
"proxy-addr": "~2.0.7",
- "qs": "6.13.0",
+ "qs": "~6.14.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
- "send": "0.19.0",
- "serve-static": "1.16.2",
+ "send": "~0.19.0",
+ "serve-static": "~1.16.2",
"setprototypeof": "1.2.0",
- "statuses": "2.0.1",
+ "statuses": "~2.0.1",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
@@ -5606,15 +7674,6 @@
"express": ">= 4.11"
}
},
- "node_modules/express/node_modules/cookie": {
- "version": "0.7.1",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
- "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/express/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -5643,6 +7702,36 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@@ -5675,6 +7764,26 @@
"fxparser": "src/cli/cli.js"
}
},
+ "node_modules/fastq": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
+ "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/fb-watchman": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
+ "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bser": "2.1.1"
+ }
+ },
"node_modules/fecha": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
@@ -5779,16 +7888,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/flat": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
- "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
- "dev": true,
- "license": "BSD-3-Clause",
- "bin": {
- "flat": "cli.js"
- }
- },
"node_modules/flat-cache": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
@@ -5913,6 +8012,13 @@
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"license": "MIT"
},
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -6103,6 +8209,16 @@
"node": ">=0.10"
}
},
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@@ -6136,6 +8252,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/get-package-type": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
+ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
@@ -6179,9 +8305,9 @@
}
},
"node_modules/glob": {
- "version": "10.4.5",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
- "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
@@ -6248,6 +8374,27 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/globby": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+ "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.2.9",
+ "ignore": "^5.2.0",
+ "merge2": "^1.4.1",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -6285,6 +8432,13 @@
"url": "https://github.com/sindresorhus/got?sponsor=1"
}
},
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/handlebars": {
"version": "4.7.8",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
@@ -6355,16 +8509,6 @@
"node": ">= 0.4"
}
},
- "node_modules/he": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
- "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "he": "bin/he"
- }
- },
"node_modules/helmet": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz",
@@ -6529,6 +8673,16 @@
"numbered": "^1.1.0"
}
},
+ "node_modules/human-signals": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=10.17.0"
+ }
+ },
"node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -6571,6 +8725,13 @@
"node": ">= 4"
}
},
+ "node_modules/ignore-by-default": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
+ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/import-fresh": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
@@ -6587,6 +8748,26 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/import-local": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
+ "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pkg-dir": "^4.2.0",
+ "resolve-cwd": "^3.0.0"
+ },
+ "bin": {
+ "import-local-fixture": "fixtures/cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@@ -6597,6 +8778,18 @@
"node": ">=0.8.19"
}
},
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
@@ -6681,6 +8874,16 @@
"node": ">=8"
}
},
+ "node_modules/is-generator-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz",
+ "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@@ -6708,16 +8911,6 @@
"node": ">=0.12.0"
}
},
- "node_modules/is-plain-obj": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
- "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/is-potential-custom-element-name": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
@@ -6736,19 +8929,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/is-unicode-supported": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
- "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
@@ -6988,6 +9168,23 @@
"node": ">=8"
}
},
+ "node_modules/istanbul-lib-instrument": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz",
+ "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@babel/core": "^7.23.9",
+ "@babel/parser": "^7.23.9",
+ "@istanbuljs/schema": "^0.1.3",
+ "istanbul-lib-coverage": "^3.2.0",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/istanbul-lib-report": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
@@ -7003,6 +9200,21 @@
"node": ">=10"
}
},
+ "node_modules/istanbul-lib-source-maps": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz",
+ "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.23",
+ "debug": "^4.1.1",
+ "istanbul-lib-coverage": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/istanbul-reports": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
@@ -7032,6 +9244,600 @@
"@pkgjs/parseargs": "^0.11.0"
}
},
+ "node_modules/jest": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz",
+ "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jest/core": "30.2.0",
+ "@jest/types": "30.2.0",
+ "import-local": "^3.2.0",
+ "jest-cli": "30.2.0"
+ },
+ "bin": {
+ "jest": "bin/jest.js"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-changed-files": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz",
+ "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "execa": "^5.1.1",
+ "jest-util": "30.2.0",
+ "p-limit": "^3.1.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-circus": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz",
+ "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "30.2.0",
+ "@jest/expect": "30.2.0",
+ "@jest/test-result": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "co": "^4.6.0",
+ "dedent": "^1.6.0",
+ "is-generator-fn": "^2.1.0",
+ "jest-each": "30.2.0",
+ "jest-matcher-utils": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-runtime": "30.2.0",
+ "jest-snapshot": "30.2.0",
+ "jest-util": "30.2.0",
+ "p-limit": "^3.1.0",
+ "pretty-format": "30.2.0",
+ "pure-rand": "^7.0.0",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.6"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-cli": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz",
+ "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/core": "30.2.0",
+ "@jest/test-result": "30.2.0",
+ "@jest/types": "30.2.0",
+ "chalk": "^4.1.2",
+ "exit-x": "^0.2.2",
+ "import-local": "^3.2.0",
+ "jest-config": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-validate": "30.2.0",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "jest": "bin/jest.js"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-config": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz",
+ "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.27.4",
+ "@jest/get-type": "30.1.0",
+ "@jest/pattern": "30.0.1",
+ "@jest/test-sequencer": "30.2.0",
+ "@jest/types": "30.2.0",
+ "babel-jest": "30.2.0",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "deepmerge": "^4.3.1",
+ "glob": "^10.3.10",
+ "graceful-fs": "^4.2.11",
+ "jest-circus": "30.2.0",
+ "jest-docblock": "30.2.0",
+ "jest-environment-node": "30.2.0",
+ "jest-regex-util": "30.0.1",
+ "jest-resolve": "30.2.0",
+ "jest-runner": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-validate": "30.2.0",
+ "micromatch": "^4.0.8",
+ "parse-json": "^5.2.0",
+ "pretty-format": "30.2.0",
+ "slash": "^3.0.0",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "@types/node": "*",
+ "esbuild-register": ">=3.4.0",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "esbuild-register": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-diff": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz",
+ "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/diff-sequences": "30.0.1",
+ "@jest/get-type": "30.1.0",
+ "chalk": "^4.1.2",
+ "pretty-format": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-docblock": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz",
+ "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "detect-newline": "^3.1.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-each": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz",
+ "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0",
+ "@jest/types": "30.2.0",
+ "chalk": "^4.1.2",
+ "jest-util": "30.2.0",
+ "pretty-format": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-environment-node": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz",
+ "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "30.2.0",
+ "@jest/fake-timers": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "jest-mock": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-validate": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-haste-map": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz",
+ "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "anymatch": "^3.1.3",
+ "fb-watchman": "^2.0.2",
+ "graceful-fs": "^4.2.11",
+ "jest-regex-util": "30.0.1",
+ "jest-util": "30.2.0",
+ "jest-worker": "30.2.0",
+ "micromatch": "^4.0.8",
+ "walker": "^1.0.8"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "^2.3.3"
+ }
+ },
+ "node_modules/jest-leak-detector": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz",
+ "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0",
+ "pretty-format": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-matcher-utils": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz",
+ "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0",
+ "chalk": "^4.1.2",
+ "jest-diff": "30.2.0",
+ "pretty-format": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-message-util": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz",
+ "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@jest/types": "30.2.0",
+ "@types/stack-utils": "^2.0.3",
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "micromatch": "^4.0.8",
+ "pretty-format": "30.2.0",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.6"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-mock": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz",
+ "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "jest-util": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-pnp-resolver": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz",
+ "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "peerDependencies": {
+ "jest-resolve": "*"
+ },
+ "peerDependenciesMeta": {
+ "jest-resolve": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-regex-util": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz",
+ "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-resolve": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz",
+ "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.2.0",
+ "jest-pnp-resolver": "^1.2.3",
+ "jest-util": "30.2.0",
+ "jest-validate": "30.2.0",
+ "slash": "^3.0.0",
+ "unrs-resolver": "^1.7.11"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-resolve-dependencies": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz",
+ "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "jest-regex-util": "30.0.1",
+ "jest-snapshot": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-runner": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz",
+ "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/console": "30.2.0",
+ "@jest/environment": "30.2.0",
+ "@jest/test-result": "30.2.0",
+ "@jest/transform": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "emittery": "^0.13.1",
+ "exit-x": "^0.2.2",
+ "graceful-fs": "^4.2.11",
+ "jest-docblock": "30.2.0",
+ "jest-environment-node": "30.2.0",
+ "jest-haste-map": "30.2.0",
+ "jest-leak-detector": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-resolve": "30.2.0",
+ "jest-runtime": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-watcher": "30.2.0",
+ "jest-worker": "30.2.0",
+ "p-limit": "^3.1.0",
+ "source-map-support": "0.5.13"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-runtime": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz",
+ "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "30.2.0",
+ "@jest/fake-timers": "30.2.0",
+ "@jest/globals": "30.2.0",
+ "@jest/source-map": "30.0.1",
+ "@jest/test-result": "30.2.0",
+ "@jest/transform": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "cjs-module-lexer": "^2.1.0",
+ "collect-v8-coverage": "^1.0.2",
+ "glob": "^10.3.10",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-mock": "30.2.0",
+ "jest-regex-util": "30.0.1",
+ "jest-resolve": "30.2.0",
+ "jest-snapshot": "30.2.0",
+ "jest-util": "30.2.0",
+ "slash": "^3.0.0",
+ "strip-bom": "^4.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-snapshot": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz",
+ "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.27.4",
+ "@babel/generator": "^7.27.5",
+ "@babel/plugin-syntax-jsx": "^7.27.1",
+ "@babel/plugin-syntax-typescript": "^7.27.1",
+ "@babel/types": "^7.27.3",
+ "@jest/expect-utils": "30.2.0",
+ "@jest/get-type": "30.1.0",
+ "@jest/snapshot-utils": "30.2.0",
+ "@jest/transform": "30.2.0",
+ "@jest/types": "30.2.0",
+ "babel-preset-current-node-syntax": "^1.2.0",
+ "chalk": "^4.1.2",
+ "expect": "30.2.0",
+ "graceful-fs": "^4.2.11",
+ "jest-diff": "30.2.0",
+ "jest-matcher-utils": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-util": "30.2.0",
+ "pretty-format": "30.2.0",
+ "semver": "^7.7.2",
+ "synckit": "^0.11.8"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-util": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz",
+ "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "graceful-fs": "^4.2.11",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-util/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/jest-validate": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz",
+ "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0",
+ "@jest/types": "30.2.0",
+ "camelcase": "^6.3.0",
+ "chalk": "^4.1.2",
+ "leven": "^3.1.0",
+ "pretty-format": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-validate/node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/jest-watcher": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz",
+ "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/test-result": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "ansi-escapes": "^4.3.2",
+ "chalk": "^4.1.2",
+ "emittery": "^0.13.1",
+ "jest-util": "30.2.0",
+ "string-length": "^4.0.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-worker": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz",
+ "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@ungap/structured-clone": "^1.3.0",
+ "jest-util": "30.2.0",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^8.1.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-worker/node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
"node_modules/jmespath": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz",
@@ -7061,9 +9867,9 @@
"license": "MIT"
},
"node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
@@ -7111,6 +9917,19 @@
}
}
},
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
@@ -7137,6 +9956,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
@@ -7247,13 +10079,6 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
- "node_modules/just-extend": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz",
- "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/jwa": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz",
@@ -7266,12 +10091,12 @@
}
},
"node_modules/jws": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
- "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz",
+ "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==",
"license": "MIT",
"dependencies": {
- "jwa": "^1.4.1",
+ "jwa": "^1.4.2",
"safe-buffer": "^5.0.1"
}
},
@@ -7299,6 +10124,16 @@
"integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==",
"license": "MIT"
},
+ "node_modules/leven": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
+ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -7432,23 +10267,6 @@
"integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==",
"license": "MIT"
},
- "node_modules/log-symbols": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
- "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "chalk": "^4.1.0",
- "is-unicode-supported": "^0.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/logform": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz",
@@ -7472,13 +10290,6 @@
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"license": "Apache-2.0"
},
- "node_modules/loupe": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
- "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/lowercase-keys": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz",
@@ -7533,6 +10344,23 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/make-error": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/makeerror": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
+ "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "tmpl": "1.0.5"
+ }
+ },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -7578,6 +10406,23 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
@@ -7587,6 +10432,20 @@
"node": ">= 0.6"
}
},
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
@@ -7629,6 +10488,16 @@
"node": ">= 0.6"
}
},
+ "node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/mimic-response": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz",
@@ -8119,81 +10988,6 @@
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
"license": "MIT"
},
- "node_modules/mocha": {
- "version": "11.1.0",
- "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.1.0.tgz",
- "integrity": "sha512-8uJR5RTC2NgpY3GrYcgpZrsEd9zKbPDpob1RezyR2upGHRQtHWofmzTMzTMSV6dru3tj5Ukt0+Vnq1qhFEEwAg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-colors": "^4.1.3",
- "browser-stdout": "^1.3.1",
- "chokidar": "^3.5.3",
- "debug": "^4.3.5",
- "diff": "^5.2.0",
- "escape-string-regexp": "^4.0.0",
- "find-up": "^5.0.0",
- "glob": "^10.4.5",
- "he": "^1.2.0",
- "js-yaml": "^4.1.0",
- "log-symbols": "^4.1.0",
- "minimatch": "^5.1.6",
- "ms": "^2.1.3",
- "serialize-javascript": "^6.0.2",
- "strip-json-comments": "^3.1.1",
- "supports-color": "^8.1.1",
- "workerpool": "^6.5.1",
- "yargs": "^17.7.2",
- "yargs-parser": "^21.1.1",
- "yargs-unparser": "^2.0.0"
- },
- "bin": {
- "_mocha": "bin/_mocha",
- "mocha": "bin/mocha.js"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/mocha/node_modules/brace-expansion": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/mocha/node_modules/minimatch": {
- "version": "5.1.6",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
- "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/mocha/node_modules/supports-color": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
- "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/supports-color?sponsor=1"
- }
- },
"node_modules/moment": {
"version": "2.30.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
@@ -8370,6 +11164,20 @@
"node": ">= 6.0.0"
}
},
+ "node_modules/mylas": {
+ "version": "2.1.14",
+ "resolved": "https://registry.npmjs.org/mylas/-/mylas-2.1.14.tgz",
+ "integrity": "sha512-BzQguy9W9NJgoVn2mRWzbFrFWWztGCcng2QI9+41frfk+Athwgx3qhqhvStz7ExeUUu7Kzw427sNzHpEZNINog==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/raouldeheer"
+ }
+ },
"node_modules/nan": {
"version": "2.23.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.23.0.tgz",
@@ -8395,6 +11203,22 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
+ "node_modules/napi-postinstall": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz",
+ "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "napi-postinstall": "lib/cli.js"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/napi-postinstall"
+ }
+ },
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -8423,31 +11247,6 @@
"integrity": "sha512-kbhcj2SVVR4caaVnGLJKmlk2+f+oLkjqdKeQlmUtz6nGzOpbcobwVIeSURNgraV/v3tlmGIX82OcPCl0K6RbHQ==",
"license": "MIT"
},
- "node_modules/nise": {
- "version": "6.1.1",
- "resolved": "https://registry.npmjs.org/nise/-/nise-6.1.1.tgz",
- "integrity": "sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@sinonjs/commons": "^3.0.1",
- "@sinonjs/fake-timers": "^13.0.1",
- "@sinonjs/text-encoding": "^0.7.3",
- "just-extend": "^6.2.0",
- "path-to-regexp": "^8.1.0"
- }
- },
- "node_modules/nise/node_modules/path-to-regexp": {
- "version": "8.3.0",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
- "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
"node_modules/node-abort-controller": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz",
@@ -8531,6 +11330,13 @@
"node-gyp-build-optional-packages-test": "build-test.js"
}
},
+ "node_modules/node-int64": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
+ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/node-releases": {
"version": "2.0.26",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.26.tgz",
@@ -8546,6 +11352,58 @@
"node": ">=6.0.0"
}
},
+ "node_modules/nodemon": {
+ "version": "3.1.11",
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz",
+ "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^3.5.2",
+ "debug": "^4",
+ "ignore-by-default": "^1.0.1",
+ "minimatch": "^3.1.2",
+ "pstree.remy": "^1.1.8",
+ "semver": "^7.5.3",
+ "simple-update-notifier": "^2.0.0",
+ "supports-color": "^5.5.0",
+ "touch": "^3.1.0",
+ "undefsafe": "^2.0.5"
+ },
+ "bin": {
+ "nodemon": "bin/nodemon.js"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/nodemon"
+ }
+ },
+ "node_modules/nodemon/node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/nodemon/node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -8567,6 +11425,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/nth-check": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
@@ -8651,6 +11522,22 @@
"fn.name": "1.x.x"
}
},
+ "node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -8710,18 +11597,22 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"license": "BlueOak-1.0.0"
},
- "node_modules/papaparse": {
- "version": "5.5.3",
- "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.3.tgz",
- "integrity": "sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==",
- "license": "MIT"
- },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -8820,6 +11711,16 @@
"node": ">=8"
}
},
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@@ -8851,14 +11752,14 @@
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
"license": "MIT"
},
- "node_modules/pathval": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz",
- "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==",
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">= 14.16"
+ "node": ">=8"
}
},
"node_modules/picocolors": {
@@ -8888,6 +11789,98 @@
"node": ">=4.0.0"
}
},
+ "node_modules/pirates": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/pkg-dir": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "find-up": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/plimit-lit": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.6.1.tgz",
+ "integrity": "sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "queue-lit": "^1.5.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/postcss": {
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
@@ -8907,6 +11900,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -9325,6 +12319,40 @@
"postcss": "^8.4.32"
}
},
+ "node_modules/postcss-svgo/node_modules/commander": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
+ "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/postcss-svgo/node_modules/svgo": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz",
+ "integrity": "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==",
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^11.1.0",
+ "css-select": "^5.1.0",
+ "css-tree": "^3.0.1",
+ "css-what": "^6.1.0",
+ "csso": "^5.0.5",
+ "picocolors": "^1.1.1",
+ "sax": "^1.4.1"
+ },
+ "bin": {
+ "svgo": "bin/svgo.js"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/svgo"
+ }
+ },
"node_modules/postcss-unique-selectors": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.4.tgz",
@@ -9491,6 +12519,34 @@
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
+ "node_modules/pretty-format": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz",
+ "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/schemas": "30.0.5",
+ "ansi-styles": "^5.2.0",
+ "react-is": "^18.3.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@@ -9540,6 +12596,13 @@
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
+ "node_modules/pstree.remy": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
+ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/pump": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
@@ -9559,13 +12622,30 @@
"node": ">=6"
}
},
+ "node_modules/pure-rand": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz",
+ "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/qs": {
- "version": "6.13.0",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
- "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
+ "version": "6.14.1",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
+ "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
"license": "BSD-3-Clause",
"dependencies": {
- "side-channel": "^1.0.6"
+ "side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
@@ -9574,6 +12654,37 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/queue-lit": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.2.tgz",
+ "integrity": "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/quick-lru": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
@@ -9593,16 +12704,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/randombytes": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
- "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "^5.1.0"
- }
- },
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -9613,20 +12714,56 @@
}
},
"node_modules/raw-body": {
- "version": "2.5.2",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
- "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
+ "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
"license": "MIT",
"dependencies": {
- "bytes": "3.1.2",
- "http-errors": "2.0.0",
- "iconv-lite": "0.4.24",
- "unpipe": "1.0.0"
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
+ "node_modules/raw-body/node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/raw-body/node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
@@ -9698,6 +12835,29 @@
"integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==",
"license": "MIT"
},
+ "node_modules/resolve-cwd": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
+ "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/resolve-cwd/node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -9732,12 +12892,47 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/rrweb-cssom": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
"integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==",
"license": "MIT"
},
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -9774,10 +12969,13 @@
"license": "MIT"
},
"node_modules/sax": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
- "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
- "license": "ISC"
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz",
+ "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=11.0.0"
+ }
},
"node_modules/saxes": {
"version": "6.0.0",
@@ -9873,16 +13071,6 @@
"node": ">= 0.8"
}
},
- "node_modules/serialize-javascript": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
- "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "randombytes": "^2.1.0"
- }
- },
"node_modules/serve-static": {
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
@@ -10069,33 +13257,27 @@
"integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
"license": "MIT"
},
- "node_modules/sinon": {
- "version": "19.0.2",
- "resolved": "https://registry.npmjs.org/sinon/-/sinon-19.0.2.tgz",
- "integrity": "sha512-euuToqM+PjO4UgXeLETsfQiuoyPXlqFezr6YZDFwHR3t4qaX0fZUe1MfPMznTL5f8BWrVS89KduLdMUsxFCO6g==",
+ "node_modules/simple-update-notifier": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
+ "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
"dev": true,
- "license": "BSD-3-Clause",
+ "license": "MIT",
"dependencies": {
- "@sinonjs/commons": "^3.0.1",
- "@sinonjs/fake-timers": "^13.0.2",
- "@sinonjs/samsam": "^8.0.1",
- "diff": "^7.0.0",
- "nise": "^6.1.1",
- "supports-color": "^7.2.0"
+ "semver": "^7.5.3"
},
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/sinon"
+ "engines": {
+ "node": ">=10"
}
},
- "node_modules/sinon/node_modules/diff": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz",
- "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==",
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"dev": true,
- "license": "BSD-3-Clause",
+ "license": "MIT",
"engines": {
- "node": ">=0.3.1"
+ "node": ">=8"
}
},
"node_modules/slick": {
@@ -10125,6 +13307,17 @@
"node": ">=0.10.0"
}
},
+ "node_modules/source-map-support": {
+ "version": "0.5.13",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
+ "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
"node_modules/sparse-bitfield": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
@@ -10140,6 +13333,13 @@
"integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==",
"license": "ISC"
},
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
"node_modules/ssh2": {
"version": "1.17.0",
"resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.17.0.tgz",
@@ -10172,6 +13372,29 @@
"node": "*"
}
},
+ "node_modules/stack-utils": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
+ "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/stack-utils/node_modules/escape-string-regexp": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
+ "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/standard-as-callback": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
@@ -10213,6 +13436,43 @@
"safe-buffer": "~5.2.0"
}
},
+ "node_modules/string-length": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
+ "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "char-regex": "^1.0.2",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/string-length/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-length/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/string-to-stream": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string-to-stream/-/string-to-stream-1.1.1.tgz",
@@ -10355,6 +13615,26 @@
"node": ">=8"
}
},
+ "node_modules/strip-bom": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
+ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@@ -10397,9 +13677,9 @@
}
},
"node_modules/super-simple-scheduler": {
- "version": "1.4.4",
- "resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.4.4.tgz",
- "integrity": "sha512-o99zHFrrFECwj1FFE+ns4su5hKSYif+SUNB3fUJbaRWGkwLLoj19TRuRJYp72TYt9eDiCii68UBuKiLF4tlkMg==",
+ "version": "1.4.5",
+ "resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.4.5.tgz",
+ "integrity": "sha512-xHR3a+pLywY7HtwjWj/+OaANdEwAieQhckC/i0s6OoHtVJQ4+VLO1VfkKUbxp+oSROGXcaZet8PTGONVdKNUUw==",
"license": "MIT",
"dependencies": {
"uuid": "11.1.0",
@@ -10467,24 +13747,25 @@
}
},
"node_modules/svgo": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz",
- "integrity": "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==",
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz",
+ "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==",
"license": "MIT",
+ "optional": true,
"dependencies": {
- "commander": "^11.1.0",
+ "@trysound/sax": "0.2.0",
+ "commander": "^7.2.0",
"css-select": "^5.1.0",
- "css-tree": "^3.0.1",
+ "css-tree": "^2.3.1",
"css-what": "^6.1.0",
"csso": "^5.0.5",
- "picocolors": "^1.1.1",
- "sax": "^1.4.1"
+ "picocolors": "^1.0.0"
},
"bin": {
- "svgo": "bin/svgo.js"
+ "svgo": "bin/svgo"
},
"engines": {
- "node": ">=16"
+ "node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
@@ -10492,14 +13773,36 @@
}
},
"node_modules/svgo/node_modules/commander": {
- "version": "11.1.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
- "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
+ "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
"license": "MIT",
+ "optional": true,
"engines": {
- "node": ">=16"
+ "node": ">= 10"
}
},
+ "node_modules/svgo/node_modules/css-tree": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
+ "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "mdn-data": "2.0.30",
+ "source-map-js": "^1.0.1"
+ },
+ "engines": {
+ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
+ }
+ },
+ "node_modules/svgo/node_modules/mdn-data": {
+ "version": "2.0.30",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
+ "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
+ "license": "CC0-1.0",
+ "optional": true
+ },
"node_modules/swagger-ui-dist": {
"version": "5.30.0",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.30.0.tgz",
@@ -10530,6 +13833,22 @@
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
"license": "MIT"
},
+ "node_modules/synckit": {
+ "version": "0.11.12",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz",
+ "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@pkgr/core": "^0.2.9"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/synckit"
+ }
+ },
"node_modules/tar-fs": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
@@ -10637,6 +13956,13 @@
"integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",
"license": "MIT"
},
+ "node_modules/tmpl": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
+ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -10658,6 +13984,16 @@
"node": ">=0.6"
}
},
+ "node_modules/touch": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
+ "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "nodetouch": "bin/nodetouch.js"
+ }
+ },
"node_modules/tough-cookie": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",
@@ -10691,6 +14027,136 @@
"node": ">= 14.0.0"
}
},
+ "node_modules/ts-jest": {
+ "version": "29.4.6",
+ "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz",
+ "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bs-logger": "^0.2.6",
+ "fast-json-stable-stringify": "^2.1.0",
+ "handlebars": "^4.7.8",
+ "json5": "^2.2.3",
+ "lodash.memoize": "^4.1.2",
+ "make-error": "^1.3.6",
+ "semver": "^7.7.3",
+ "type-fest": "^4.41.0",
+ "yargs-parser": "^21.1.1"
+ },
+ "bin": {
+ "ts-jest": "cli.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": ">=7.0.0-beta.0 <8",
+ "@jest/transform": "^29.0.0 || ^30.0.0",
+ "@jest/types": "^29.0.0 || ^30.0.0",
+ "babel-jest": "^29.0.0 || ^30.0.0",
+ "jest": "^29.0.0 || ^30.0.0",
+ "jest-util": "^29.0.0 || ^30.0.0",
+ "typescript": ">=4.3 <6"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "@jest/transform": {
+ "optional": true
+ },
+ "@jest/types": {
+ "optional": true
+ },
+ "babel-jest": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jest-util": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ts-node": {
+ "version": "10.9.2",
+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
+ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@cspotcode/source-map-support": "^0.8.0",
+ "@tsconfig/node10": "^1.0.7",
+ "@tsconfig/node12": "^1.0.7",
+ "@tsconfig/node14": "^1.0.0",
+ "@tsconfig/node16": "^1.0.2",
+ "acorn": "^8.4.1",
+ "acorn-walk": "^8.1.1",
+ "arg": "^4.1.0",
+ "create-require": "^1.1.0",
+ "diff": "^4.0.1",
+ "make-error": "^1.1.1",
+ "v8-compile-cache-lib": "^3.0.1",
+ "yn": "3.1.1"
+ },
+ "bin": {
+ "ts-node": "dist/bin.js",
+ "ts-node-cwd": "dist/bin-cwd.js",
+ "ts-node-esm": "dist/bin-esm.js",
+ "ts-node-script": "dist/bin-script.js",
+ "ts-node-transpile-only": "dist/bin-transpile.js",
+ "ts-script": "dist/bin-script-deprecated.js"
+ },
+ "peerDependencies": {
+ "@swc/core": ">=1.2.50",
+ "@swc/wasm": ">=1.2.50",
+ "@types/node": "*",
+ "typescript": ">=2.7"
+ },
+ "peerDependenciesMeta": {
+ "@swc/core": {
+ "optional": true
+ },
+ "@swc/wasm": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tsc-alias": {
+ "version": "1.8.16",
+ "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.16.tgz",
+ "integrity": "sha512-QjCyu55NFyRSBAl6+MTFwplpFcnm2Pq01rR/uxfqJoLMm6X3O14KEGtaSDZpJYaE1bJBGDjD0eSuiIWPe2T58g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^3.5.3",
+ "commander": "^9.0.0",
+ "get-tsconfig": "^4.10.0",
+ "globby": "^11.0.4",
+ "mylas": "^2.1.9",
+ "normalize-path": "^3.0.0",
+ "plimit-lit": "^1.2.6"
+ },
+ "bin": {
+ "tsc-alias": "dist/bin/index.js"
+ },
+ "engines": {
+ "node": ">=16.20.2"
+ }
+ },
+ "node_modules/tsc-alias/node_modules/commander": {
+ "version": "9.5.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
+ "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || >=14"
+ }
+ },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@@ -10783,6 +14249,7 @@
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"devOptional": true,
"license": "Apache-2.0",
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -10804,6 +14271,13 @@
"node": ">=0.8.0"
}
},
+ "node_modules/undefsafe": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
+ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/undici": {
"version": "6.22.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.22.0.tgz",
@@ -10837,6 +14311,41 @@
"node": ">= 0.8"
}
},
+ "node_modules/unrs-resolver": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz",
+ "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "napi-postinstall": "^0.3.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/unrs-resolver"
+ },
+ "optionalDependencies": {
+ "@unrs/resolver-binding-android-arm-eabi": "1.11.1",
+ "@unrs/resolver-binding-android-arm64": "1.11.1",
+ "@unrs/resolver-binding-darwin-arm64": "1.11.1",
+ "@unrs/resolver-binding-darwin-x64": "1.11.1",
+ "@unrs/resolver-binding-freebsd-x64": "1.11.1",
+ "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1",
+ "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1",
+ "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-arm64-musl": "1.11.1",
+ "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1",
+ "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-x64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-x64-musl": "1.11.1",
+ "@unrs/resolver-binding-wasm32-wasi": "1.11.1",
+ "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1",
+ "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1",
+ "@unrs/resolver-binding-win32-x64-msvc": "1.11.1"
+ }
+ },
"node_modules/update-browserslist-db": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz",
@@ -10905,6 +14414,13 @@
"uuid": "dist/bin/uuid"
}
},
+ "node_modules/v8-compile-cache-lib": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/v8-to-istanbul": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
@@ -10956,6 +14472,16 @@
"node": ">=18"
}
},
+ "node_modules/walker": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
+ "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "makeerror": "1.0.12"
+ }
+ },
"node_modules/web-resource-inliner": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/web-resource-inliner/-/web-resource-inliner-7.0.0.tgz",
@@ -11224,13 +14750,6 @@
"integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
"license": "MIT"
},
- "node_modules/workerpool": {
- "version": "6.5.1",
- "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz",
- "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==",
- "dev": true,
- "license": "Apache-2.0"
- },
"node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
@@ -11325,6 +14844,20 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
},
+ "node_modules/write-file-atomic": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz",
+ "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "imurmurhash": "^0.1.4",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
"node_modules/ws": {
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
@@ -11408,6 +14941,13 @@
"node": ">=10"
}
},
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
@@ -11435,22 +14975,6 @@
"node": ">=12"
}
},
- "node_modules/yargs-unparser": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz",
- "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "camelcase": "^6.0.0",
- "decamelize": "^4.0.0",
- "flat": "^5.0.2",
- "is-plain-obj": "^2.1.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/yargs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -11492,6 +15016,16 @@
"node": ">=8"
}
},
+ "node_modules/yn": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+ "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
diff --git a/server/package.json b/server/package.json
index 380755b5b..2421aeb1c 100755
--- a/server/package.json
+++ b/server/package.json
@@ -5,10 +5,10 @@
"main": "index.js",
"type": "module",
"scripts": {
- "test": "c8 mocha",
- "dev": "tsx watch src/index.js",
+ "test": "NODE_OPTIONS=--experimental-vm-modules c8 jest --runInBand",
+ "dev": "nodemon --exec tsx src/index.js",
"start": "node --watch ./dist/index.js",
- "build": "tsc",
+ "build": "tsc && tsc-alias",
"lint": "eslint .",
"lint-fix": "eslint --fix .",
"format": "prettier --write .",
@@ -25,6 +25,7 @@
"axios": "^1.7.2",
"bcryptjs": "3.0.2",
"bullmq": "5.41.2",
+ "cacheable-lookup": "7.0.0",
"compression": "1.8.1",
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
@@ -47,29 +48,42 @@
"mongoose": "^8.3.3",
"multer": "^1.4.5-lts.1",
"nodemailer": "^6.9.14",
- "papaparse": "^5.5.2",
"ping": "0.4.4",
"sharp": "0.33.5",
"ssl-checker": "2.0.10",
- "super-simple-scheduler": "1.4.4",
+ "super-simple-scheduler": "1.4.5",
"swagger-ui-express": "5.0.1",
"winston": "^3.13.0"
},
"devDependencies": {
"@eslint/js": "^9.17.0",
+ "@types/compression": "1.8.1",
+ "@types/cookie-parser": "1.4.10",
+ "@types/cors": "2.8.19",
+ "@types/dockerode": "^4.0.0",
"@types/express": "5.0.3",
+ "@types/gamedig": "^5.0.3",
+ "@types/jest": "^30.0.0",
+ "@types/jmespath": "^0.15.2",
+ "@types/jsdom": "^27.0.0",
"@types/jsonwebtoken": "9.0.10",
+ "@types/mjml": "^4.7.4",
+ "@types/multer": "^2.0.0",
"@types/nodemailer": "7.0.1",
+ "@types/papaparse": "^5.5.2",
"@types/ping": "0.4.4",
+ "@types/swagger-ui-express": "4.1.8",
"c8": "10.1.3",
- "chai": "5.2.0",
"eslint": "^9.17.0",
"eslint-plugin-mocha": "^10.5.0",
"esm": "3.2.25",
"globals": "^15.14.0",
- "mocha": "11.1.0",
+ "jest": "^30.2.0",
+ "nodemon": "^3.1.11",
"prettier": "^3.3.3",
- "sinon": "19.0.2",
+ "ts-jest": "^29.4.6",
+ "ts-node": "^10.9.2",
+ "tsc-alias": "1.8.16",
"tsx": "4.20.5",
"typescript": "5.9.2"
}
diff --git a/server/scripts/generate-checks.js b/server/scripts/generate-checks.js
new file mode 100644
index 000000000..544770bb6
--- /dev/null
+++ b/server/scripts/generate-checks.js
@@ -0,0 +1,176 @@
+import mongoose from "mongoose";
+import { MonitorModel } from "../dist/db/models/Monitor.js";
+import { CheckModel } from "../dist/db/models/Check.js";
+
+const DEFAULT_MONITOR_ID = "000000000000000000000001";
+const DEFAULT_TEAM_ID = "0000000000000000000000aa";
+const DEFAULT_USER_ID = "0000000000000000000000bb";
+const DEFAULT_MONITOR_TYPE = "http";
+const DEFAULT_TOTAL = 1_000_000;
+const DEFAULT_BATCH_SIZE = 5_000;
+
+const parseObjectId = (value, fallback) => {
+ try {
+ return new mongoose.Types.ObjectId(value || fallback);
+ } catch (error) {
+ console.warn(`Invalid ObjectId '${value}', falling back to '${fallback}'.`);
+ return new mongoose.Types.ObjectId(fallback);
+ }
+};
+
+async function ensureMonitor({ monitorId, teamId, userId, type }) {
+ const existing = await MonitorModel.findById(monitorId);
+ if (existing) {
+ return existing;
+ }
+
+ console.log(`Monitor ${monitorId.toString()} not found, creating it.`);
+ const monitor = new MonitorModel({
+ _id: monitorId,
+ userId,
+ teamId,
+ name: `Seed Monitor ${monitorId.toString()}`,
+ description: "Synthetic monitor for performance testing",
+ statusWindow: [],
+ statusWindowSize: 5,
+ statusWindowThreshold: 60,
+ type,
+ ignoreTlsErrors: false,
+ url: "https://example.com",
+ isActive: true,
+ interval: 60000,
+ alertThreshold: 5,
+ cpuAlertThreshold: 5,
+ memoryAlertThreshold: 5,
+ diskAlertThreshold: 5,
+ tempAlertThreshold: 5,
+ selectedDisks: [],
+ });
+
+ await monitor.save();
+ return monitor;
+}
+
+async function run() {
+ const mongoUri = process.env.MONGO_URI ?? "mongodb://localhost:27017/uptime_db";
+ const monitorId = parseObjectId(process.env.MONITOR_ID ?? DEFAULT_MONITOR_ID, DEFAULT_MONITOR_ID);
+ const teamId = parseObjectId("6971546de1b2bc3de6498e6e");
+ const userId = parseObjectId("6971546de1b2bc3de6498e70");
+ const monitorType = process.env.MONITOR_TYPE ?? DEFAULT_MONITOR_TYPE;
+ const total = Number(process.env.CHECK_TOTAL ?? DEFAULT_TOTAL);
+ const batchSize = Number(process.env.CHECK_BATCH_SIZE ?? DEFAULT_BATCH_SIZE);
+
+ console.log(`Connecting to MongoDB at ${mongoUri}`);
+ await mongoose.connect(mongoUri);
+
+ await ensureMonitor({ monitorId, teamId, userId, type: monitorType });
+
+ console.log(`Seeding ${total} checks for monitor ${monitorId.toString()} (team ${teamId.toString()}) in batches of ${batchSize}.`);
+
+ const docs = [];
+ const startTime = Date.now();
+
+ for (let i = 0; i < total; i += 1) {
+ const baseTime = Date.now() - (total - i) * 1000;
+ const createdAt = new Date(baseTime);
+ docs.push({
+ metadata: {
+ monitorId,
+ teamId,
+ type: monitorType,
+ },
+ status: i % 50 !== 0,
+ statusCode: i % 50 !== 0 ? 200 : 500,
+ responseTime: Math.floor(Math.random() * 1000),
+ message: i % 50 !== 0 ? "OK" : "Error",
+ expiry: createdAt,
+ createdAt,
+ updatedAt: createdAt,
+ timings: {
+ start: baseTime,
+ socket: baseTime,
+ lookup: baseTime,
+ connect: baseTime,
+ secureConnect: baseTime,
+ upload: baseTime,
+ response: baseTime + 40,
+ end: baseTime + 45,
+ phases: {
+ wait: 0,
+ dns: 1,
+ tcp: 2,
+ tls: 4,
+ request: 0,
+ firstByte: 30,
+ download: 5,
+ total: 45,
+ },
+ },
+ cpu: {
+ physical_core: 8,
+ logical_core: 16,
+ frequency: 3600,
+ temperature: [50 + Math.random() * 10],
+ free_percent: 40,
+ usage_percent: Math.random() * 100,
+ },
+ memory: {
+ total_bytes: 32 * 1024 ** 3,
+ available_bytes: 16 * 1024 ** 3,
+ used_bytes: 16 * 1024 ** 3,
+ usage_percent: Math.random() * 100,
+ },
+ disk: [
+ {
+ device: "/dev/sda1",
+ mountpoint: "/",
+ read_speed_bytes: Math.random() * 10_000_000,
+ write_speed_bytes: Math.random() * 10_000_000,
+ total_bytes: 512 * 1024 ** 3,
+ free_bytes: 128 * 1024 ** 3,
+ usage_percent: Math.random() * 100,
+ },
+ ],
+ host: {
+ os: "linux",
+ platform: "ubuntu",
+ kernel_version: "5.15.0",
+ },
+ net: [
+ {
+ name: "eth0",
+ bytes_sent: Math.random() * 10_000_000,
+ bytes_recv: Math.random() * 10_000_000,
+ packets_sent: Math.random() * 1_000_000,
+ packets_recv: Math.random() * 1_000_000,
+ err_in: 0,
+ err_out: 0,
+ drop_in: 0,
+ drop_out: 0,
+ fifo_in: 0,
+ fifo_out: 0,
+ },
+ ],
+ errors: i % 50 === 0 ? [{ metric: ["uptime"], err: "500" }] : [],
+ });
+
+ if (docs.length === batchSize) {
+ await CheckModel.insertMany(docs, { ordered: false });
+ console.log(`Inserted ${i + 1} / ${total}`);
+ docs.length = 0;
+ }
+ }
+
+ if (docs.length > 0) {
+ await CheckModel.insertMany(docs, { ordered: false });
+ }
+
+ await mongoose.disconnect();
+ const duration = ((Date.now() - startTime) / 1000).toFixed(2);
+ console.log(`Finished inserting ${total} checks in ${duration}s`);
+}
+
+run().catch((error) => {
+ console.error("Failed to seed checks", error);
+ process.exit(1);
+});
diff --git a/server/src/app.js b/server/src/app.ts
similarity index 67%
rename from server/src/app.js
rename to server/src/app.ts
index 4578de896..04699964d 100644
--- a/server/src/app.js
+++ b/server/src/app.ts
@@ -1,28 +1,37 @@
import express from "express";
import path from "path";
-import { responseHandler } from "./middleware/v1/responseHandler.js";
import cors from "cors";
import helmet from "helmet";
import compression from "compression";
import cookieParser from "cookie-parser";
-import languageMiddleware from "./middleware/v1/languageMiddleware.js";
import swaggerUi from "swagger-ui-express";
-import { handleErrors } from "./middleware/v1/handleErrors.js";
-import { setupRoutes } from "./config/routes.js";
-import { generalApiLimiter } from "./middleware/v1/rateLimiter.js";
-import { sanitizeBody, sanitizeQuery } from "./middleware/v1/sanitization.js";
+import { handleErrors } from "@/middleware/handleErrors.js";
+import { generalApiLimiter } from "@/middleware/rateLimiter.js";
+import { sanitizeBody, sanitizeQuery } from "@/middleware/sanitization.js";
+import { setupRoutes } from "@/config/routes.js";
+import { InitializedServices } from "@/config/services.js";
+import { InitializedControllers } from "@/config/controllers.js";
+import { EnvConfig } from "@/service/system/settingsService.js";
-export const createApp = ({ services, controllers, envSettings, frontendPath, openApiSpec }) => {
+export const createApp = ({
+ services,
+ controllers,
+ envSettings,
+ frontendPath,
+ openApiSpec,
+}: {
+ services: InitializedServices;
+ controllers: InitializedControllers;
+ envSettings: EnvConfig;
+ frontendPath: string;
+ openApiSpec: any;
+}) => {
const allowedOrigin = envSettings.clientHost;
-
const app = express();
app.use(generalApiLimiter);
// Static files
app.use(express.static(frontendPath));
- // Response handler
- app.use(responseHandler);
-
app.use(
cors({
origin: allowedOrigin,
@@ -63,7 +72,6 @@ export const createApp = ({ services, controllers, envSettings, frontendPath, op
},
})
);
- app.use(languageMiddleware(services.stringService, services.translationService, services.settingsService));
// Swagger UI
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(openApiSpec));
@@ -74,7 +82,7 @@ export const createApp = ({ services, controllers, envSettings, frontendPath, op
});
// Main app routes
- setupRoutes(app, controllers);
+ setupRoutes(app, controllers, services);
// FE routes
app.get("*", (req, res) => {
diff --git a/server/src/config/controllers.js b/server/src/config/controllers.js
deleted file mode 100644
index 927acd1a2..000000000
--- a/server/src/config/controllers.js
+++ /dev/null
@@ -1,82 +0,0 @@
-import { createCommonDependencies } from "../controllers/v1/baseController.js";
-
-// Services
-
-// V1 Controllers
-import MonitorController from "../controllers/v1/monitorController.js";
-import AuthController from "../controllers/v1/authController.js";
-import SettingsController from "../controllers/v1/settingsController.js";
-import CheckController from "../controllers/v1/checkController.js";
-import InviteController from "../controllers/v1/inviteController.js";
-import MaintenanceWindowController from "../controllers/v1/maintenanceWindowController.js";
-import QueueController from "../controllers/v1/queueController.js";
-import LogController from "../controllers/v1/logController.js";
-import StatusPageController from "../controllers/v1/statusPageController.js";
-import NotificationController from "../controllers/v1/notificationController.js";
-import DiagnosticController from "../controllers/v1/diagnosticController.js";
-
-// V2 Controllers
-import AuthControllerV2 from "../controllers/v2/AuthController.js";
-import InviteControllerV2 from "../controllers/v2/InviteController.js";
-import MaintenanceControllerV2 from "../controllers/v2/MaintenanceController.js";
-import MonitorControllerV2 from "../controllers/v2/MonitorController.js";
-import NotificationChannelControllerV2 from "../controllers/v2/NotificationChannelController.js";
-import QueueControllerV2 from "../controllers/v2/QueueController.js";
-export const initializeControllers = (services) => {
- const controllers = {};
- const commonDependencies = createCommonDependencies(services.db, services.errorService, services.logger, services.stringService);
-
- // V1
- controllers.authController = new AuthController(commonDependencies, {
- settingsService: services.settingsService,
- emailService: services.emailService,
- jobQueue: services.jobQueue,
- userService: services.userService,
- });
-
- controllers.monitorController = new MonitorController(commonDependencies, {
- settingsService: services.settingsService,
- jobQueue: services.jobQueue,
- emailService: services.emailService,
- monitorService: services.monitorService,
- });
-
- controllers.settingsController = new SettingsController(commonDependencies, {
- settingsService: services.settingsService,
- emailService: services.emailService,
- });
- controllers.checkController = new CheckController(commonDependencies, {
- settingsService: services.settingsService,
- checkService: services.checkService,
- });
- controllers.inviteController = new InviteController(commonDependencies, {
- inviteService: services.inviteService,
- });
-
- controllers.maintenanceWindowController = new MaintenanceWindowController(commonDependencies, {
- settingsService: services.settingsService,
- maintenanceWindowService: services.maintenanceWindowService,
- });
- controllers.queueController = new QueueController(commonDependencies, {
- jobQueue: services.jobQueue,
- });
- controllers.logController = new LogController(commonDependencies);
- controllers.statusPageController = new StatusPageController(commonDependencies);
- controllers.notificationController = new NotificationController(commonDependencies, {
- notificationService: services.notificationService,
- statusService: services.statusService,
- });
- controllers.diagnosticController = new DiagnosticController(commonDependencies, {
- diagnosticService: services.diagnosticService,
- });
-
- //V2
- controllers.authControllerV2 = new AuthControllerV2(services.authServiceV2, services.inviteServiceV2);
- controllers.inviteControllerV2 = new InviteControllerV2(services.inviteServiceV2);
- controllers.maintenanceControllerV2 = new MaintenanceControllerV2(services.maintenanceServiceV2);
- controllers.monitorControllerV2 = new MonitorControllerV2(services.monitorServiceV2, services.checkServiceV2);
- controllers.notificationChannelControllerV2 = new NotificationChannelControllerV2(services.notificationChannelServiceV2);
- controllers.queueControllerV2 = new QueueControllerV2(services.jobQueueV2);
-
- return controllers;
-};
diff --git a/server/src/config/controllers.ts b/server/src/config/controllers.ts
new file mode 100644
index 000000000..7a20ed93c
--- /dev/null
+++ b/server/src/config/controllers.ts
@@ -0,0 +1,44 @@
+import MonitorController from "../controllers/monitorController.js";
+import AuthController from "../controllers/authController.js";
+import SettingsController from "../controllers/settingsController.js";
+import CheckController from "../controllers/checkController.js";
+import InviteController from "../controllers/inviteController.js";
+import MaintenanceWindowController from "../controllers/maintenanceWindowController.js";
+import QueueController from "../controllers/queueController.js";
+import LogController from "../controllers/logController.js";
+import StatusPageController from "../controllers/statusPageController.js";
+import NotificationController from "../controllers/notificationController.js";
+import DiagnosticController from "../controllers/diagnosticController.js";
+import IncidentController from "../controllers/incidentController.js";
+import type { InitializedServices } from "@/config/services.js";
+
+export interface InitializedControllers {
+ authController: AuthController;
+ monitorController: MonitorController;
+ settingsController: SettingsController;
+ checkController: CheckController;
+ inviteController: InviteController;
+ maintenanceWindowController: MaintenanceWindowController;
+ queueController: QueueController;
+ logController: LogController;
+ statusPageController: StatusPageController;
+ notificationController: NotificationController;
+ diagnosticController: DiagnosticController;
+ incidentController: IncidentController;
+}
+export const initializeControllers = (services: InitializedServices): InitializedControllers => {
+ return {
+ authController: new AuthController(services.userService),
+ monitorController: new MonitorController(services.monitorService),
+ settingsController: new SettingsController(services.settingsService, services.emailService, services.db),
+ checkController: new CheckController(services.checkService),
+ inviteController: new InviteController(services.inviteService),
+ maintenanceWindowController: new MaintenanceWindowController(services.maintenanceWindowService),
+ queueController: new QueueController(services.jobQueue),
+ logController: new LogController(services.logger),
+ statusPageController: new StatusPageController(services.statusPageService, services.monitorsRepository, services.settingsService),
+ notificationController: new NotificationController(services.notificationsService, services.monitorsRepository),
+ diagnosticController: new DiagnosticController(services.diagnosticService),
+ incidentController: new IncidentController(services.incidentService),
+ };
+};
diff --git a/server/src/config/database.js b/server/src/config/database.js
deleted file mode 100644
index e69de29bb..000000000
diff --git a/server/src/config/index.ts b/server/src/config/index.ts
deleted file mode 100644
index 7ace6f99c..000000000
--- a/server/src/config/index.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import dotenv from "dotenv";
-
-export interface IEevConfig {
- NODE_ENV: string;
- JWT_SECRET: string;
- PORT: number;
- PAGESPEED_API_KEY: string;
- SMTP_HOST: string;
- SMTP_PORT: number;
- SMTP_USER: string;
- SMTP_PASS: string;
-}
-
-dotenv.config();
-
-export const config: IEevConfig = {
- NODE_ENV: process.env.NODE_ENV || "development",
- JWT_SECRET: process.env.JWT_SECRET || "your_jwt_secret",
- PORT: process.env.PORT ? parseInt(process.env.PORT, 10) : 3000,
- PAGESPEED_API_KEY: process.env.PAGESPEED_API_KEY || "",
- SMTP_HOST: process.env.SMTP_HOST || "smtp.example.com",
- SMTP_PORT: process.env.SMTP_PORT ? parseInt(process.env.SMTP_PORT, 10) : 587,
- SMTP_USER: process.env.SMTP_USER || "user@example.com",
- SMTP_PASS: process.env.SMTP_PASS || "your_smtp_password",
-};
diff --git a/server/src/config/routes.js b/server/src/config/routes.js
deleted file mode 100644
index 13b369262..000000000
--- a/server/src/config/routes.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import { verifyJWT } from "../middleware/v1/verifyJWT.js";
-import { authApiLimiter } from "../middleware/v1/rateLimiter.js";
-
-import AuthRoutes from "../routes/v1/authRoute.js";
-import InviteRoutes from "../routes/v1//inviteRoute.js";
-import MonitorRoutes from "../routes/v1/monitorRoute.js";
-import CheckRoutes from "../routes/v1/checkRoute.js";
-import SettingsRoutes from "../routes/v1/settingsRoute.js";
-import MaintenanceWindowRoutes from "../routes/v1/maintenanceWindowRoute.js";
-import StatusPageRoutes from "../routes/v1/statusPageRoute.js";
-import QueueRoutes from "../routes/v1/queueRoute.js";
-import LogRoutes from "../routes/v1/logRoutes.js";
-import DiagnosticRoutes from "../routes/v1//diagnosticRoute.js";
-import NotificationRoutes from "../routes/v1/notificationRoute.js";
-
-//V2
-import AuthRoutesV2 from "../routes/v2/auth.js";
-import InviteRoutesV2 from "../routes/v2/invite.js";
-import MaintenanceRoutesV2 from "../routes/v2/maintenance.js";
-import MonitorRoutesV2 from "../routes/v2/monitors.js";
-import NotificationChannelRoutesV2 from "../routes/v2/notificationChannels.js";
-import QueueRoutesV2 from "../routes/v2/queue.js";
-
-export const setupRoutes = (app, controllers) => {
- // V1
- const authRoutes = new AuthRoutes(controllers.authController);
- const monitorRoutes = new MonitorRoutes(controllers.monitorController);
- const settingsRoutes = new SettingsRoutes(controllers.settingsController);
- const checkRoutes = new CheckRoutes(controllers.checkController);
- const inviteRoutes = new InviteRoutes(controllers.inviteController);
- const maintenanceWindowRoutes = new MaintenanceWindowRoutes(controllers.maintenanceWindowController);
- const queueRoutes = new QueueRoutes(controllers.queueController);
- const logRoutes = new LogRoutes(controllers.logController);
- const statusPageRoutes = new StatusPageRoutes(controllers.statusPageController);
- const notificationRoutes = new NotificationRoutes(controllers.notificationController);
- const diagnosticRoutes = new DiagnosticRoutes(controllers.diagnosticController);
-
- app.use("/api/v1/auth", authApiLimiter, authRoutes.getRouter());
- app.use("/api/v1/monitors", verifyJWT, monitorRoutes.getRouter());
- app.use("/api/v1/settings", verifyJWT, settingsRoutes.getRouter());
- app.use("/api/v1/checks", verifyJWT, checkRoutes.getRouter());
- app.use("/api/v1/invite", inviteRoutes.getRouter());
- app.use("/api/v1/maintenance-window", verifyJWT, maintenanceWindowRoutes.getRouter());
- app.use("/api/v1/queue", verifyJWT, queueRoutes.getRouter());
- app.use("/api/v1/logs", verifyJWT, logRoutes.getRouter());
- app.use("/api/v1/status-page", statusPageRoutes.getRouter());
- app.use("/api/v1/notifications", verifyJWT, notificationRoutes.getRouter());
- app.use("/api/v1/diagnostic", verifyJWT, diagnosticRoutes.getRouter());
-
- // V2
- const authRoutesV2 = new AuthRoutesV2(controllers.authControllerV2);
- const inviteRoutesV2 = new InviteRoutesV2(controllers.inviteControllerV2);
- const maintenanceRoutesV2 = new MaintenanceRoutesV2(controllers.maintenanceControllerV2);
- const monitorRoutesV2 = new MonitorRoutesV2(controllers.monitorControllerV2);
- const notificationChannelRoutesV2 = new NotificationChannelRoutesV2(controllers.notificationChannelControllerV2);
- const queueRoutesV2 = new QueueRoutesV2(controllers.queueControllerV2);
-
- app.use("/api/v2/auth", authApiLimiter, authRoutesV2.getRouter());
- app.use("/api/v2/invite", inviteRoutesV2.getRouter());
- app.use("/api/v2/maintenance", maintenanceRoutesV2.getRouter());
- app.use("/api/v2/monitors", monitorRoutesV2.getRouter());
- app.use("/api/v2/notification-channels", notificationChannelRoutesV2.getRouter());
- app.use("/api/v2/queue", queueRoutesV2.getRouter());
-};
diff --git a/server/src/config/routes.ts b/server/src/config/routes.ts
new file mode 100644
index 000000000..d38ac69ae
--- /dev/null
+++ b/server/src/config/routes.ts
@@ -0,0 +1,45 @@
+import { createVerifyJWT } from "../middleware/verifyJWT.js";
+import { authApiLimiter } from "../middleware/rateLimiter.js";
+
+import AuthRoutes from "../routes/authRoute.js";
+import InviteRoutes from "../routes/inviteRoute.js";
+import MonitorRoutes from "../routes/monitorRoute.js";
+import CheckRoutes from "../routes/checkRoute.js";
+import SettingsRoutes from "../routes/settingsRoute.js";
+import MaintenanceWindowRoutes from "../routes/maintenanceWindowRoute.js";
+import StatusPageRoutes from "../routes/statusPageRoute.js";
+import QueueRoutes from "../routes/queueRoute.js";
+import LogRoutes from "../routes/logRoutes.js";
+import DiagnosticRoutes from "../routes/diagnosticRoute.js";
+import NotificationRoutes from "../routes/notificationRoute.js";
+
+import IncidentRoutes from "../routes/incidentRoute.js";
+
+export const setupRoutes = (app: any, controllers: Record, services: Record) => {
+ const verifyJWT = createVerifyJWT(services.settingsService);
+ const authRoutes = new AuthRoutes(controllers.authController, verifyJWT);
+ const monitorRoutes = new MonitorRoutes(controllers.monitorController);
+ const settingsRoutes = new SettingsRoutes(controllers.settingsController);
+ const checkRoutes = new CheckRoutes(controllers.checkController);
+ const inviteRoutes = new InviteRoutes(controllers.inviteController, verifyJWT);
+ const maintenanceWindowRoutes = new MaintenanceWindowRoutes(controllers.maintenanceWindowController);
+ const queueRoutes = new QueueRoutes(controllers.queueController);
+ const logRoutes = new LogRoutes(controllers.logController);
+ const statusPageRoutes = new StatusPageRoutes(controllers.statusPageController, verifyJWT);
+ const notificationRoutes = new NotificationRoutes(controllers.notificationController);
+ const diagnosticRoutes = new DiagnosticRoutes(controllers.diagnosticController, verifyJWT);
+ const incidentRoutes = new IncidentRoutes(controllers.incidentController);
+
+ app.use("/api/v1/auth", authApiLimiter, authRoutes.getRouter());
+ app.use("/api/v1/monitors", verifyJWT, monitorRoutes.getRouter());
+ app.use("/api/v1/settings", verifyJWT, settingsRoutes.getRouter());
+ app.use("/api/v1/checks", verifyJWT, checkRoutes.getRouter());
+ app.use("/api/v1/invite", inviteRoutes.getRouter());
+ app.use("/api/v1/maintenance-window", verifyJWT, maintenanceWindowRoutes.getRouter());
+ app.use("/api/v1/queue", verifyJWT, queueRoutes.getRouter());
+ app.use("/api/v1/logs", verifyJWT, logRoutes.getRouter());
+ app.use("/api/v1/status-page", statusPageRoutes.getRouter());
+ app.use("/api/v1/notifications", verifyJWT, notificationRoutes.getRouter());
+ app.use("/api/v1/diagnostic", verifyJWT, diagnosticRoutes.getRouter());
+ app.use("/api/v1/incidents", verifyJWT, incidentRoutes.getRouter());
+};
diff --git a/server/src/config/services.js b/server/src/config/services.js
deleted file mode 100644
index c1679868d..000000000
--- a/server/src/config/services.js
+++ /dev/null
@@ -1,306 +0,0 @@
-import ServiceRegistry from "../service/v1/system/serviceRegistry.js";
-import TranslationService from "../service/v1/system/translationService.js";
-import StringService from "../service/v1/system/stringService.js";
-import MongoDB from "../db/v1/MongoDB.js";
-import NetworkService from "../service/v1/infrastructure/networkService.js";
-import EmailService from "../service/v1/infrastructure/emailService.js";
-import BufferService from "../service/v1/infrastructure/bufferService.js";
-import StatusService from "../service/v1/infrastructure/statusService.js";
-import NotificationUtils from "../service/v1/infrastructure/notificationUtils.js";
-import NotificationService from "../service/v1/infrastructure/notificationService.js";
-import ErrorService from "../service/v1/infrastructure/errorService.js";
-import SuperSimpleQueueHelper from "../service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js";
-import SuperSimpleQueue from "../service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js";
-import UserService from "../service/v1/business/userService.js";
-import CheckService from "../service/v1/business/checkService.js";
-import DiagnosticService from "../service/v1/business/diagnosticService.js";
-import InviteService from "../service/v1/business/inviteService.js";
-import MaintenanceWindowService from "../service/v1/business/maintenanceWindowService.js";
-import MonitorService from "../service/v1/business/monitorService.js";
-import papaparse from "papaparse";
-import axios from "axios";
-import got from "got";
-import ping from "ping";
-import http from "http";
-import https from "https";
-import Docker from "dockerode";
-import net from "net";
-import fs from "fs";
-import path from "path";
-import nodemailer from "nodemailer";
-import pkg from "handlebars";
-const { compile } = pkg;
-import mjml2html from "mjml";
-import jwt from "jsonwebtoken";
-import crypto from "crypto";
-import { games } from "gamedig";
-import jmespath from "jmespath";
-import { GameDig } from "gamedig";
-
-import { fileURLToPath } from "url";
-import { ObjectId } from "mongodb";
-
-// DB Modules
-import { NormalizeData, NormalizeDataUptimeDetails } from "../utils/dataUtils.js";
-import { GenerateAvatarImage } from "../utils/imageProcessing.js";
-import { ParseBoolean } from "../utils/utils.js";
-
-// Models
-import Check from "../db/v1/models/Check.js";
-import Monitor from "../db/v1/models/Monitor.js";
-import User from "../db/v1/models/User.js";
-import InviteToken from "../db/v1/models/InviteToken.js";
-import StatusPage from "../db/v1/models/StatusPage.js";
-import Team from "../db/v1/models/Team.js";
-import MaintenanceWindow from "../db/v1/models/MaintenanceWindow.js";
-import MonitorStats from "../db/v1/models/MonitorStats.js";
-import Notification from "../db/v1/models/Notification.js";
-import RecoveryToken from "../db/v1/models/RecoveryToken.js";
-import AppSettings from "../db/v1/models/AppSettings.js";
-
-import InviteModule from "../db/v1/modules/inviteModule.js";
-import CheckModule from "../db/v1/modules/checkModule.js";
-import StatusPageModule from "../db/v1/modules/statusPageModule.js";
-import UserModule from "../db/v1/modules/userModule.js";
-import MaintenanceWindowModule from "../db/v1/modules/maintenanceWindowModule.js";
-import MonitorModule from "../db/v1/modules/monitorModule.js";
-import NotificationModule from "../db/v1/modules/notificationModule.js";
-import RecoveryModule from "../db/v1/modules/recoveryModule.js";
-import SettingsModule from "../db/v1/modules/settingsModule.js";
-
-// V2 Business
-import AuthServiceV2 from "../service/v2/business/AuthService.js";
-import CheckServiceV2 from "../service/v2/business/CheckService.js";
-import InviteServiceV2 from "../service/v2/business/InviteService.js";
-import MaintenanceServiceV2 from "../service/v2/business/MaintenanceService.js";
-import MonitorServiceV2 from "../service/v2/business/MonitorService.js";
-import MonitorStatsServiceV2 from "../service/v2/business/MonitorStatsService.js";
-import NotificationChannelServiceV2 from "../service/v2/business/NotificationChannelService.js";
-import QueueServiceV2 from "../service/v2/business/QueueService.js";
-import UserServiceV2 from "../service/v2/business/UserService.js";
-
-// V2 Infra
-import DiscordServiceV2 from "../service/v2/infrastructure/NotificationServices/Discord.js";
-import EmailServiceV2 from "../service/v2/infrastructure/NotificationServices/Email.js";
-import SlackServiceV2 from "../service/v2/infrastructure/NotificationServices/Slack.js";
-import WebhookServiceV2 from "../service/v2/infrastructure/NotificationServices/Webhook.js";
-import JobGeneratorV2 from "../service/v2/infrastructure/JobGenerator.js";
-import JobQueueV2 from "../service/v2/infrastructure/JobQueue.js";
-import NetworkServiceV2 from "../service/v2/infrastructure/NetworkService.js";
-import NotificationServiceV2 from "../service/v2/infrastructure/NotificationService.js";
-import StatusServiceV2 from "../service/v2/infrastructure/StatusService.js";
-
-export const initializeServices = async ({ logger, envSettings, settingsService }) => {
- const serviceRegistry = new ServiceRegistry({ logger });
- ServiceRegistry.instance = serviceRegistry;
-
- const translationService = new TranslationService(logger);
- await translationService.initialize();
-
- const stringService = new StringService(translationService);
-
- // Create DB
- const checkModule = new CheckModule({ logger, Check, Monitor, User });
- const inviteModule = new InviteModule({ InviteToken, crypto, stringService });
- const statusPageModule = new StatusPageModule({ StatusPage, NormalizeData, stringService });
- const userModule = new UserModule({ User, Team, GenerateAvatarImage, ParseBoolean, stringService });
- const maintenanceWindowModule = new MaintenanceWindowModule({ MaintenanceWindow });
- const monitorModule = new MonitorModule({
- Monitor,
- MonitorStats,
- Check,
- stringService,
- fs,
- path,
- fileURLToPath,
- ObjectId,
- NormalizeData,
- NormalizeDataUptimeDetails,
- });
- const notificationModule = new NotificationModule({ Notification, Monitor });
- const recoveryModule = new RecoveryModule({ User, RecoveryToken, crypto, stringService });
- const settingsModule = new SettingsModule({ AppSettings });
-
- const db = new MongoDB({
- logger,
- envSettings,
- checkModule,
- inviteModule,
- statusPageModule,
- userModule,
- maintenanceWindowModule,
- monitorModule,
- notificationModule,
- recoveryModule,
- settingsModule,
- });
-
- await db.connect();
-
- const networkService = new NetworkService({
- axios,
- got,
- https,
- jmespath,
- GameDig,
- ping,
- logger,
- http,
- Docker,
- net,
- stringService,
- settingsService,
- });
- const emailService = new EmailService(settingsService, fs, path, compile, mjml2html, nodemailer, logger);
- const bufferService = new BufferService({ db, logger, envSettings });
- const statusService = new StatusService({ db, logger, buffer: bufferService });
-
- const notificationUtils = new NotificationUtils({
- stringService,
- emailService,
- });
-
- const notificationService = new NotificationService({
- emailService,
- db,
- logger,
- networkService,
- stringService,
- notificationUtils,
- });
-
- const errorService = new ErrorService();
-
- const superSimpleQueueHelper = new SuperSimpleQueueHelper({
- db,
- logger,
- networkService,
- statusService,
- notificationService,
- });
-
- const superSimpleQueue = await SuperSimpleQueue.create({
- envSettings,
- db,
- logger,
- helper: superSimpleQueueHelper,
- });
-
- // Business services
- const userService = new UserService({
- crypto,
- db,
- emailService,
- settingsService,
- logger,
- stringService,
- jwt,
- errorService,
- jobQueue: superSimpleQueue,
- });
- const checkService = new CheckService({
- db,
- settingsService,
- stringService,
- errorService,
- });
- const diagnosticService = new DiagnosticService();
- const inviteService = new InviteService({
- db,
- settingsService,
- emailService,
- stringService,
- errorService,
- });
- const maintenanceWindowService = new MaintenanceWindowService({
- db,
- settingsService,
- stringService,
- errorService,
- });
- const monitorService = new MonitorService({
- db,
- settingsService,
- jobQueue: superSimpleQueue,
- stringService,
- emailService,
- papaparse,
- logger,
- errorService,
- games,
- });
-
- // V2 Services
- const checkServiceV2 = new CheckServiceV2();
- const inviteServiceV2 = new InviteServiceV2();
- const maintenanceServiceV2 = new MaintenanceServiceV2();
- const monitorStatsServiceV2 = new MonitorStatsServiceV2();
- const notificationChannelServiceV2 = new NotificationChannelServiceV2();
- const userServiceV2 = new UserServiceV2();
- const discordServiceV2 = new DiscordServiceV2();
- const emailServiceV2 = new EmailServiceV2(userServiceV2);
- const slackServiceV2 = new SlackServiceV2();
- const webhookServiceV2 = new WebhookServiceV2();
- const networkServiceV2 = new NetworkServiceV2();
- const statusServiceV2 = new StatusServiceV2();
- const notificationServiceV2 = new NotificationServiceV2(userServiceV2);
- const jobGeneratorV2 = new JobGeneratorV2(
- networkServiceV2,
- checkServiceV2,
- monitorStatsServiceV2,
- statusServiceV2,
- notificationServiceV2,
- maintenanceServiceV2
- );
- const jobQueueV2 = await JobQueueV2.create(jobGeneratorV2);
- const authServiceV2 = new AuthServiceV2(jobQueueV2);
- const monitorServiceV2 = new MonitorServiceV2(jobQueueV2);
- const queueServiceV2 = new QueueServiceV2(jobQueueV2);
-
- const services = {
- //v1
- settingsService,
- translationService,
- stringService,
- db,
- networkService,
- emailService,
- bufferService,
- statusService,
- notificationService,
- jobQueue: superSimpleQueue,
- userService,
- checkService,
- diagnosticService,
- inviteService,
- maintenanceWindowService,
- monitorService,
- errorService,
- logger,
- //v2
- jobQueueV2,
- authServiceV2,
- checkServiceV2,
- inviteServiceV2,
- maintenanceServiceV2,
- monitorServiceV2,
- monitorStatsServiceV2,
- notificationChannelServiceV2,
- queueServiceV2,
- userServiceV2,
- discordServiceV2,
- emailServiceV2,
- slackServiceV2,
- webhookServiceV2,
- networkServiceV2,
- statusServiceV2,
- notificationServiceV2,
- jobGeneratorV2,
- };
-
- Object.values(services).forEach((service) => {
- ServiceRegistry.register(service.serviceName, service);
- });
-
- return services;
-};
diff --git a/server/src/config/services.ts b/server/src/config/services.ts
new file mode 100644
index 000000000..4437e9176
--- /dev/null
+++ b/server/src/config/services.ts
@@ -0,0 +1,292 @@
+import MongoDB from "../db/MongoDB.js";
+import NetworkService from "../service/infrastructure/networkService.js";
+import EmailService from "../service/infrastructure/emailService.js";
+import BufferService from "../service/infrastructure/bufferService.js";
+import {
+ NotificationsService,
+ StatusService,
+ WebhookProvider,
+ SlackProvider,
+ EmailProvider,
+ DiscordProvider,
+ PagerDutyProvider,
+ MatrixProvider,
+ INotificationsService,
+} from "@/service/index.js";
+import SuperSimpleQueueHelper from "../service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js";
+import SuperSimpleQueue from "../service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js";
+import UserService from "../service/business/userService.js";
+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/index.js";
+import { StatusPageService, IStatusPageService } from "../service/business/statusPageService.js";
+import IncidentService from "../service/business/incidentService.js";
+import axios from "axios";
+import got from "got";
+import ping from "ping";
+import http from "http";
+import https from "https";
+import Docker from "dockerode";
+import net from "net";
+import fs from "fs";
+import path from "path";
+import nodemailer from "nodemailer";
+import pkg from "handlebars";
+const { compile } = pkg;
+import mjml2html from "mjml";
+import jwt from "jsonwebtoken";
+import crypto from "crypto";
+import { games, GameDig } from "gamedig";
+import jmespath from "jmespath";
+
+// DB Modules
+import { GenerateAvatarImage } from "../utils/imageProcessing.js";
+import { ParseBoolean } from "../utils/utils.js";
+
+// Models
+import InviteToken from "../db/models/Invite.js";
+import Team from "../db/models/Team.js";
+import MaintenanceWindow from "../db/models/MaintenanceWindow.js";
+import MonitorStats from "../db/models/MonitorStats.js";
+import NotificationModel from "../db/models/Notification.js";
+import RecoveryToken from "../db/models/RecoveryToken.js";
+
+// repositories
+import {
+ MongoMonitorsRepository,
+ MongoChecksRepository,
+ MongoMonitorStatsRepository,
+ MongoStatusPagesRepository,
+ MongoUsersRepository,
+ MongoInvitesRepository,
+ MongoRecoveryTokensRepository,
+ MongoNotificationsRepository,
+ MongoIncidentRepository,
+ MongoTeamsRepository,
+ MongoMaintenanceWindowsRepository,
+ IMonitorsRepository,
+ IChecksRepository,
+ IMonitorStatsRepository,
+ IStatusPagesRepository,
+ IUsersRepository,
+ IInvitesRepository,
+ IRecoveryTokensRepository,
+ ISettingsRepository,
+ INotificationsRepository,
+ IIncidentsRepository,
+ ITeamsRepository,
+ IMaintenanceWindowsRepository,
+} from "@/repositories/index.js";
+import { ILogger } from "@/utils/logger.js";
+import { EnvConfig } from "@/service/system/settingsService.js";
+
+export type InitializedServices = {
+ settingsService: any;
+ db: any;
+ networkService: any;
+ emailService: any;
+ bufferService: any;
+ statusService: any;
+ jobQueue: any;
+ userService: any;
+ checkService: any;
+ diagnosticService: any;
+ inviteService: any;
+ maintenanceWindowService: any;
+ monitorService: any;
+ incidentService: any;
+ logger: any;
+ notificationsService: INotificationsService;
+ statusPageService: IStatusPageService;
+
+ // Repositories
+ monitorsRepository: IMonitorsRepository;
+ checksRepository: IChecksRepository;
+ monitorStatsRepository: IMonitorStatsRepository;
+ statusPagesRepository: IStatusPagesRepository;
+ usersRepository: IUsersRepository;
+ invitesRepository: IInvitesRepository;
+ recoveryTokensRepository: IRecoveryTokensRepository;
+ settingsRepository: ISettingsRepository;
+ notificationsRepository: INotificationsRepository;
+ incidentsRepository: IIncidentsRepository;
+ teamsRepository: ITeamsRepository;
+ maintenanceWindowsRepository: IMaintenanceWindowsRepository;
+};
+
+export const initializeServices = async ({
+ logger,
+ envSettings,
+ settingsService,
+ settingsRepository,
+}: {
+ logger: ILogger;
+ envSettings: EnvConfig;
+ settingsService: any;
+ settingsRepository: ISettingsRepository;
+}): Promise => {
+ // Create DB
+
+ const db = new MongoDB(logger, envSettings);
+
+ await db.connect();
+
+ // Repositories
+ const monitorsRepository = new MongoMonitorsRepository();
+ const checksRepository = new MongoChecksRepository(logger);
+ const monitorStatsRepository = new MongoMonitorStatsRepository();
+ const statusPagesRepository = new MongoStatusPagesRepository();
+ const usersRepository = new MongoUsersRepository();
+ const invitesRepository = new MongoInvitesRepository();
+ const recoveryTokensRepository = new MongoRecoveryTokensRepository();
+ const notificationsRepository = new MongoNotificationsRepository();
+ const incidentsRepository = new MongoIncidentRepository();
+ const teamsRepository = new MongoTeamsRepository();
+ const maintenanceWindowsRepository = new MongoMaintenanceWindowsRepository();
+
+ const networkService = new NetworkService({
+ axios,
+ got,
+ https,
+ jmespath,
+ GameDig,
+ ping,
+ logger,
+ http,
+ Docker,
+ net,
+ settingsService,
+ });
+ const emailService = new EmailService(settingsService, fs, path, compile, mjml2html, nodemailer, logger);
+
+ const incidentService = new IncidentService({
+ logger,
+ incidentsRepository,
+ monitorsRepository,
+ usersRepository,
+ });
+
+ const checkService = new CheckService({
+ monitorsRepository,
+ logger,
+ checksRepository,
+ });
+
+ const bufferService = new BufferService({ logger, checkService, settingsService });
+
+ const statusService = new StatusService({ logger, buffer: bufferService, monitorsRepository });
+
+ const webhookProvider = new WebhookProvider(logger);
+ const slackProvider = new SlackProvider(logger);
+ const emailProvider = new EmailProvider(emailService, logger);
+ const discordProvider = new DiscordProvider(logger);
+ const pagerDutyProvider = new PagerDutyProvider(logger);
+ const matrixProvider = new MatrixProvider(logger);
+
+ const notificationsService = new NotificationsService(
+ notificationsRepository,
+ monitorsRepository,
+ webhookProvider,
+ emailProvider,
+ slackProvider,
+ discordProvider,
+ pagerDutyProvider,
+ matrixProvider,
+ logger
+ );
+
+ const superSimpleQueueHelper = new SuperSimpleQueueHelper({
+ logger,
+ networkService,
+ statusService,
+ notificationsService,
+ checkService,
+ buffer: bufferService,
+ incidentService,
+ maintenanceWindowsRepository,
+ });
+
+ const superSimpleQueue = await SuperSimpleQueue.create({
+ logger,
+ helper: superSimpleQueueHelper,
+ monitorsRepository,
+ });
+
+ // Business services
+ const userService = new UserService({
+ crypto,
+ emailService,
+ settingsService,
+ logger,
+ jwt,
+ jobQueue: superSimpleQueue,
+ monitorsRepository,
+ usersRepository,
+ invitesRepository,
+ recoveryTokensRepository,
+ settingsRepository,
+ teamsRepository,
+ });
+
+ const diagnosticService = new DiagnosticService();
+ const inviteService = new InviteService({
+ invitesRepository,
+ settingsService,
+ emailService,
+ });
+ const maintenanceWindowService = new MaintenanceWindowService({
+ monitorsRepository,
+ maintenanceWindowsRepository,
+ });
+ const monitorService = new MonitorService({
+ jobQueue: superSimpleQueue,
+ emailService,
+ logger,
+ games,
+ monitorsRepository,
+ checksRepository,
+ monitorStatsRepository,
+ statusPagesRepository,
+ });
+
+ const statusPageService = new StatusPageService(statusPagesRepository);
+
+ const services = {
+ //v1
+ settingsService,
+ db,
+ networkService,
+ emailService,
+ bufferService,
+ statusService,
+ jobQueue: superSimpleQueue,
+ userService,
+ checkService,
+ diagnosticService,
+ inviteService,
+ maintenanceWindowService,
+ monitorService,
+ incidentService,
+ logger,
+ notificationsService,
+ statusPageService,
+
+ // Repositories
+ monitorsRepository,
+ checksRepository,
+ monitorStatsRepository,
+ statusPagesRepository,
+ usersRepository,
+ invitesRepository,
+ recoveryTokensRepository,
+ settingsRepository,
+ notificationsRepository,
+ incidentsRepository,
+ teamsRepository,
+ maintenanceWindowsRepository,
+ };
+
+ return services;
+};
diff --git a/server/src/controllers/authController.ts b/server/src/controllers/authController.ts
new file mode 100755
index 000000000..23ea6e42e
--- /dev/null
+++ b/server/src/controllers/authController.ts
@@ -0,0 +1,237 @@
+import { Request, Response, NextFunction } from "express";
+import { AppError } from "@/utils/AppError.js";
+
+import {
+ registrationBodyValidation,
+ loginValidation,
+ editUserBodyValidation,
+ recoveryValidation,
+ recoveryTokenBodyValidation,
+ newPasswordValidation,
+ getUserByIdParamValidation,
+ editUserByIdParamValidation,
+ editUserByIdBodyValidation,
+ editSuperadminUserByIdBodyValidation,
+ editUserPasswordByIdBodyValidation,
+} from "@/validation/joi.js";
+
+const SERVICE_NAME = "authController";
+
+class AuthController {
+ static SERVICE_NAME = SERVICE_NAME;
+
+ private userService: any;
+
+ constructor(userService: any) {
+ this.userService = userService;
+ }
+
+ get serviceName() {
+ return AuthController.SERVICE_NAME;
+ }
+
+ registerUser = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const newUser = req.body.user;
+ const newUserToken = req.body.token;
+ if (newUser?.email) {
+ newUser.email = newUser.email.toLowerCase();
+ }
+ await registrationBodyValidation.validateAsync(newUser);
+
+ const { user, token } = await this.userService.registerUser(newUser, newUserToken, req.file);
+ res.status(200).json({
+ success: true,
+ msg: "User registered successfully",
+ data: { user, token },
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ loginUser = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ if (req.body?.email) {
+ req.body.email = req.body.email?.toLowerCase();
+ }
+ await loginValidation.validateAsync(req.body);
+ const { user, token } = await this.userService.loginUser(req.body.email, req.body.password);
+
+ return res.status(200).json({
+ success: true,
+ msg: "User logged in successfully",
+ data: {
+ user,
+ token,
+ },
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ editUser = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await editUserBodyValidation.validateAsync(req.body);
+
+ const updatedUser = await this.userService.editUser(req.body, req.file, req.user);
+
+ res.status(200).json({
+ success: true,
+ msg: "User updated successfully",
+ data: updatedUser,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ checkSuperadminExists = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const superAdminExists = await this.userService.checkSuperadminExists();
+ return res.status(200).json({
+ success: true,
+ msg: "Superadmin existence checked successfully",
+ data: superAdminExists,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ requestRecovery = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await recoveryValidation.validateAsync(req.body);
+ const email = req?.body?.email;
+ const msgId = await this.userService.requestRecovery(email);
+ return res.status(200).json({
+ success: true,
+ msg: "Password recovery email sent successfully",
+ data: msgId,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ validateRecovery = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await recoveryTokenBodyValidation.validateAsync(req.body);
+ await this.userService.validateRecovery(req.body.recoveryToken);
+ return res.status(200).json({
+ success: true,
+ msg: "Recovery token is valid",
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ resetPassword = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await newPasswordValidation.validateAsync(req.body);
+ const { user, token } = await this.userService.resetPassword(req.body.password, req.body.recoveryToken);
+ return res.status(200).json({
+ success: true,
+ msg: "Password has been reset successfully",
+ data: { user, token },
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ deleteUser = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await this.userService.deleteUser(req.user);
+ return res.status(200).json({
+ success: true,
+ msg: "User deleted successfully",
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getAllUsers = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const allUsers = await this.userService.getAllUsers();
+ return res.status(200).json({
+ success: true,
+ msg: "Users retrieved successfully",
+ data: allUsers,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getUserById = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await getUserByIdParamValidation.validateAsync(req.params);
+ const userId = req?.params?.userId;
+ const roles = req?.user?.role;
+
+ if (!userId) {
+ throw new Error("No user ID in request");
+ }
+
+ if (!roles || roles.length === 0) {
+ throw new Error("No roles in request");
+ }
+
+ const user = await this.userService.getUserById(roles, userId);
+
+ return res.status(200).json({ success: true, msg: "ok", data: user });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ editUserById = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const roles = req?.user?.role;
+
+ if (!roles || !roles.includes("superadmin")) {
+ throw new AppError({ message: "Unauthorized", status: 403 });
+ }
+
+ const userId = req.params.userId;
+ const user = { ...req.body };
+
+ await editUserByIdParamValidation.validateAsync(req.params);
+ // If this is superadmin self edit, allow "superadmin" role
+ if (userId === req.user?.id) {
+ await editSuperadminUserByIdBodyValidation.validateAsync(req.body);
+ } else {
+ await editUserByIdBodyValidation.validateAsync(req.body);
+ }
+
+ await this.userService.editUserById(userId, user);
+ return res.status(200).json({ success: true, msg: "ok" });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ editUserPasswordById = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const roles = req?.user?.role;
+ if (!roles || !roles.includes("superadmin")) {
+ throw new AppError({ message: "Unauthorized", status: 403 });
+ }
+
+ const userId = req.params.userId;
+ await editUserByIdParamValidation.validateAsync(req.params);
+ await editUserPasswordByIdBodyValidation.validateAsync(req.body);
+ const updatedPassword = req.body.password;
+ await this.userService.setPasswordByUserId(userId, updatedPassword);
+ return res.status(200).json({ success: true, msg: "Password reset successfully" });
+ } catch (error) {
+ next(error);
+ }
+ };
+}
+
+export default AuthController;
diff --git a/server/src/controllers/checkController.ts b/server/src/controllers/checkController.ts
new file mode 100755
index 000000000..b13663d97
--- /dev/null
+++ b/server/src/controllers/checkController.ts
@@ -0,0 +1,135 @@
+import { Request, Response, NextFunction } from "express";
+
+import {
+ getChecksParamValidation,
+ getChecksQueryValidation,
+ getTeamChecksQueryValidation,
+ deleteChecksParamValidation,
+ deleteChecksByTeamIdParamValidation,
+ updateChecksTTLBodyValidation,
+ ackCheckBodyValidation,
+ ackAllChecksParamValidation,
+ ackAllChecksBodyValidation,
+} from "@/validation/joi.js";
+
+const SERVICE_NAME = "checkController";
+
+class CheckController {
+ static SERVICE_NAME = SERVICE_NAME;
+
+ private checkService: any;
+ constructor(checkService: any) {
+ this.checkService = checkService;
+ }
+
+ get serviceName() {
+ return CheckController.SERVICE_NAME;
+ }
+
+ getChecksByMonitor = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await getChecksParamValidation.validateAsync(req.params);
+ await getChecksQueryValidation.validateAsync(req.query);
+
+ const result = await this.checkService.getChecksByMonitor({
+ monitorId: req?.params?.monitorId,
+ query: req?.query,
+ teamId: req?.user?.teamId,
+ });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Checks retrieved successfully",
+ data: result,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getChecksByTeam = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await getTeamChecksQueryValidation.validateAsync(req.query);
+ const checkData = await this.checkService.getChecksByTeam({
+ teamId: req?.user?.teamId,
+ query: req?.query,
+ });
+ return res.status(200).json({
+ success: true,
+ msg: "Team checks retrieved successfully",
+ data: checkData,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getChecksSummaryByTeamId = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const dateRange = req.query?.dateRange || "hour";
+ const summary = await this.checkService.getChecksSummaryByTeamId({ teamId: req?.user?.teamId, dateRange });
+ return res.status(200).json({
+ success: true,
+ msg: "Checks summary retrieved successfully",
+ data: summary,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ deleteChecks = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await deleteChecksParamValidation.validateAsync(req.params);
+
+ const deletedCount = await this.checkService.deleteChecks({
+ monitorId: req.params.monitorId,
+ teamId: req?.user?.teamId,
+ });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Checks deleted successfully",
+ data: { deletedCount },
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ deleteChecksByTeamId = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await deleteChecksByTeamIdParamValidation.validateAsync(req.params);
+
+ const deletedCount = await this.checkService.deleteChecksByTeamId({ teamId: req?.user?.teamId });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Checks deleted successfully",
+ data: { deletedCount },
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ updateChecksTTL = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await updateChecksTTLBodyValidation.validateAsync(req.body);
+
+ await this.checkService.updateChecksTTL({
+ teamId: req?.user?.teamId,
+ ttl: req?.body?.ttl,
+ });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Checks TTL updated successfully",
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+}
+
+export default CheckController;
diff --git a/server/src/controllers/controllerUtils.ts b/server/src/controllers/controllerUtils.ts
new file mode 100755
index 000000000..e6c8767fa
--- /dev/null
+++ b/server/src/controllers/controllerUtils.ts
@@ -0,0 +1,119 @@
+import { AppError } from "@/utils/AppError.js";
+import { type MonitorType, MonitorTypes } from "@/types/index.js";
+
+const fetchMonitorCertificate = async (sslChecker: any, monitor: any): Promise => {
+ const monitorUrl = new URL(monitor.url);
+ const hostname = monitorUrl.hostname;
+ const cert = await sslChecker(hostname);
+ // Throw an error if no cert or if cert.validTo is not present
+ if (cert?.validTo === null || cert?.validTo === undefined) {
+ throw new Error("Certificate not found");
+ }
+ return cert;
+};
+const requireString = (value: unknown, fieldName: string): string => {
+ if (typeof value === "string" && value.trim().length > 0) {
+ return value;
+ }
+ throw new AppError({ message: `${fieldName} is required`, status: 400 });
+};
+
+const optionalString = (value: unknown, fieldName: string): string | undefined => {
+ if (value === undefined) {
+ return undefined;
+ }
+ if (typeof value === "string") {
+ return value;
+ }
+ throw new AppError({ message: `${fieldName} must be a string`, status: 400 });
+};
+
+const optionalNumber = (value: unknown, fieldName: string): number | undefined => {
+ if (value === undefined) {
+ return undefined;
+ }
+ if (typeof value === "number" && Number.isFinite(value)) {
+ return value;
+ }
+ if (typeof value === "string" && value.trim() !== "") {
+ const parsed = Number(value);
+ if (!Number.isNaN(parsed)) {
+ return parsed;
+ }
+ }
+ throw new AppError({ message: `${fieldName} must be a number`, status: 400 });
+};
+
+const optionalBoolean = (value: unknown, fieldName: string): boolean | undefined => {
+ if (value === undefined) {
+ return undefined;
+ }
+ if (typeof value === "boolean") {
+ return value;
+ }
+ if (typeof value === "string") {
+ if (value === "true") {
+ return true;
+ }
+ if (value === "false") {
+ return false;
+ }
+ }
+ throw new AppError({ message: `${fieldName} must be a boolean`, status: 400 });
+};
+
+const parseMonitorTypeFilter = (value: unknown): MonitorType | MonitorType[] | undefined => {
+ const parseSingle = (input: unknown): MonitorType => {
+ if (typeof input !== "string") {
+ throw new AppError({ message: "Monitor type must be a string", status: 400 });
+ }
+ if (!MonitorTypes.includes(input as MonitorType)) {
+ throw new AppError({ message: `Invalid monitor type: ${input}`, status: 400 });
+ }
+ return input as MonitorType;
+ };
+
+ if (value === undefined) {
+ return undefined;
+ }
+ if (Array.isArray(value)) {
+ return value.map((entry) => parseSingle(entry));
+ }
+ return parseSingle(value);
+};
+
+const parseSortOrder = (value: unknown): "asc" | "desc" | undefined => {
+ if (value === undefined) {
+ return undefined;
+ }
+ if (value === "asc" || value === "desc") {
+ return value;
+ }
+ throw new AppError({ message: "order must be either 'asc' or 'desc'", status: 400 });
+};
+
+const requireTeamId = (teamId?: string): string => {
+ if (!teamId) {
+ throw new AppError({ message: "Team ID is required", status: 400 });
+ }
+ return teamId;
+};
+
+const requireUserId = (userId?: string): string => {
+ if (!userId) {
+ throw new AppError({ message: "User ID is required", status: 400 });
+ }
+ return userId;
+};
+
+export {
+ fetchMonitorCertificate,
+ requireString,
+ optionalString,
+ optionalNumber,
+ optionalBoolean,
+ parseMonitorTypeFilter,
+ parseSortOrder,
+ requireTeamId,
+ requireUserId,
+};
diff --git a/server/src/controllers/diagnosticController.ts b/server/src/controllers/diagnosticController.ts
new file mode 100755
index 000000000..b4d59ceaf
--- /dev/null
+++ b/server/src/controllers/diagnosticController.ts
@@ -0,0 +1,31 @@
+import { Request, Response, NextFunction } from "express";
+
+const SERVICE_NAME = "diagnosticController";
+class DiagnosticController {
+ static SERVICE_NAME = SERVICE_NAME;
+
+ private diagnosticService: any;
+
+ constructor(diagnosticService: any) {
+ this.diagnosticService = diagnosticService;
+ }
+
+ get serviceName() {
+ return DiagnosticController.SERVICE_NAME;
+ }
+
+ getSystemStats = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const diagnostics = await this.diagnosticService.getSystemStats();
+ return res.status(200).json({
+ success: true,
+ msg: "OK",
+ data: diagnostics,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+}
+
+export default DiagnosticController;
diff --git a/server/src/controllers/incidentController.ts b/server/src/controllers/incidentController.ts
new file mode 100644
index 000000000..72e6498f9
--- /dev/null
+++ b/server/src/controllers/incidentController.ts
@@ -0,0 +1,107 @@
+import { AppError } from "@/utils/AppError.js";
+import { Request, Response, NextFunction } from "express";
+import { requireTeamId } from "./controllerUtils.js";
+
+const SERVICE_NAME = "incidentController";
+
+class IncidentController {
+ static SERVICE_NAME = SERVICE_NAME;
+
+ private incidentService: any;
+ constructor(incidentService: any) {
+ this.incidentService = incidentService;
+ }
+
+ get serviceName() {
+ return IncidentController.SERVICE_NAME;
+ }
+
+ getIncidentsByTeam = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const { sortOrder, dateRange, page, rowsPerPage, status, monitorId, resolutionType } = req.query || {};
+
+ const teamId = requireTeamId(req.user?.teamId);
+ const result = await this.incidentService.getIncidentsByTeam(
+ teamId,
+ sortOrder,
+ dateRange,
+ page,
+ rowsPerPage,
+ status,
+ monitorId,
+ resolutionType
+ );
+
+ return res.status(200).json({
+ success: true,
+ msg: "Incidents retrieved successfully",
+ data: result,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getIncidentSummary = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const teamId = req.user?.teamId;
+
+ if (!teamId) {
+ throw new AppError({ message: "Team ID is required", service: SERVICE_NAME, status: 400 });
+ }
+
+ const limit = req.query.limit ? parseInt(req.query.limit as string, 10) : 10;
+
+ const summary = await this.incidentService.getIncidentSummary(req?.user?.teamId, limit);
+
+ return res.status(200).json({
+ success: true,
+ msg: "Incident summary retrieved successfully",
+ data: summary,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getIncidentById = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const teamId = requireTeamId(req.user?.teamId);
+ const incidentId = req.params.incidentId;
+ if (!incidentId) {
+ throw new AppError({ message: "Incident ID is required", service: SERVICE_NAME, status: 400 });
+ }
+
+ const incident = await this.incidentService.getIncidentById(incidentId, teamId);
+
+ return res.status(200).json({
+ success: true,
+ msg: "Incident retrieved successfully",
+ data: incident,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ resolveIncidentManually = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const resolvedIncident = await this.incidentService.resolveIncident(
+ req?.params?.incidentId,
+ req?.user?.id,
+ req?.user?.teamId,
+ req?.body?.comment
+ );
+
+ return res.status(200).json({
+ success: true,
+ msg: "Incident resolved successfully",
+ data: resolvedIncident,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+}
+
+export default IncidentController;
diff --git a/server/src/controllers/inviteController.ts b/server/src/controllers/inviteController.ts
new file mode 100755
index 000000000..c3d63aa80
--- /dev/null
+++ b/server/src/controllers/inviteController.ts
@@ -0,0 +1,68 @@
+import { Request, Response, NextFunction } from "express";
+import { inviteBodyValidation, inviteVerificationBodyValidation } from "@/validation/joi.js";
+const SERVICE_NAME = "inviteController";
+
+class InviteController {
+ static SERVICE_NAME = SERVICE_NAME;
+ private inviteService: any;
+ constructor(inviteService: any) {
+ this.inviteService = inviteService;
+ }
+
+ get serviceName() {
+ return InviteController.SERVICE_NAME;
+ }
+
+ getInviteToken = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const invite = req.body;
+ const teamId = req?.user?.teamId;
+ invite.teamId = teamId;
+ await inviteBodyValidation.validateAsync(invite);
+ const inviteToken = await this.inviteService.getInviteToken({ invite, teamId });
+ return res.status(200).json({
+ success: true,
+ msg: "Invite token generated successfully",
+ data: inviteToken,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ sendInviteEmail = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const inviteRequest = req.body;
+ inviteRequest.teamId = req?.user?.teamId;
+ await inviteBodyValidation.validateAsync(inviteRequest);
+
+ const inviteToken = await this.inviteService.sendInviteEmail({
+ inviteRequest,
+ firstName: req?.user?.firstName,
+ });
+ return res.status(200).json({
+ success: true,
+ msg: "Invite issued successfully",
+ data: inviteToken,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ verifyInviteToken = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await inviteVerificationBodyValidation.validateAsync(req.body);
+ const invite = await this.inviteService.verifyInviteToken({ inviteToken: req?.body?.token });
+ return res.status(200).json({
+ success: true,
+ msg: "Invite verified successfully",
+ data: invite,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+}
+
+export default InviteController;
diff --git a/server/src/controllers/logController.ts b/server/src/controllers/logController.ts
new file mode 100644
index 000000000..d99de1e9c
--- /dev/null
+++ b/server/src/controllers/logController.ts
@@ -0,0 +1,28 @@
+import { Request, Response, NextFunction } from "express";
+
+const SERVICE_NAME = "LogController";
+
+class LogController {
+ static SERVICE_NAME = SERVICE_NAME;
+ private logger: any;
+ constructor(logger: any) {
+ this.logger = logger;
+ }
+ get serviceName() {
+ return LogController.SERVICE_NAME;
+ }
+
+ getLogs = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const logs = await this.logger.getLogs();
+ res.status(200).json({
+ success: true,
+ msg: "Logs fetched successfully",
+ data: logs,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+}
+export default LogController;
diff --git a/server/src/controllers/maintenanceWindowController.ts b/server/src/controllers/maintenanceWindowController.ts
new file mode 100644
index 000000000..0cc9610ea
--- /dev/null
+++ b/server/src/controllers/maintenanceWindowController.ts
@@ -0,0 +1,130 @@
+import { Request, Response, NextFunction } from "express";
+import {
+ createMaintenanceWindowBodyValidation,
+ editMaintenanceWindowByIdParamValidation,
+ editMaintenanceByIdWindowBodyValidation,
+ getMaintenanceWindowByIdParamValidation,
+ getMaintenanceWindowsByMonitorIdParamValidation,
+ getMaintenanceWindowsByTeamIdQueryValidation,
+ deleteMaintenanceWindowByIdParamValidation,
+} from "@/validation/joi.js";
+import { requireTeamId } from "@/controllers/controllerUtils.js";
+
+const SERVICE_NAME = "maintenanceWindowController";
+
+class MaintenanceWindowController {
+ static SERVICE_NAME = SERVICE_NAME;
+ private maintenanceWindowService: any;
+ constructor(maintenanceWindowService: any) {
+ this.maintenanceWindowService = maintenanceWindowService;
+ }
+
+ get serviceName() {
+ return MaintenanceWindowController.SERVICE_NAME;
+ }
+
+ createMaintenanceWindows = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await createMaintenanceWindowBodyValidation.validateAsync(req.body);
+ const teamId = requireTeamId(req?.user?.teamId);
+
+ await this.maintenanceWindowService.createMaintenanceWindow({ teamId, body: req.body });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Maintenance window created successfully",
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+ getMaintenanceWindowById = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await getMaintenanceWindowByIdParamValidation.validateAsync(req.params);
+
+ const teamId = requireTeamId(req.user?.teamId);
+
+ const maintenanceWindow = await this.maintenanceWindowService.getMaintenanceWindowById({ id: req.params.id, teamId });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Maintenance window fetched successfully",
+ data: maintenanceWindow,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getMaintenanceWindowsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await getMaintenanceWindowsByTeamIdQueryValidation.validateAsync(req.query);
+
+ const teamId = requireTeamId(req?.user?.teamId);
+
+ const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByTeamId({ teamId, query: req.query });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Maintenance windows fetched successfully",
+ data: maintenanceWindows,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getMaintenanceWindowsByMonitorId = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await getMaintenanceWindowsByMonitorIdParamValidation.validateAsync(req.params);
+
+ const teamId = requireTeamId(req?.user?.teamId);
+
+ const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByMonitorId({ monitorId: req.params.monitorId, teamId });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Maintenance windows fetched successfully",
+ data: maintenanceWindows,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+ deleteMaintenanceWindow = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await deleteMaintenanceWindowByIdParamValidation.validateAsync(req.params);
+
+ const teamId = requireTeamId(req?.user?.teamId);
+
+ await this.maintenanceWindowService.deleteMaintenanceWindow({ id: req.params.id, teamId });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Maintenance window deleted successfully",
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ editMaintenanceWindow = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await editMaintenanceWindowByIdParamValidation.validateAsync(req.params);
+ await editMaintenanceByIdWindowBodyValidation.validateAsync(req.body);
+
+ const teamId = requireTeamId(req.user?.teamId);
+
+ const editedMaintenanceWindow = await this.maintenanceWindowService.editMaintenanceWindow({ id: req.params.id, body: req.body, teamId });
+ return res.status(200).json({
+ success: true,
+ msg: "Maintenance window edited successfully",
+ data: editedMaintenanceWindow,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+}
+
+export default MaintenanceWindowController;
diff --git a/server/src/controllers/monitorController.ts b/server/src/controllers/monitorController.ts
new file mode 100644
index 000000000..721565f59
--- /dev/null
+++ b/server/src/controllers/monitorController.ts
@@ -0,0 +1,409 @@
+import { Request, Response, NextFunction } from "express";
+
+import {
+ getMonitorByIdParamValidation,
+ getMonitorByIdQueryValidation,
+ getMonitorsByTeamIdParamValidation,
+ getMonitorsByTeamIdQueryValidation,
+ getMonitorsWithChecksQueryValidation,
+ createMonitorBodyValidation,
+ editMonitorBodyValidation,
+ pauseMonitorParamValidation,
+ getCertificateParamValidation,
+ getHardwareDetailsByIdParamValidation,
+ getHardwareDetailsByIdQueryValidation,
+} from "@/validation/joi.js";
+import sslChecker from "ssl-checker";
+import {
+ fetchMonitorCertificate,
+ requireString,
+ optionalString,
+ optionalNumber,
+ optionalBoolean,
+ parseMonitorTypeFilter,
+ parseSortOrder,
+ requireTeamId,
+} from "./controllerUtils.js";
+import { AppError } from "@/utils/AppError.js";
+import { IMonitorService } from "@/service/index.js";
+
+const SERVICE_NAME = "monitorController";
+class MonitorController {
+ static SERVICE_NAME = SERVICE_NAME;
+
+ private monitorService: IMonitorService;
+
+ constructor(monitorService: IMonitorService) {
+ this.monitorService = monitorService;
+ }
+
+ get serviceName() {
+ return MonitorController.SERVICE_NAME;
+ }
+
+ getMonitorCertificate = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await getCertificateParamValidation.validateAsync(req.params);
+ const teamId = requireTeamId(req?.user?.teamId);
+ const monitorId = requireString(req.params?.monitorId, "Monitor ID");
+ const monitor = await this.monitorService.getMonitorById({ teamId, monitorId });
+ const certificate = await fetchMonitorCertificate(sslChecker, monitor);
+
+ return res.status(200).json({
+ success: true,
+ msg: "SSL certificate retrieved successfully",
+ data: {
+ certificateDate: new Date(certificate.validTo),
+ },
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getUptimeDetailsById = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
+ const dateRange = requireString(req?.query?.dateRange, "dateRange");
+ const normalize = optionalBoolean(req?.query?.normalize, "normalize");
+ const teamId = requireTeamId(req?.user?.teamId);
+
+ const data = await this.monitorService.getUptimeDetailsById({
+ teamId,
+ monitorId,
+ dateRange,
+ normalize,
+ });
+ return res.status(200).json({
+ success: true,
+ msg: "Uptime details retrieved successfully",
+ data: data,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getHardwareDetailsById = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await getHardwareDetailsByIdParamValidation.validateAsync(req.params);
+ await getHardwareDetailsByIdQueryValidation.validateAsync(req.query);
+
+ const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
+ const dateRange = optionalString(req?.query?.dateRange, "dateRange") || "recent";
+ const teamId = requireTeamId(req?.user?.teamId);
+
+ const monitor = await this.monitorService.getHardwareDetailsById({
+ teamId,
+ monitorId,
+ dateRange,
+ });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Hardware details retrieved successfully",
+ data: monitor,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+ getPageSpeedDetailsById = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await getHardwareDetailsByIdParamValidation.validateAsync(req.params);
+ await getHardwareDetailsByIdQueryValidation.validateAsync(req.query);
+
+ const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
+ const dateRange = requireString(req?.query?.dateRange, "dateRange");
+ const teamId = requireTeamId(req?.user?.teamId);
+
+ const data = await this.monitorService.getPageSpeedDetailsById({
+ teamId,
+ monitorId,
+ dateRange,
+ });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Page speed details retrieved successfully",
+ data,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getMonitorById = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await getMonitorByIdParamValidation.validateAsync(req.params);
+ await getMonitorByIdQueryValidation.validateAsync(req.query);
+
+ const teamId = requireTeamId(req?.user?.teamId);
+ const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
+
+ const monitor = await this.monitorService.getMonitorById({ teamId, monitorId });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Monitor retrieved successfully",
+ data: monitor,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ createMonitor = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await createMonitorBodyValidation.validateAsync(req.body);
+
+ const userId = requireString(req?.user?.id, "User ID");
+ const teamId = requireTeamId(req?.user?.teamId);
+
+ const monitor = await this.monitorService.createMonitor(teamId, userId, req.body);
+
+ return res.status(200).json({
+ success: true,
+ msg: "Monitor created successfully",
+ data: monitor,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ createBulkMonitors = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const userId = req.user?.id;
+ if (!userId) {
+ throw new AppError({ message: "User ID is required", status: 400 });
+ }
+
+ const teamId = requireTeamId(req?.user?.teamId);
+
+ const data = req.body;
+ const monitors = await this.monitorService.createBulkMonitors(data, userId, teamId);
+
+ return res.status(200).json({
+ success: true,
+ msg: "Bulk monitors created successfully",
+ data: monitors,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ deleteMonitor = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await getMonitorByIdParamValidation.validateAsync(req.params);
+ const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
+ const teamId = requireTeamId(req?.user?.teamId);
+
+ const deletedMonitor = await this.monitorService.deleteMonitor({ teamId, monitorId });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Monitor deleted successfully",
+ data: deletedMonitor,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ deleteAllMonitors = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const teamId = requireTeamId(req?.user?.teamId);
+
+ const deletedCount = await this.monitorService.deleteAllMonitors({ teamId });
+
+ return res.status(200).json({
+ success: true,
+ msg: `Deleted ${deletedCount} monitors`,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ editMonitor = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await getMonitorByIdParamValidation.validateAsync(req.params);
+ await editMonitorBodyValidation.validateAsync(req.body);
+ const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
+ const teamId = requireTeamId(req?.user?.teamId);
+
+ const editedMonitor = await this.monitorService.editMonitor({ teamId, monitorId, body: req.body });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Monitor edited successfully",
+ data: editedMonitor,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ pauseMonitor = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await pauseMonitorParamValidation.validateAsync(req.params);
+
+ const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
+ const teamId = requireTeamId(req?.user?.teamId);
+
+ const monitor = await this.monitorService.pauseMonitor({ teamId, monitorId });
+
+ return res.status(200).json({
+ success: true,
+ msg: monitor.isActive ? "Monitor resumed successfully" : "Monitor paused successfully",
+ data: monitor,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ addDemoMonitors = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const id = requireString(req?.user?.id, "User ID");
+ const teamId = requireTeamId(req?.user?.teamId);
+ const demoMonitors = await this.monitorService.addDemoMonitors({ userId: id, teamId });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Demo monitors added successfully",
+ data: demoMonitors?.length ?? 0,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ sendTestEmail = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const { to } = req.body;
+ if (!to || typeof to !== "string") {
+ throw new AppError({ message: "Invalid 'to' email address", status: 400 });
+ }
+
+ const messageId = await this.monitorService.sendTestEmail({ to });
+ return res.status(200).json({
+ success: true,
+ msg: "Test email sent successfully",
+ data: { messageId },
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getMonitorsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await getMonitorsByTeamIdParamValidation.validateAsync(req.params);
+ await getMonitorsByTeamIdQueryValidation.validateAsync(req.query);
+
+ const teamId = requireTeamId(req?.user?.teamId);
+ const type = parseMonitorTypeFilter(req.query?.type);
+ const filter = optionalString(req.query?.filter, "filter");
+
+ const monitors = await this.monitorService.getMonitorsByTeamId({ teamId, type, filter });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Monitors retrieved successfully",
+ data: monitors,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getMonitorsWithChecksByTeamId = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await getMonitorsByTeamIdParamValidation.validateAsync(req.params);
+ await getMonitorsWithChecksQueryValidation.validateAsync(req.query);
+ const explain = optionalBoolean(req?.query?.explain, "explain");
+ const limit = optionalNumber(req?.query?.limit, "limit");
+ const page = optionalNumber(req?.query?.page, "page");
+ const rowsPerPage = optionalNumber(req?.query?.rowsPerPage, "rowsPerPage");
+ const filter = optionalString(req?.query?.filter, "filter");
+ const field = optionalString(req?.query?.field, "field");
+ const order = parseSortOrder(req?.query?.order);
+ const type = parseMonitorTypeFilter(req?.query?.type);
+ const teamId = requireTeamId(req?.user?.teamId);
+
+ const monitors = await this.monitorService.getMonitorsWithChecksByTeamId({
+ teamId,
+ limit,
+ type,
+ page,
+ rowsPerPage,
+ filter,
+ field,
+ order,
+ explain,
+ });
+
+ return res.status(200).json({
+ msg: "Monitors retrieved successfully",
+ data: monitors,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ exportMonitorsToJSON = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const teamId = req?.user?.teamId;
+ if (!teamId) {
+ throw new AppError({ message: "Team ID is required", status: 400 });
+ }
+
+ const json = await this.monitorService.exportMonitorsToJSON({ teamId });
+
+ return res.status(200).json({
+ success: true,
+ msg: "Monitors exported successfully",
+ data: json,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getAllGames = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const games = this.monitorService.getAllGames();
+ return res.status(200).json({
+ success: true,
+ msg: "Supported games retrieved successfully",
+ data: games,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getGroupsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const teamId = req?.user?.teamId;
+ if (!teamId) {
+ throw new AppError({ message: "Team ID is required", status: 400 });
+ }
+
+ const groups = await this.monitorService.getGroupsByTeamId({ teamId });
+
+ return res.status(200).json({
+ msg: "Groups retrieved successfully",
+ data: groups,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+}
+
+export default MonitorController;
diff --git a/server/src/controllers/notificationController.ts b/server/src/controllers/notificationController.ts
new file mode 100644
index 000000000..1aa10a82c
--- /dev/null
+++ b/server/src/controllers/notificationController.ts
@@ -0,0 +1,188 @@
+import { Request, Response, NextFunction } from "express";
+
+import { Notification } from "@/types/index.js";
+import { createNotificationBodyValidation } from "@/validation/joi.js";
+import { AppError } from "@/utils/AppError.js";
+import { IMonitorsRepository } from "@/repositories/index.js";
+import { INotificationsService } from "@/service/index.js";
+import { requireTeamId } from "./controllerUtils.js";
+
+const SERVICE_NAME = "NotificationController";
+
+class NotificationController {
+ static SERVICE_NAME = SERVICE_NAME;
+ private notificationsService: INotificationsService;
+ private monitorsRepository: IMonitorsRepository;
+ constructor(notificationsService: INotificationsService, monitorsRepository: IMonitorsRepository) {
+ this.notificationsService = notificationsService;
+ this.monitorsRepository = monitorsRepository;
+ }
+
+ get serviceName() {
+ return NotificationController.SERVICE_NAME;
+ }
+
+ testNotification = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const notification: Notification = req.body;
+ const success = await this.notificationsService.sendTestNotification(notification);
+
+ if (!success) {
+ throw new AppError({ message: "Sending notification failed", status: 500 });
+ }
+
+ return res.status(200).json({
+ success: true,
+ msg: "Notification sent successfully",
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ createNotification = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await createNotificationBodyValidation.validateAsync(req.body, {
+ abortEarly: false,
+ });
+
+ const body = req.body;
+
+ const teamId = req?.user?.teamId;
+ if (!teamId) {
+ throw new AppError({ message: "Team ID is required", status: 400 });
+ }
+
+ const userId = req?.user?.id;
+ if (!userId) {
+ throw new AppError({ message: "User ID is required", status: 400 });
+ }
+ body.userId = userId;
+ body.teamId = teamId;
+
+ const notification = await this.notificationsService.createNotification(body);
+ return res.status(200).json({
+ success: true,
+ msg: "Notification created successfully",
+ data: notification,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getNotificationsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const teamId = req?.user?.teamId;
+ if (!teamId) {
+ throw new AppError({ message: "Team ID is required", status: 400 });
+ }
+
+ const notifications = await this.notificationsService.findNotificationsByTeamId(teamId);
+
+ return res.status(200).json({
+ success: true,
+ msg: "Notifications fetched successfully",
+ data: notifications,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ deleteNotification = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const teamId = req?.user?.teamId;
+ if (!teamId) {
+ throw new AppError({ message: "Team ID is required", status: 400 });
+ }
+
+ const notificationId = req.params.id;
+ if (!notificationId) {
+ throw new AppError({ message: "Notification ID is required", status: 400 });
+ }
+
+ await this.notificationsService.deleteById(notificationId, teamId);
+ return res.status(200).json({
+ success: true,
+ msg: "Notification deleted successfully",
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getNotificationById = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const teamId = requireTeamId(req.user?.teamId);
+ const notificationId = req.params.id;
+ if (!notificationId) {
+ throw new AppError({ message: "Notification ID is required", status: 400 });
+ }
+
+ const notification = await this.notificationsService.findById(notificationId, teamId);
+
+ return res.status(200).json({
+ success: true,
+ msg: "Notification fetched successfully",
+ data: notification,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ editNotification = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await createNotificationBodyValidation.validateAsync(req.body, {
+ abortEarly: false,
+ });
+
+ const teamId = requireTeamId(req.user?.teamId);
+ const notificationId = req.params.id;
+ if (!notificationId) {
+ throw new AppError({ message: "Notification ID is required", status: 400 });
+ }
+ const editedNotification = await this.notificationsService.updateById(notificationId, teamId, req.body);
+ return res.status(200).json({
+ success: true,
+ msg: "Notification updated successfully",
+ data: editedNotification,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ testAllNotifications = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const monitorId = req.body.monitorId;
+ const teamId = req?.user?.teamId;
+ if (!teamId) {
+ throw new AppError({ message: "Team ID is required", status: 400 });
+ }
+
+ const monitor = await this.monitorsRepository.findById(monitorId, teamId);
+
+ const notifications = monitor.notifications;
+ if (notifications.length === 0) {
+ throw new AppError({ message: "No notifications", status: 400 });
+ }
+
+ const result = await this.notificationsService.testAllNotifications(notifications);
+
+ if (!result) {
+ throw new AppError({ message: "Failed to send all notifications", status: 500 });
+ }
+
+ return res.status(200).json({
+ success: true,
+ msg: "All notifications sent successfully",
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+}
+
+export default NotificationController;
diff --git a/server/src/controllers/queueController.ts b/server/src/controllers/queueController.ts
new file mode 100755
index 000000000..9885bab87
--- /dev/null
+++ b/server/src/controllers/queueController.ts
@@ -0,0 +1,94 @@
+import { Request, Response, NextFunction } from "express";
+
+const SERVICE_NAME = "JobQueueController";
+
+class JobQueueController {
+ static SERVICE_NAME = SERVICE_NAME;
+ private jobQueue: any;
+ constructor(jobQueue: any) {
+ this.jobQueue = jobQueue;
+ }
+
+ get serviceName() {
+ return JobQueueController.SERVICE_NAME;
+ }
+
+ getMetrics = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const metrics = await this.jobQueue.getMetrics();
+ res.status(200).json({
+ success: true,
+ msg: "Queue metrics fetched successfully",
+ data: metrics,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getJobs = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const jobs = await this.jobQueue.getJobs();
+ return res.status(200).json({
+ success: true,
+ msg: "Queue jobs fetched successfully",
+ data: jobs,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getAllMetrics = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const jobs = await this.jobQueue.getJobs();
+ const metrics = await this.jobQueue.getMetrics();
+ return res.status(200).json({
+ success: true,
+ msg: "Queue metrics fetched successfully",
+ data: { jobs, metrics },
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ addJob = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await this.jobQueue.addJob(Math.random().toString(36).substring(7));
+ return res.status(200).json({
+ success: true,
+ msg: "Job added to queue successfully",
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ flushQueue = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const result = await this.jobQueue.flushQueues();
+ return res.status(200).json({
+ success: true,
+ msg: "Queue flushed successfully",
+ data: result,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ checkQueueHealth = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const stuckQueues = await this.jobQueue.checkQueueHealth();
+ return res.status(200).json({
+ success: true,
+ msg: "Queue health checked successfully",
+ data: stuckQueues,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+}
+export default JobQueueController;
diff --git a/server/src/controllers/v1/settingsController.js b/server/src/controllers/settingsController.ts
similarity index 57%
rename from server/src/controllers/v1/settingsController.js
rename to server/src/controllers/settingsController.ts
index 3db73a05a..0a01daa0c 100644
--- a/server/src/controllers/v1/settingsController.js
+++ b/server/src/controllers/settingsController.ts
@@ -1,28 +1,32 @@
-import { updateAppSettingsBodyValidation } from "../../validation/joi.js";
-import { sendTestEmailBodyValidation } from "../../validation/joi.js";
-import BaseController from "./baseController.js";
+import { Request, Response, NextFunction } from "express";
+import { sendTestEmailBodyValidation, updateAppSettingsBodyValidation } from "@/validation/joi.js";
+import { AppError } from "@/utils/AppError.js";
const SERVICE_NAME = "SettingsController";
-class SettingsController extends BaseController {
+class SettingsController {
static SERVICE_NAME = SERVICE_NAME;
- constructor(commonDependencies, { settingsService, emailService }) {
- super(commonDependencies);
+ private settingsService: any;
+ private emailService: any;
+ private db: any;
+ constructor(settingsService: any, emailService: any, db: any) {
this.settingsService = settingsService;
this.emailService = emailService;
+ this.db = db;
}
get serviceName() {
return SettingsController.SERVICE_NAME;
}
- buildAppSettings = (dbSettings) => {
+ buildAppSettings = (dbSettings: any) => {
const sanitizedSettings = { ...dbSettings };
delete sanitizedSettings.version;
-
+ delete sanitizedSettings.jwtSecret;
const returnSettings = {
pagespeedKeySet: false,
emailPasswordSet: false,
+ settings: null,
};
if (typeof sanitizedSettings.pagespeedApiKey !== "undefined") {
@@ -37,37 +41,35 @@ class SettingsController extends BaseController {
return returnSettings;
};
- getAppSettings = this.asyncHandler(
- async (req, res) => {
+ getAppSettings = async (req: Request, res: Response, next: NextFunction) => {
+ try {
const dbSettings = await this.settingsService.getDBSettings();
const returnSettings = this.buildAppSettings(dbSettings);
- return res.success({
- msg: this.stringService.getAppSettings,
+ return res.status(200).json({
+ success: true,
+ msg: "App settings fetched successfully",
data: returnSettings,
});
- },
- SERVICE_NAME,
- "getAppSettings"
- );
+ } catch (error) {
+ next(error);
+ }
+ };
- updateAppSettings = this.asyncHandler(
- async (req, res) => {
- await updateAppSettingsBodyValidation.validateAsync(req.body);
+ updateAppSettings = async (req: Request, res: Response, next: NextFunction) => {
+ await updateAppSettingsBodyValidation.validateAsync(req.body);
- const updatedSettings = await this.db.settingsModule.updateAppSettings(req.body);
- const returnSettings = this.buildAppSettings(updatedSettings);
- return res.success({
- msg: this.stringService.updateAppSettings,
- data: returnSettings,
- });
- },
- SERVICE_NAME,
- "updateAppSettings"
- );
+ const updatedSettings = await this.settingsService.updateDbSettings(req.body);
+ const returnSettings = this.buildAppSettings(updatedSettings);
+ return res.status(200).json({
+ success: true,
+ msg: "App settings updated successfully",
+ data: returnSettings,
+ });
+ };
- sendTestEmail = this.asyncHandler(
- async (req, res) => {
+ sendTestEmail = async (req: Request, res: Response, next: NextFunction) => {
+ try {
await sendTestEmailBodyValidation.validateAsync(req.body);
const {
@@ -86,7 +88,7 @@ class SettingsController extends BaseController {
systemEmailTLSServername,
} = req.body;
- const subject = this.stringService.testEmailSubject;
+ const subject = "This is a test email from Checkmate";
const context = { testName: "Monitoring System" };
const html = await this.emailService.buildEmail("testEmailTemplate", context);
@@ -106,17 +108,18 @@ class SettingsController extends BaseController {
});
if (!messageId) {
- throw this.errorService.createServerError("Failed to send test email.");
+ throw new AppError({ message: "Failed to send test email.", status: 500 });
}
- return res.success({
- msg: this.stringService.sendTestEmail,
+ return res.status(200).json({
+ success: true,
+ msg: "Test email sent successfully",
data: { messageId },
});
- },
- SERVICE_NAME,
- "sendTestEmail"
- );
+ } catch (error) {
+ next(error);
+ }
+ };
}
export default SettingsController;
diff --git a/server/src/controllers/statusPageController.ts b/server/src/controllers/statusPageController.ts
new file mode 100644
index 000000000..866dd715a
--- /dev/null
+++ b/server/src/controllers/statusPageController.ts
@@ -0,0 +1,135 @@
+import { Request, Response, NextFunction } from "express";
+
+import { createStatusPageBodyValidation, getStatusPageParamValidation, getStatusPageQueryValidation, imageValidation } from "@/validation/joi.js";
+import { AppError } from "@/utils/AppError.js";
+import { requireTeamId, requireUserId } from "@/controllers/controllerUtils.js";
+import { IStatusPageService } from "@/service/business/statusPageService.js";
+import { IMonitorsRepository } from "@/repositories/index.js";
+import { ISettingsService } from "@/service/system/settingsService.js";
+import { ParseBoolean } from "@/utils/utils.js";
+import { NormalizeData } from "@/utils/dataUtils.js";
+
+const SERVICE_NAME = "statusPageController";
+
+class StatusPageController {
+ static SERVICE_NAME = SERVICE_NAME;
+ private statusPageService: IStatusPageService;
+ private monitorsRepository: IMonitorsRepository;
+ private settingsService: ISettingsService;
+ constructor(statusPageService: IStatusPageService, monitorsRepository: IMonitorsRepository, settingsService: ISettingsService) {
+ this.statusPageService = statusPageService;
+ this.monitorsRepository = monitorsRepository;
+ this.settingsService = settingsService;
+ }
+
+ get serviceName() {
+ return StatusPageController.SERVICE_NAME;
+ }
+
+ createStatusPage = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await createStatusPageBodyValidation.validateAsync(req.body);
+ await imageValidation.validateAsync(req.file);
+
+ const teamId = requireTeamId(req?.user?.teamId);
+ const userId = requireUserId(req?.user?.id);
+ const statusPage = await this.statusPageService.createStatusPage(userId, teamId, req.file, req.body);
+
+ return res.status(200).json({
+ success: true,
+ msg: "Status page created successfully",
+ data: statusPage,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ updateStatusPage = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await createStatusPageBodyValidation.validateAsync(req.body);
+ await imageValidation.validateAsync(req.file);
+ const teamId = requireTeamId(req?.user?.teamId);
+ const statusPageId = req.params.id;
+ if (!statusPageId) {
+ throw new AppError({ message: "Status page ID is required", status: 400 });
+ }
+ const statusPage = await this.statusPageService.updateStatusPage(statusPageId, teamId, req.file, req.body);
+ if (statusPage === null) {
+ throw new AppError({ message: "Status page not found", status: 404 });
+ }
+ return res.status(200).json({
+ success: true,
+ msg: "Status page updated successfully",
+ data: statusPage,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ getStatusPageByUrl = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ await getStatusPageParamValidation.validateAsync(req.params);
+ await getStatusPageQueryValidation.validateAsync(req.query);
+
+ if (!req.params.url) {
+ throw new AppError({ message: "Status page URL is required", status: 400 });
+ }
+
+ const statusPage = await this.statusPageService.getStatusPageByUrl(req.params.url);
+ const settings = await this.settingsService.getDBSettings();
+ const showURL = settings.showURL;
+
+ const monitors = await this.monitorsRepository.findByIdsWithChecks(statusPage.monitors);
+ const normalizedMonitors = monitors.map((monitor) => {
+ const normalizedChecks = NormalizeData(monitor.recentChecks, 10, 100);
+ if (!showURL) {
+ const { url, port, secret, notifications, ...rest } = monitor;
+ return { ...rest, checks: normalizedChecks };
+ }
+ return { ...monitor, checks: normalizedChecks };
+ });
+ return res.status(200).json({
+ success: true,
+ msg: "Status page retrieved successfully",
+ data: { statusPage, monitors: normalizedMonitors },
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+ getStatusPagesByTeamId = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const teamId = requireTeamId(req.user?.teamId);
+ const statusPages = await this.statusPageService.getStatusPagesByTeamId(teamId);
+
+ return res.status(200).json({
+ success: true,
+ msg: "Status pages retrieved successfully",
+ data: statusPages,
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+
+ deleteStatusPage = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const statusPageId = req.params.id;
+ if (!statusPageId) {
+ throw new AppError({ message: "Status page ID is required", status: 400 });
+ }
+ const teamId = requireTeamId(req.user?.teamId);
+ await this.statusPageService.deleteStatusPage(statusPageId, teamId);
+ return res.status(200).json({
+ success: true,
+ msg: "Status page deleted successfully",
+ });
+ } catch (error) {
+ next(error);
+ }
+ };
+}
+
+export default StatusPageController;
diff --git a/server/src/controllers/v1/announcementsController.js b/server/src/controllers/v1/announcementsController.js
deleted file mode 100755
index 0425b44aa..000000000
--- a/server/src/controllers/v1/announcementsController.js
+++ /dev/null
@@ -1,77 +0,0 @@
-import { createAnnouncementValidation } from "../../validation/joi.js";
-import BaseController from "./baseController.js";
-
-const SERVICE_NAME = "announcementController";
-
-/**
- * Controller for managing announcements in the system.
- * This class handles the creation of new announcements.
- *
- * @class AnnouncementController
- */
-
-class AnnouncementController extends BaseController {
- static SERVICE_NAME = SERVICE_NAME;
- constructor(commonDependencies) {
- super(commonDependencies);
- this.createAnnouncement = this.createAnnouncement.bind(this);
- this.getAnnouncement = this.getAnnouncement.bind(this);
- }
-
- get serviceName() {
- return AnnouncementController.SERVICE_NAME;
- }
-
- /**
- * Handles the creation of a new announcement.
- *
- * @async
- * @param {Object} req - The request object, containing the announcement data in the body.
- * @param {Object} res - The response object used to send the result back to the client.
- * @param {Function} next - The next middleware function in the stack for error handling.
- *
- * @returns {Promise} A promise that resolves once the response is sent.
- */
- createAnnouncement = asyncHandler(
- async (req, res, next) => {
- await createAnnouncementValidation.validateAsync(req.body);
- const { title, message } = req.body;
- const announcementData = {
- title: title.trim(),
- message: message.trim(),
- userId: req.user._id,
- };
-
- const newAnnouncement = await this.db.createAnnouncement(announcementData);
- return res.success({
- msg: this.stringService.createAnnouncement,
- data: newAnnouncement,
- });
- },
- SERVICE_NAME,
- "createAnnouncement"
- );
-
- /**
- * Handles retrieving announcements with pagination.
- *
- * @async
- * @param {Object} res - The response object used to send the result back to the client.
- * - `data`: The list of announcements to be sent back to the client.
- * - `msg`: A message about the success of the request.
- * @param {Function} next - The next middleware function in the stack for error handling.
- */
- getAnnouncement = asyncHandler(
- async (req, res, next) => {
- const allAnnouncements = await this.db.getAnnouncements();
- return res.success({
- msg: this.stringService.getAnnouncement,
- data: allAnnouncements,
- });
- },
- SERVICE_NAME,
- "getAnnouncement"
- );
-}
-
-export default AnnouncementController;
diff --git a/server/src/controllers/v1/authController.js b/server/src/controllers/v1/authController.js
deleted file mode 100755
index e52de3849..000000000
--- a/server/src/controllers/v1/authController.js
+++ /dev/null
@@ -1,478 +0,0 @@
-import BaseController from "./baseController.js";
-import {
- registrationBodyValidation,
- loginValidation,
- editUserBodyValidation,
- recoveryValidation,
- recoveryTokenBodyValidation,
- newPasswordValidation,
- getUserByIdParamValidation,
- editUserByIdParamValidation,
- editUserByIdBodyValidation,
- editSuperadminUserByIdBodyValidation,
- editUserPasswordByIdBodyValidation,
-} from "../../validation/joi.js";
-
-const SERVICE_NAME = "authController";
-/**
- * Authentication Controller
- *
- * Handles all authentication-related HTTP requests including user registration,
- * login, password recovery, and user management operations.
- *
- * @class AuthController
- * @description Manages user authentication and authorization operations
- */
-class AuthController extends BaseController {
- static SERVICE_NAME = SERVICE_NAME;
- /**
- * Creates an instance of AuthController.
- *
- * @param {Object} commonDependencies - Common dependencies injected into the controller
- * @param {Object} dependencies - The dependencies required by the controller
- * @param {Object} dependencies.settingsService - Service for application settings
- * @param {Object} dependencies.emailService - Service for email operations
- * @param {Object} dependencies.jobQueue - Service for job queue operations
- * @param {Object} dependencies.userService - User business logic service
- */
- constructor(commonDependencies, { settingsService, emailService, jobQueue, userService }) {
- super(commonDependencies);
- this.settingsService = settingsService;
- this.emailService = emailService;
- this.jobQueue = jobQueue;
- this.userService = userService;
- }
-
- get serviceName() {
- return AuthController.SERVICE_NAME;
- }
-
- /**
- * Registers a new user in the system.
- *
- * @async
- * @function registerUser
- * @param {Object} req - Express request object
- * @param {Object} req.body - Request body containing user registration data
- * @param {string} req.body.firstName - User's first name
- * @param {string} req.body.lastName - User's last name
- * @param {string} req.body.email - User's email address (will be converted to lowercase)
- * @param {string} req.body.password - User's password
- * @param {string} [req.body.inviteToken] - Invite token for registration (required if superadmin exists)
- * @param {string} [req.body.teamId] - Team ID (auto-assigned if superadmin)
- * @param {Array} [req.body.role] - User roles (auto-assigned if superadmin)
- * @param {Object} [req.file] - Profile image file uploaded via multer
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with user data and JWT token
- * @throws {Error} 422 - Validation error if request body is invalid
- * @throws {Error} 409 - Conflict if user already exists
- * @example
- * // Register first user (becomes superadmin)
- * POST /auth/register
- * {
- * "firstName": "John",
- * "lastName": "Doe",
- * "email": "john@example.com",
- * "password": "SecurePass123!"
- * }
- *
- * // Register subsequent user (requires invite token)
- * POST /auth/register
- * {
- * "firstName": "Jane",
- * "lastName": "Smith",
- * "email": "jane@example.com",
- * "password": "SecurePass123!",
- * "inviteToken": "abc123..."
- * }
- */
- registerUser = this.asyncHandler(
- async (req, res) => {
- if (req.body?.email) {
- req.body.email = req.body.email?.toLowerCase();
- }
- await registrationBodyValidation.validateAsync(req.body);
- const { user, token } = await this.userService.registerUser(req.body, req.file);
- res.success({
- msg: this.stringService.authCreateUser,
- data: { user, token },
- });
- },
- SERVICE_NAME,
- "registerUser"
- );
-
- /**
- * Authenticates a user and returns a JWT token.
- *
- * @async
- * @function loginUser
- * @param {Object} req - Express request object
- * @param {Object} req.body - Request body containing login credentials
- * @param {string} req.body.email - User's email address (will be converted to lowercase)
- * @param {string} req.body.password - User's password
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with user data and JWT token
- * @throws {Error} 422 - Validation error if request body is invalid
- * @throws {Error} 401 - Unauthorized if credentials are incorrect
- * @example
- * POST /auth/login
- * {
- * "email": "john@example.com",
- * "password": "SecurePass123!"
- * }
- */
- loginUser = this.asyncHandler(
- async (req, res) => {
- if (req.body?.email) {
- req.body.email = req.body.email?.toLowerCase();
- }
- await loginValidation.validateAsync(req.body);
- const { user, token } = await this.userService.loginUser(req.body.email, req.body.password);
-
- return res.success({
- msg: this.stringService.authLoginUser,
- data: {
- user,
- token,
- },
- });
- },
- SERVICE_NAME,
- "loginUser"
- );
-
- /**
- * Updates the current user's profile information.
- *
- * @async
- * @function editUser
- * @param {Object} req - Express request object
- * @param {Object} req.body - Request body containing user update data
- * @param {string} [req.body.firstName] - Updated first name
- * @param {string} [req.body.lastName] - Updated last name
- * @param {string} [req.body.password] - Current password (required for password change)
- * @param {string} [req.body.newPassword] - New password (required for password change)
- * @param {boolean} [req.body.deleteProfileImage] - Flag to delete profile image
- * @param {Object} [req.file] - New profile image file
- * @param {Object} req.user - Current authenticated user (from JWT)
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with updated user data
- * @throws {Error} 422 - Validation error if request body is invalid
- * @throws {Error} 403 - Forbidden if current password is incorrect
- * @example
- * PUT /auth/user
- * {
- * "firstName": "John Updated",
- * "lastName": "Doe Updated"
- * }
- *
- * // Change password
- * PUT /auth/user
- * {
- * "password": "OldPass123!",
- * "newPassword": "NewPass123!"
- * }
- */
- editUser = this.asyncHandler(
- async (req, res) => {
- await editUserBodyValidation.validateAsync(req.body);
-
- const updatedUser = await this.userService.editUser(req.body, req.file, req.user);
-
- res.success({
- msg: this.stringService.authUpdateUser,
- data: updatedUser,
- });
- },
- SERVICE_NAME,
- "editUser"
- );
-
- /**
- * Checks if a superadmin account exists in the system.
- *
- * @async
- * @function checkSuperadminExists
- * @param {Object} req - Express request object
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with boolean indicating superadmin existence
- * @example
- * GET /auth/users/superadmin
- * // Response: { "data": true } or { "data": false }
- */
- checkSuperadminExists = this.asyncHandler(
- async (req, res) => {
- const superAdminExists = await this.userService.checkSuperadminExists();
- return res.success({
- msg: this.stringService.authAdminExists,
- data: superAdminExists,
- });
- },
- SERVICE_NAME,
- "checkSuperadminExists"
- );
-
- /**
- * Initiates password recovery process by sending a recovery email.
- *
- * @async
- * @function requestRecovery
- * @param {Object} req - Express request object
- * @param {Object} req.body - Request body containing email
- * @param {string} req.body.email - Email address for password recovery
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with message ID
- * @throws {Error} 422 - Validation error if email is invalid
- * @throws {Error} 404 - Not found if user doesn't exist
- * @example
- * POST /auth/recovery/request
- * {
- * "email": "john@example.com"
- * }
- */
- requestRecovery = this.asyncHandler(
- async (req, res) => {
- await recoveryValidation.validateAsync(req.body);
- const email = req?.body?.email;
- const msgId = await this.userService.requestRecovery(email);
- return res.success({
- msg: this.stringService.authCreateRecoveryToken,
- data: msgId,
- });
- },
- SERVICE_NAME,
- "requestRecovery"
- );
-
- /**
- * Validates a password recovery token.
- *
- * @async
- * @function validateRecovery
- * @param {Object} req - Express request object
- * @param {Object} req.body - Request body containing recovery token
- * @param {string} req.body.recoveryToken - Recovery token to validate
- * @param {Object} res - Express response object
- * @returns {Promise} Success response if token is valid
- * @throws {Error} 422 - Validation error if token format is invalid
- * @throws {Error} 400 - Bad request if token is invalid or expired
- * @example
- * POST /auth/recovery/validate
- * {
- * "recoveryToken": "abc123..."
- * }
- */
- validateRecovery = this.asyncHandler(
- async (req, res) => {
- await recoveryTokenBodyValidation.validateAsync(req.body);
- await this.userService.validateRecovery(req.body.recoveryToken);
- return res.success({
- msg: this.stringService.authVerifyRecoveryToken,
- });
- },
- SERVICE_NAME,
- "validateRecovery"
- );
-
- /**
- * Resets user password using a valid recovery token.
- *
- * @async
- * @function resetPassword
- * @param {Object} req - Express request object
- * @param {Object} req.body - Request body containing new password and recovery token
- * @param {string} req.body.password - New password
- * @param {string} req.body.recoveryToken - Valid recovery token
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with user data and JWT token
- * @throws {Error} 422 - Validation error if password format is invalid
- * @throws {Error} 400 - Bad request if token is invalid or expired
- * @example
- * POST /auth/recovery/reset
- * {
- * "password": "NewSecurePass123!",
- * "recoveryToken": "abc123..."
- * }
- */
- resetPassword = this.asyncHandler(
- async (req, res) => {
- await newPasswordValidation.validateAsync(req.body);
- const { user, token } = await this.userService.resetPassword(req.body.password, req.body.recoveryToken);
- return res.success({
- msg: this.stringService.authResetPassword,
- data: { user, token },
- });
- },
- SERVICE_NAME,
- "resetPassword"
- );
-
- /**
- * Deletes the current user's account and associated data.
- *
- * @async
- * @function deleteUser
- * @param {Object} req - Express request object
- * @param {Object} req.user - Current authenticated user (from JWT)
- * @param {string} req.user._id - User ID
- * @param {string} req.user.email - User email
- * @param {string} req.user.teamId - User's team ID
- * @param {Array} req.user.role - User roles
- * @param {Object} res - Express response object
- * @returns {Promise} Success response confirming user deletion
- * @throws {Error} 400 - Bad request if user is demo user
- * @throws {Error} 404 - Not found if user doesn't exist
- * @example
- * DELETE /auth/user
- * // Requires JWT authentication
- */
- deleteUser = this.asyncHandler(
- async (req, res) => {
- await this.userService.deleteUser(req.user);
- return res.success({
- msg: this.stringService.authDeleteUser,
- });
- },
- SERVICE_NAME,
- "deleteUser"
- );
-
- /**
- * Retrieves all users in the system (admin/superadmin only).
- *
- * @async
- * @function getAllUsers
- * @param {Object} req - Express request object
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with array of users
- * @throws {Error} 403 - Forbidden if user doesn't have admin/superadmin role
- * @example
- * GET /auth/users
- * // Requires JWT authentication with admin/superadmin role
- */
- getAllUsers = this.asyncHandler(
- async (req, res) => {
- const allUsers = await this.userService.getAllUsers();
- return res.success({
- msg: this.stringService.authGetAllUsers,
- data: allUsers,
- });
- },
- SERVICE_NAME,
- "getAllUsers"
- );
-
- /**
- * Retrieves a specific user by ID (superadmin only).
- *
- * @async
- * @function getUserById
- * @param {Object} req - Express request object
- * @param {Object} req.params - URL parameters
- * @param {string} req.params.userId - ID of the user to retrieve
- * @param {Object} req.user - Current authenticated user (from JWT)
- * @param {Array} req.user.role - Current user's roles
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with user data
- * @throws {Error} 422 - Validation error if userId is invalid
- * @throws {Error} 403 - Forbidden if user doesn't have superadmin role
- * @throws {Error} 404 - Not found if user doesn't exist
- * @example
- * GET /auth/users/507f1f77bcf86cd799439011
- * // Requires JWT authentication with superadmin role
- */
- getUserById = this.asyncHandler(
- async (req, res) => {
- await getUserByIdParamValidation.validateAsync(req.params);
- const userId = req?.params?.userId;
- const roles = req?.user?.role;
-
- if (!userId) {
- throw new Error("No user ID in request");
- }
-
- if (!roles || roles.length === 0) {
- throw new Error("No roles in request");
- }
-
- const user = await this.userService.getUserById(roles, userId);
-
- return res.success({ msg: "ok", data: user });
- },
- SERVICE_NAME,
- "getUserById"
- );
-
- /**
- * Updates a specific user by ID (superadmin only).
- *
- * @async
- * @function editUserById
- * @param {Object} req - Express request object
- * @param {Object} req.params - URL parameters
- * @param {string} req.params.userId - ID of the user to update
- * @param {Object} req.body - Request body containing user update data
- * @param {string} [req.body.firstName] - Updated first name
- * @param {string} [req.body.lastName] - Updated last name
- * @param {Array} [req.body.role] - Updated user roles
- * @param {Object} req.user - Current authenticated user (from JWT)
- * @param {string} req.user._id - Current user's ID
- * @param {Array} req.user.role - Current user's roles
- * @param {Object} res - Express response object
- * @returns {Promise} Success response confirming user update
- * @throws {Error} 422 - Validation error if parameters or body are invalid
- * @throws {Error} 403 - Forbidden if user doesn't have superadmin role
- * @throws {Error} 404 - Not found if user doesn't exist
- * @example
- * PUT /auth/users/507f1f77bcf86cd799439011
- * {
- * "firstName": "Updated Name",
- * "role": ["admin"]
- * }
- * // Requires JWT authentication with superadmin role
- */
- editUserById = this.asyncHandler(
- async (req, res) => {
- const roles = req?.user?.role;
- if (!roles.includes("superadmin")) {
- throw this.errorService.createError("Unauthorized", 403);
- }
-
- const userId = req.params.userId;
- const user = { ...req.body };
-
- await editUserByIdParamValidation.validateAsync(req.params);
- // If this is superadmin self edit, allow "superadmin" role
- if (userId === req.user._id) {
- await editSuperadminUserByIdBodyValidation.validateAsync(req.body);
- } else {
- await editUserByIdBodyValidation.validateAsync(req.body);
- }
-
- await this.userService.editUserById(userId, user);
- return res.success({ msg: "ok" });
- },
- SERVICE_NAME,
- "editUserById"
- );
- editUserPasswordById = this.asyncHandler(
- async (req, res) => {
- const roles = req?.user?.role;
- if (!roles.includes("superadmin")) {
- throw this.errorService.createError("Unauthorized", 403);
- }
-
- const userId = req.params.userId;
- await editUserByIdParamValidation.validateAsync(req.params);
- await editUserPasswordByIdBodyValidation.validateAsync(req.body);
- const updatedPassword = req.body.password;
- await this.userService.setPasswordByUserId(userId, updatedPassword);
- return res.success({ msg: "Password reset successfully" });
- },
- SERVICE_NAME,
- "editUserPasswordById"
- );
-}
-
-export default AuthController;
diff --git a/server/src/controllers/v1/baseController.js b/server/src/controllers/v1/baseController.js
deleted file mode 100644
index e8612cec5..000000000
--- a/server/src/controllers/v1/baseController.js
+++ /dev/null
@@ -1,83 +0,0 @@
-import { AppError } from "../../service/v1/infrastructure/errorService.js";
-
-export const createCommonDependencies = (db, errorService, logger, stringService) => {
- return {
- db,
- errorService,
- logger,
- stringService,
- };
-};
-
-class BaseController {
- constructor({ db, logger, errorService, ...additionalDependencies }) {
- this.db = db;
- this.logger = logger;
- this.errorService = errorService;
- Object.assign(this, additionalDependencies);
-
- this.asyncHandler = (fn, serviceName, methodName) => {
- return async (req, res, next) => {
- try {
- await fn(req, res, next);
- } catch (error) {
- // Handle validation errors
- if (error.isJoi) {
- const validationError = this.errorService.createValidationError(error.message, error.details, serviceName, methodName);
- return next(validationError);
- }
-
- if (error.name === "ValidationError") {
- const validationError = this.errorService.createValidationError("Database validation failed", error.errors, serviceName, methodName);
- return next(validationError);
- }
-
- if (error.name === "CastError") {
- const notFoundError = this.errorService.createNotFoundError(
- "Invalid resource identifier",
- { field: error.path, value: error.value },
- serviceName,
- methodName
- );
- return next(notFoundError);
- }
-
- if (error.code === "11000") {
- const conflictError = this.errorService.createConflictError("Resource already exists", {
- originalError: error.message,
- code: error.code,
- });
- conflictError.service = serviceName;
- conflictError.method = methodName;
- return next(conflictError);
- }
-
- if (error instanceof AppError) {
- error.service = error.service || serviceName;
- error.method = error.method || methodName;
- return next(error);
- }
-
- if (error.status) {
- const appError = this.errorService.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 = this.errorService.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);
- }
- };
- };
- }
-}
-export default BaseController;
diff --git a/server/src/controllers/v1/checkController.js b/server/src/controllers/v1/checkController.js
deleted file mode 100755
index ea293d74d..000000000
--- a/server/src/controllers/v1/checkController.js
+++ /dev/null
@@ -1,360 +0,0 @@
-import BaseController from "./baseController.js";
-import {
- getChecksParamValidation,
- getChecksQueryValidation,
- getTeamChecksQueryValidation,
- deleteChecksParamValidation,
- deleteChecksByTeamIdParamValidation,
- updateChecksTTLBodyValidation,
- ackCheckBodyValidation,
- ackAllChecksParamValidation,
- ackAllChecksBodyValidation,
-} from "../../validation/joi.js";
-
-const SERVICE_NAME = "checkController";
-
-/**
- * Check Controller
- *
- * Handles all check-related HTTP requests including retrieving checks,
- * acknowledging checks, deleting checks, and managing check TTL settings.
- *
- * @class CheckController
- * @description Manages check operations and monitoring data
- */
-class CheckController extends BaseController {
- static SERVICE_NAME = SERVICE_NAME;
- /**
- * Creates an instance of CheckController.
- *
- * @param {Object} commonDependencies - Common dependencies injected into the controller
- * @param {Object} dependencies - The dependencies required by the controller
- * @param {Object} dependencies.settingsService - Service for application settings
- * @param {Object} dependencies.checkService - Check business logic service
- */
- constructor(commonDependencies, { settingsService, checkService }) {
- super(commonDependencies);
- this.settingsService = settingsService;
- this.checkService = checkService;
- }
-
- get serviceName() {
- return CheckController.SERVICE_NAME;
- }
-
- /**
- * Retrieves checks for a specific monitor with filtering and pagination.
- *
- * @async
- * @function getChecksByMonitor
- * @param {Object} req - Express request object
- * @param {Object} req.params - URL parameters
- * @param {string} req.params.monitorId - ID of the monitor to get checks for
- * @param {Object} req.query - Query parameters for filtering and pagination
- * @param {string} [req.query.type] - Type of checks to filter by
- * @param {string} [req.query.sortOrder] - Sort order (asc/desc)
- * @param {string} [req.query.dateRange] - Date range filter
- * @param {string} [req.query.filter] - General filter string
- * @param {boolean} [req.query.ack] - Filter by acknowledgment status
- * @param {number} [req.query.page] - Page number for pagination
- * @param {number} [req.query.rowsPerPage] - Number of rows per page
- * @param {string} [req.query.status] - Filter by check status
- * @param {Object} req.user - Current authenticated user (from JWT)
- * @param {string} req.user.teamId - User's team ID
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with checks data
- * @throws {Error} 422 - Validation error if parameters are invalid
- * @throws {Error} 404 - Not found if monitor doesn't exist
- * @throws {Error} 403 - Forbidden if user doesn't have access to monitor
- * @example
- * GET /checks/monitor/507f1f77bcf86cd799439011?page=1&rowsPerPage=10&status=down
- * // Requires JWT authentication
- */
- getChecksByMonitor = this.asyncHandler(
- async (req, res) => {
- await getChecksParamValidation.validateAsync(req.params);
- await getChecksQueryValidation.validateAsync(req.query);
-
- const result = await this.checkService.getChecksByMonitor({
- monitorId: req?.params?.monitorId,
- query: req?.query,
- teamId: req?.user?.teamId,
- });
-
- return res.success({
- msg: this.stringService.checkGet,
- data: result,
- });
- },
- SERVICE_NAME,
- "getChecksByMonitor"
- );
-
- /**
- * Retrieves all checks for the current user's team with filtering and pagination.
- *
- * @async
- * @function getChecksByTeam
- * @param {Object} req - Express request object
- * @param {Object} req.query - Query parameters for filtering and pagination
- * @param {string} [req.query.sortOrder] - Sort order (asc/desc)
- * @param {string} [req.query.dateRange] - Date range filter
- * @param {string} [req.query.filter] - General filter string
- * @param {boolean} [req.query.ack] - Filter by acknowledgment status
- * @param {number} [req.query.page] - Page number for pagination
- * @param {number} [req.query.rowsPerPage] - Number of rows per page
- * @param {Object} req.user - Current authenticated user (from JWT)
- * @param {string} req.user.teamId - User's team ID
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with checks data
- * @throws {Error} 422 - Validation error if query parameters are invalid
- * @example
- * GET /checks/team?page=1&rowsPerPage=20&status=down&ack=false
- * // Requires JWT authentication
- */
- getChecksByTeam = this.asyncHandler(
- async (req, res) => {
- await getTeamChecksQueryValidation.validateAsync(req.query);
- const checkData = await this.checkService.getChecksByTeam({
- teamId: req?.user?.teamId,
- query: req?.query,
- });
- return res.success({
- msg: this.stringService.checkGet,
- data: checkData,
- });
- },
- SERVICE_NAME,
- "getChecksByTeam"
- );
-
- /**
- * Retrieves a summary of checks for the current user's team.
- *
- * @async
- * @function getChecksSummaryByTeamId
- * @param {Object} req - Express request object
- * @param {Object} req.user - Current authenticated user (from JWT)
- * @param {string} req.user.teamId - User's team ID
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with checks summary
- * @example
- * GET /checks/summary
- * // Requires JWT authentication
- * // Response includes counts by status, time ranges, etc.
- */
- getChecksSummaryByTeamId = this.asyncHandler(
- async (req, res) => {
- const summary = await this.checkService.getChecksSummaryByTeamId({ teamId: req?.user?.teamId });
- return res.success({
- msg: this.stringService.checkGetSummary,
- data: summary,
- });
- },
- SERVICE_NAME,
- "getChecksSummaryByTeamId"
- );
-
- /**
- * Acknowledges a specific check by ID.
- *
- * @async
- * @function ackCheck
- * @param {Object} req - Express request object
- * @param {Object} req.params - URL parameters
- * @param {string} req.params.checkId - ID of the check to acknowledge
- * @param {Object} req.body - Request body containing acknowledgment data
- * @param {boolean} req.body.ack - Acknowledgment status (true/false)
- * @param {Object} req.user - Current authenticated user (from JWT)
- * @param {string} req.user.teamId - User's team ID
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with updated check data
- * @throws {Error} 422 - Validation error if request body is invalid
- * @throws {Error} 404 - Not found if check doesn't exist
- * @throws {Error} 403 - Forbidden if user doesn't have access to check
- * @example
- * PUT /checks/507f1f77bcf86cd799439011/ack
- * {
- * "ack": true
- * }
- * // Requires JWT authentication
- */
- ackCheck = this.asyncHandler(
- async (req, res) => {
- await ackCheckBodyValidation.validateAsync(req.body);
-
- const updatedCheck = await this.checkService.ackCheck({
- checkId: req?.params?.checkId,
- teamId: req?.user?.teamId,
- ack: req?.body?.ack,
- });
-
- return res.success({
- msg: this.stringService.checkUpdateStatus,
- data: updatedCheck,
- });
- },
- SERVICE_NAME,
- "ackCheck"
- );
-
- /**
- * Acknowledges all checks for a specific monitor or path.
- *
- * @async
- * @function ackAllChecks
- * @param {Object} req - Express request object
- * @param {Object} req.params - URL parameters
- * @param {string} req.params.monitorId - ID of the monitor
- * @param {string} req.params.path - Path for acknowledgment (e.g., "monitor")
- * @param {Object} req.body - Request body containing acknowledgment data
- * @param {boolean} req.body.ack - Acknowledgment status (true/false)
- * @param {Object} req.user - Current authenticated user (from JWT)
- * @param {string} req.user.teamId - User's team ID
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with updated checks data
- * @throws {Error} 422 - Validation error if parameters or body are invalid
- * @throws {Error} 404 - Not found if monitor doesn't exist
- * @throws {Error} 403 - Forbidden if user doesn't have access to monitor
- * @example
- * PUT /checks/monitor/507f1f77bcf86cd799439011/ack
- * {
- * "ack": true
- * }
- * // Requires JWT authentication
- */
- ackAllChecks = this.asyncHandler(
- async (req, res) => {
- await ackAllChecksParamValidation.validateAsync(req.params);
- await ackAllChecksBodyValidation.validateAsync(req.body);
-
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw new Error("No team ID in request");
- }
-
- const updatedChecks = await this.checkService.ackAllChecks({
- monitorId: req?.params?.monitorId,
- path: req?.params?.path,
- teamId: req?.user?.teamId,
- ack: req?.body?.ack,
- });
-
- return res.success({
- msg: this.stringService.checkUpdateStatus,
- data: updatedChecks,
- });
- },
- SERVICE_NAME,
- "ackAllChecks"
- );
-
- /**
- * Deletes all checks for a specific monitor.
- *
- * @async
- * @function deleteChecks
- * @param {Object} req - Express request object
- * @param {Object} req.params - URL parameters
- * @param {string} req.params.monitorId - ID of the monitor whose checks to delete
- * @param {Object} req.user - Current authenticated user (from JWT)
- * @param {string} req.user.teamId - User's team ID
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with deletion count
- * @throws {Error} 422 - Validation error if monitorId is invalid
- * @throws {Error} 404 - Not found if monitor doesn't exist
- * @throws {Error} 403 - Forbidden if user doesn't have access to monitor
- * @example
- * DELETE /checks/monitor/507f1f77bcf86cd799439011
- * // Requires JWT authentication
- * // Response: { "data": { "deletedCount": 150 } }
- */
- deleteChecks = this.asyncHandler(
- async (req, res) => {
- await deleteChecksParamValidation.validateAsync(req.params);
-
- const deletedCount = await this.checkService.deleteChecks({
- monitorId: req.params.monitorId,
- teamId: req?.user?.teamId,
- });
-
- return res.success({
- msg: this.stringService.checkDelete,
- data: { deletedCount },
- });
- },
- SERVICE_NAME,
- "deleteChecks"
- );
-
- /**
- * Deletes all checks for the current user's team.
- *
- * @async
- * @function deleteChecksByTeamId
- * @param {Object} req - Express request object
- * @param {Object} req.user - Current authenticated user (from JWT)
- * @param {string} req.user.teamId - User's team ID
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with deletion count
- * @throws {Error} 422 - Validation error if parameters are invalid
- * @example
- * DELETE /checks/team
- * // Requires JWT authentication
- * // Response: { "data": { "deletedCount": 1250 } }
- */
- deleteChecksByTeamId = this.asyncHandler(
- async (req, res) => {
- await deleteChecksByTeamIdParamValidation.validateAsync(req.params);
-
- const deletedCount = await this.checkService.deleteChecksByTeamId({ teamId: req?.user?.teamId });
-
- return res.success({
- msg: this.stringService.checkDelete,
- data: { deletedCount },
- });
- },
- SERVICE_NAME,
- "deleteChecksByTeamId"
- );
-
- /**
- * Updates the TTL (Time To Live) setting for checks in the current user's team.
- *
- * @async
- * @function updateChecksTTL
- * @param {Object} req - Express request object
- * @param {Object} req.body - Request body containing TTL data
- * @param {number} req.body.ttl - TTL value in days
- * @param {Object} req.user - Current authenticated user (from JWT)
- * @param {string} req.user.teamId - User's team ID
- * @param {Object} res - Express response object
- * @returns {Promise} Success response confirming TTL update
- * @throws {Error} 422 - Validation error if TTL value is invalid
- * @example
- * PUT /checks/ttl
- * {
- * "ttl": 30
- * }
- * // Requires JWT authentication
- * // Sets check TTL to 30 days
- */
- updateChecksTTL = this.asyncHandler(
- async (req, res) => {
- await updateChecksTTLBodyValidation.validateAsync(req.body);
-
- await this.checkService.updateChecksTTL({
- teamId: req?.user?.teamId,
- ttl: req?.body?.ttl,
- });
-
- return res.success({
- msg: this.stringService.checkUpdateTTL,
- });
- },
- SERVICE_NAME,
- "updateChecksTtl"
- );
-}
-
-export default CheckController;
diff --git a/server/src/controllers/v1/controllerUtils.js b/server/src/controllers/v1/controllerUtils.js
deleted file mode 100755
index f21ba154b..000000000
--- a/server/src/controllers/v1/controllerUtils.js
+++ /dev/null
@@ -1,12 +0,0 @@
-const fetchMonitorCertificate = async (sslChecker, monitor) => {
- const monitorUrl = new URL(monitor.url);
- const hostname = monitorUrl.hostname;
- const cert = await sslChecker(hostname);
- // Throw an error if no cert or if cert.validTo is not present
- if (cert?.validTo === null || cert?.validTo === undefined) {
- throw new Error("Certificate not found");
- }
- return cert;
-};
-
-export { fetchMonitorCertificate };
diff --git a/server/src/controllers/v1/diagnosticController.js b/server/src/controllers/v1/diagnosticController.js
deleted file mode 100755
index 0723a9f6c..000000000
--- a/server/src/controllers/v1/diagnosticController.js
+++ /dev/null
@@ -1,61 +0,0 @@
-const SERVICE_NAME = "diagnosticController";
-import BaseController from "./baseController.js";
-/**
- * Diagnostic Controller
- *
- * Handles system diagnostic and monitoring requests including system statistics,
- * performance metrics, and health checks.
- *
- * @class DiagnosticController
- * @description Manages system diagnostics and performance monitoring
- */
-class DiagnosticController extends BaseController {
- static SERVICE_NAME = SERVICE_NAME;
- /**
- * Creates an instance of DiagnosticController.
- * @param {Object} commonDependencies - Common dependencies injected into the controller
- * @param {Object} dependencies - The dependencies required by the controller
- * @param {Object} dependencies.diagnosticService - Service for system diagnostics and monitoring
- */
- constructor(commonDependencies, { diagnosticService }) {
- super(commonDependencies);
- this.diagnosticService = diagnosticService;
- }
-
- get serviceName() {
- return DiagnosticController.SERVICE_NAME;
- }
- /**
- * Retrieves comprehensive system statistics and performance metrics.
- *
- * @async
- * @function getSystemStats
- * @param {Object} req - Express request object
- * @param {Object} res - Express response object
- * @returns {Promise} Success response with system diagnostics data
- * @description Returns system performance metrics, memory usage, CPU statistics,
- * and other diagnostic information useful for monitoring system health.
- * @example
- * GET /diagnostics/stats
- * // Response includes:
- * // - Memory usage (heap, external, arrayBuffers)
- * // - CPU usage statistics
- * // - System uptime
- * // - Performance metrics
- * // - Database connection status
- * // - Active processes/connections
- */
- getSystemStats = this.asyncHandler(
- async (req, res) => {
- const diagnostics = await this.diagnosticService.getSystemStats();
- return res.success({
- msg: "OK",
- data: diagnostics,
- });
- },
- SERVICE_NAME,
- "getSystemStats"
- );
-}
-
-export default DiagnosticController;
diff --git a/server/src/controllers/v1/inviteController.js b/server/src/controllers/v1/inviteController.js
deleted file mode 100755
index 1ec586f31..000000000
--- a/server/src/controllers/v1/inviteController.js
+++ /dev/null
@@ -1,101 +0,0 @@
-import { inviteBodyValidation, inviteVerificationBodyValidation } from "../../validation/joi.js";
-import BaseController from "./baseController.js";
-const SERVICE_NAME = "inviteController";
-
-/**
- * Controller for handling user invitation operations
- * Manages invite token generation, email sending, and token verification
- */
-class InviteController extends BaseController {
- static SERVICE_NAME = SERVICE_NAME;
- /**
- * Creates a new InviteController instance
- * @param {Object} commonDependencies - Common dependencies injected into the controller
- * @param {Object} dependencies.inviteService - Service for invite-related operations
- */
- constructor(commonDependencies, { inviteService }) {
- super(commonDependencies);
- this.inviteService = inviteService;
- }
-
- get serviceName() {
- return InviteController.SERVICE_NAME;
- }
-
- /**
- * Generates an invite token for a user invitation
- * @param {Object} req - Express request object
- * @param {Object} req.body - Request body containing invite details
- * @param {Object} req.user - Authenticated user object
- * @param {string} req.user.teamId - Team ID of the authenticated user
- * @param {Object} res - Express response object
- * @returns {Promise} Response with invite token data
- */
- getInviteToken = this.asyncHandler(
- async (req, res) => {
- const invite = req.body;
- const teamId = req?.user?.teamId;
- invite.teamId = teamId;
- await inviteBodyValidation.validateAsync(invite);
- const inviteToken = await this.inviteService.getInviteToken({ invite, teamId });
- return res.success({
- msg: this.stringService.inviteIssued,
- data: inviteToken,
- });
- },
- SERVICE_NAME,
- "getInviteToken"
- );
-
- /**
- * Sends an invitation email to a user
- * @param {Object} req - Express request object
- * @param {Object} req.body - Request body containing invite details
- * @param {Object} req.user - Authenticated user object
- * @param {string} req.user.teamId - Team ID of the authenticated user
- * @param {string} req.user.firstName - First name of the authenticated user
- * @param {Object} res - Express response object
- * @returns {Promise} Response with invite token data
- */
- sendInviteEmail = this.asyncHandler(
- async (req, res) => {
- const inviteRequest = req.body;
- inviteRequest.teamId = req?.user?.teamId;
- await inviteBodyValidation.validateAsync(inviteRequest);
-
- const inviteToken = await this.inviteService.sendInviteEmail({
- inviteRequest,
- firstName: req?.user?.firstName,
- });
- return res.success({
- msg: this.stringService.inviteIssued,
- data: inviteToken,
- });
- },
- SERVICE_NAME,
- "sendInviteEmail"
- );
-
- /**
- * Verifies an invite token and returns invite details
- * @param {Object} req - Express request object
- * @param {Object} req.body - Request body containing the invite token
- * @param {string} req.body.token - The invite token to verify
- * @param {Object} res - Express response object
- * @returns {Promise} Response with verified invite data
- */
- verifyInviteToken = this.asyncHandler(
- async (req, res) => {
- await inviteVerificationBodyValidation.validateAsync(req.body);
- const invite = await this.inviteService.verifyInviteToken({ inviteToken: req?.body?.token });
- return res.success({
- msg: this.stringService.inviteVerified,
- data: invite,
- });
- },
- SERVICE_NAME,
- "verifyInviteToken"
- );
-}
-
-export default InviteController;
diff --git a/server/src/controllers/v1/logController.js b/server/src/controllers/v1/logController.js
deleted file mode 100644
index 3537246bb..000000000
--- a/server/src/controllers/v1/logController.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import BaseController from "./baseController.js";
-const SERVICE_NAME = "LogController";
-
-class LogController extends BaseController {
- static SERVICE_NAME = SERVICE_NAME;
- constructor(commonDependencies) {
- super(commonDependencies);
- }
-
- get serviceName() {
- return LogController.SERVICE_NAME;
- }
-
- getLogs = this.asyncHandler(
- async (req, res) => {
- const logs = await this.logger.getLogs();
- res.success({
- msg: "Logs fetched successfully",
- data: logs,
- });
- },
- SERVICE_NAME,
- "getLogs"
- );
-}
-export default LogController;
diff --git a/server/src/controllers/v1/maintenanceWindowController.js b/server/src/controllers/v1/maintenanceWindowController.js
deleted file mode 100644
index be373ace5..000000000
--- a/server/src/controllers/v1/maintenanceWindowController.js
+++ /dev/null
@@ -1,146 +0,0 @@
-import {
- createMaintenanceWindowBodyValidation,
- editMaintenanceWindowByIdParamValidation,
- editMaintenanceByIdWindowBodyValidation,
- getMaintenanceWindowByIdParamValidation,
- getMaintenanceWindowsByMonitorIdParamValidation,
- getMaintenanceWindowsByTeamIdQueryValidation,
- deleteMaintenanceWindowByIdParamValidation,
-} from "../../validation/joi.js";
-import BaseController from "./baseController.js";
-
-const SERVICE_NAME = "maintenanceWindowController";
-
-class MaintenanceWindowController extends BaseController {
- static SERVICE_NAME = SERVICE_NAME;
- constructor(commonDependencies, { settingsService, maintenanceWindowService }) {
- super(commonDependencies);
- this.settingsService = settingsService;
- this.maintenanceWindowService = maintenanceWindowService;
- }
-
- get serviceName() {
- return MaintenanceWindowController.SERVICE_NAME;
- }
-
- createMaintenanceWindows = this.asyncHandler(
- async (req, res) => {
- await createMaintenanceWindowBodyValidation.validateAsync(req.body);
-
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- await this.maintenanceWindowService.createMaintenanceWindow({ teamId, body: req.body });
-
- return res.success({
- msg: this.stringService.maintenanceWindowCreate,
- });
- },
- SERVICE_NAME,
- "createMaintenanceWindows"
- );
-
- getMaintenanceWindowById = this.asyncHandler(
- async (req, res) => {
- await getMaintenanceWindowByIdParamValidation.validateAsync(req.params);
-
- const teamId = req.user.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const maintenanceWindow = await this.maintenanceWindowService.getMaintenanceWindowById({ id: req.params.id, teamId });
-
- return res.success({
- msg: this.stringService.maintenanceWindowGetById,
- data: maintenanceWindow,
- });
- },
- SERVICE_NAME,
- "getMaintenanceWindowById"
- );
-
- getMaintenanceWindowsByTeamId = this.asyncHandler(
- async (req, res) => {
- await getMaintenanceWindowsByTeamIdQueryValidation.validateAsync(req.query);
-
- const teamId = req?.user?.teamId;
-
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByTeamId({ teamId, query: req.query });
-
- return res.success({
- msg: this.stringService.maintenanceWindowGetByTeam,
- data: maintenanceWindows,
- });
- },
- SERVICE_NAME,
- "getMaintenanceWindowsByTeamId"
- );
-
- getMaintenanceWindowsByMonitorId = this.asyncHandler(
- async (req, res) => {
- await getMaintenanceWindowsByMonitorIdParamValidation.validateAsync(req.params);
-
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByMonitorId({ monitorId: req.params.monitorId, teamId });
-
- return res.success({
- msg: this.stringService.maintenanceWindowGetByUser,
- data: maintenanceWindows,
- });
- },
- SERVICE_NAME,
- "getMaintenanceWindowsByMonitorId"
- );
-
- deleteMaintenanceWindow = this.asyncHandler(
- async (req, res) => {
- await deleteMaintenanceWindowByIdParamValidation.validateAsync(req.params);
-
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- await this.maintenanceWindowService.deleteMaintenanceWindow({ id: req.params.id, teamId });
-
- return res.success({
- msg: this.stringService.maintenanceWindowDelete,
- });
- },
- SERVICE_NAME,
- "deleteMaintenanceWindow"
- );
-
- editMaintenanceWindow = this.asyncHandler(
- async (req, res) => {
- await editMaintenanceWindowByIdParamValidation.validateAsync(req.params);
- await editMaintenanceByIdWindowBodyValidation.validateAsync(req.body);
-
- const teamId = req.user.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const editedMaintenanceWindow = await this.maintenanceWindowService.editMaintenanceWindow({ id: req.params.id, body: req.body, teamId });
- return res.success({
- msg: this.stringService.maintenanceWindowEdit,
- data: editedMaintenanceWindow,
- });
- },
- SERVICE_NAME,
- "editMaintenanceWindow"
- );
-}
-
-export default MaintenanceWindowController;
diff --git a/server/src/controllers/v1/monitorController.js b/server/src/controllers/v1/monitorController.js
deleted file mode 100644
index 5ac44fa94..000000000
--- a/server/src/controllers/v1/monitorController.js
+++ /dev/null
@@ -1,475 +0,0 @@
-import {
- getMonitorByIdParamValidation,
- getMonitorByIdQueryValidation,
- getMonitorsByTeamIdParamValidation,
- getMonitorsByTeamIdQueryValidation,
- createMonitorBodyValidation,
- editMonitorBodyValidation,
- pauseMonitorParamValidation,
- getMonitorStatsByIdParamValidation,
- getMonitorStatsByIdQueryValidation,
- getCertificateParamValidation,
- getHardwareDetailsByIdParamValidation,
- getHardwareDetailsByIdQueryValidation,
-} from "../../validation/joi.js";
-import sslChecker from "ssl-checker";
-import { fetchMonitorCertificate } from "./controllerUtils.js";
-import BaseController from "./baseController.js";
-
-const SERVICE_NAME = "monitorController";
-class MonitorController extends BaseController {
- static SERVICE_NAME = SERVICE_NAME;
- constructor(commonDependencies, { settingsService, jobQueue, emailService, monitorService }) {
- super(commonDependencies);
- this.settingsService = settingsService;
- this.jobQueue = jobQueue;
- this.emailService = emailService;
- this.monitorService = monitorService;
- }
-
- get serviceName() {
- return MonitorController.SERVICE_NAME;
- }
-
- async verifyTeamAccess(teamId, monitorId) {
- const monitor = await this.db.monitorModule.getMonitorById(monitorId);
- if (!monitor.teamId.equals(teamId)) {
- throw this.errorService.createAuthorizationError();
- }
- }
-
- getAllMonitors = this.asyncHandler(
- async (req, res) => {
- const monitors = await this.monitorService.getAllMonitors();
- return res.success({
- msg: this.stringService.monitorGetAll,
- data: monitors,
- });
- },
- SERVICE_NAME,
- "getAllMonitors"
- );
-
- getUptimeDetailsById = this.asyncHandler(
- 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 this.errorService.createBadRequestError("Team ID is required");
- }
-
- const data = await this.monitorService.getUptimeDetailsById({
- teamId,
- monitorId,
- dateRange,
- normalize,
- });
- return res.success({
- msg: this.stringService.monitorGetByIdSuccess,
- data: data,
- });
- },
- SERVICE_NAME,
- "getUptimeDetailsById"
- );
-
- getMonitorStatsById = this.asyncHandler(
- 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?.monitorId;
-
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const monitorStats = await this.monitorService.getMonitorStatsById({
- teamId,
- monitorId,
- limit,
- sortOrder,
- dateRange,
- numToDisplay,
- normalize,
- });
-
- return res.success({
- msg: this.stringService.monitorStatsById,
- data: monitorStats,
- });
- },
- SERVICE_NAME,
- "getMonitorStatsById"
- );
-
- /**
- * Get hardware details for a specific monitor by ID
- * @async
- * @param {Express.Request} req - Express request object containing monitorId in params
- * @param {Express.Response} res - Express response object
- * @param {Express.NextFunction} next - Express next middleware function
- * @returns {Promise}
- * @throws {Error} - Throws error if monitor not found or other database errors
- */
- getHardwareDetailsById = this.asyncHandler(
- async (req, res) => {
- await getHardwareDetailsByIdParamValidation.validateAsync(req.params);
- await getHardwareDetailsByIdQueryValidation.validateAsync(req.query);
-
- const monitorId = req?.params?.monitorId;
- const dateRange = req?.query?.dateRange;
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const monitor = await this.monitorService.getHardwareDetailsById({
- teamId,
- monitorId,
- dateRange,
- });
-
- return res.success({
- msg: this.stringService.monitorGetByIdSuccess,
- data: monitor,
- });
- },
- SERVICE_NAME,
- "getHardwareDetailsById"
- );
-
- getMonitorCertificate = this.asyncHandler(
- async (req, res) => {
- await getCertificateParamValidation.validateAsync(req.params);
-
- const { monitorId } = req.params;
- const monitor = await this.db.monitorModule.getMonitorById(monitorId);
- const certificate = await fetchMonitorCertificate(sslChecker, monitor);
-
- return res.success({
- msg: this.stringService.monitorCertificate,
- data: {
- certificateDate: new Date(certificate.validTo),
- },
- });
- },
- SERVICE_NAME,
- "getMonitorCertificate"
- );
-
- getMonitorById = this.asyncHandler(
- async (req, res) => {
- await getMonitorByIdParamValidation.validateAsync(req.params);
- await getMonitorByIdQueryValidation.validateAsync(req.query);
-
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const monitor = await this.monitorService.getMonitorById({ teamId, monitorId: req?.params?.monitorId });
-
- return res.success({
- msg: this.stringService.monitorGetByIdSuccess,
- data: monitor,
- });
- },
- SERVICE_NAME,
- "getMonitorById"
- );
-
- createMonitor = this.asyncHandler(
- async (req, res) => {
- await createMonitorBodyValidation.validateAsync(req.body);
-
- const userId = req?.user?._id;
- const teamId = req?.user?.teamId;
-
- const monitor = await this.monitorService.createMonitor({ teamId, userId, body: req.body });
-
- return res.success({
- msg: this.stringService.monitorCreate,
- data: monitor,
- });
- },
- SERVICE_NAME,
- "createMonitor"
- );
-
- createBulkMonitors = this.asyncHandler(
- async (req, res) => {
- if (!req.file) {
- throw this.errorService.createBadRequestError("No file uploaded");
- }
-
- if (!req.file.mimetype.includes("csv")) {
- throw this.errorService.createBadRequestError("File is not a CSV");
- }
-
- if (req.file.size === 0) {
- throw this.errorService.createBadRequestError("File is empty");
- }
-
- const userId = req?.user?._id;
- const teamId = req?.user?.teamId;
-
- if (!userId || !teamId) {
- throw this.errorService.createBadRequestError("Missing userId or teamId");
- }
-
- const fileData = req?.file?.buffer?.toString("utf-8");
- if (!fileData) {
- throw this.errorService.createBadRequestError("Cannot get file from buffer");
- }
-
- const monitors = await this.monitorService.createBulkMonitors({ fileData, userId, teamId });
-
- return res.success({
- msg: this.stringService.bulkMonitorsCreate,
- data: monitors,
- });
- },
- SERVICE_NAME,
- "createBulkMonitors"
- );
-
- deleteMonitor = this.asyncHandler(
- async (req, res) => {
- await getMonitorByIdParamValidation.validateAsync(req.params);
- const monitorId = req.params.monitorId;
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const deletedMonitor = await this.monitorService.deleteMonitor({ teamId, monitorId });
-
- return res.success({ msg: this.stringService.monitorDelete, data: deletedMonitor });
- },
- SERVICE_NAME,
- "deleteMonitor"
- );
-
- deleteAllMonitors = this.asyncHandler(
- async (req, res) => {
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const deletedCount = await this.monitorService.deleteAllMonitors({ teamId });
-
- return res.success({ msg: `Deleted ${deletedCount} monitors` });
- },
- SERVICE_NAME,
- "deleteAllMonitors"
- );
-
- editMonitor = this.asyncHandler(
- async (req, res) => {
- await getMonitorByIdParamValidation.validateAsync(req.params);
- await editMonitorBodyValidation.validateAsync(req.body);
- const monitorId = req?.params?.monitorId;
-
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const editedMonitor = await this.monitorService.editMonitor({ teamId, monitorId, body: req.body });
-
- return res.success({
- msg: this.stringService.monitorEdit,
- data: editedMonitor,
- });
- },
- SERVICE_NAME,
- "editMonitor"
- );
-
- pauseMonitor = this.asyncHandler(
- async (req, res) => {
- await pauseMonitorParamValidation.validateAsync(req.params);
-
- const monitorId = req.params.monitorId;
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const monitor = await this.monitorService.pauseMonitor({ teamId, monitorId });
-
- return res.success({
- msg: monitor.isActive ? this.stringService.monitorResume : this.stringService.monitorPause,
- data: monitor,
- });
- },
- SERVICE_NAME,
- "pauseMonitor"
- );
-
- addDemoMonitors = this.asyncHandler(
- async (req, res) => {
- const { _id, teamId } = req.user;
- const demoMonitors = await this.monitorService.addDemoMonitors({ userId: _id, teamId });
-
- return res.success({
- msg: this.stringService.monitorDemoAdded,
- data: demoMonitors?.length ?? 0,
- });
- },
- SERVICE_NAME,
- "addDemoMonitors"
- );
-
- sendTestEmail = this.asyncHandler(
- async (req, res) => {
- const { to } = req.body;
- if (!to || typeof to !== "string") {
- throw this.errorService.createBadRequestError(this.stringService.errorForValidEmailAddress);
- }
-
- const messageId = await this.monitorService.sendTestEmail({ to });
- return res.success({
- msg: this.stringService.sendTestEmail,
- data: { messageId },
- });
- },
- SERVICE_NAME,
- "sendTestEmail"
- );
-
- getMonitorsByTeamId = this.asyncHandler(
- async (req, res) => {
- await getMonitorsByTeamIdParamValidation.validateAsync(req.params);
- await getMonitorsByTeamIdQueryValidation.validateAsync(req.query);
-
- let { limit, type, page, rowsPerPage, filter, field, order } = req.query;
- const teamId = req?.user?.teamId;
-
- const monitors = await this.monitorService.getMonitorsByTeamId({ teamId, limit, type, page, rowsPerPage, filter, field, order });
-
- return res.success({
- msg: this.stringService.monitorGetByTeamId,
- data: monitors,
- });
- },
- SERVICE_NAME,
- "getMonitorsByTeamId"
- );
-
- getMonitorsAndSummaryByTeamId = this.asyncHandler(
- async (req, res) => {
- await getMonitorsByTeamIdParamValidation.validateAsync(req.params);
- await getMonitorsByTeamIdQueryValidation.validateAsync(req.query);
-
- const explain = req?.query?.explain;
- const type = req?.query?.type;
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const result = await this.monitorService.getMonitorsAndSummaryByTeamId({ teamId, type, explain });
-
- return res.success({
- msg: "OK", // TODO
- data: result,
- });
- },
- SERVICE_NAME,
- "getMonitorsAndSummaryByTeamId"
- );
-
- getMonitorsWithChecksByTeamId = this.asyncHandler(
- async (req, res) => {
- await getMonitorsByTeamIdParamValidation.validateAsync(req.params);
- await getMonitorsByTeamIdQueryValidation.validateAsync(req.query);
-
- const explain = req?.query?.explain;
- let { limit, type, page, rowsPerPage, filter, field, order } = req.query;
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const monitors = await this.monitorService.getMonitorsWithChecksByTeamId({
- teamId,
- limit,
- type,
- page,
- rowsPerPage,
- filter,
- field,
- order,
- explain,
- });
-
- return res.success({
- msg: "OK",
- data: monitors,
- });
- },
- SERVICE_NAME,
- "getMonitorsWithChecksByTeamId"
- );
-
- exportMonitorsToCSV = this.asyncHandler(
- async (req, res) => {
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const csv = await this.monitorService.exportMonitorsToCSV({ teamId });
-
- return res.file({
- data: csv,
- headers: {
- "Content-Type": "text/csv",
- "Content-Disposition": "attachment; filename=monitors.csv",
- },
- });
- },
- SERVICE_NAME,
- "exportMonitorsToCSV"
- );
-
- getAllGames = this.asyncHandler(
- async (req, res) => {
- return res.success({
- msg: "OK",
- data: this.monitorService.getAllGames(),
- });
- },
- SERVICE_NAME,
- "getAllGames"
- );
-
- getGroupsByTeamId = this.asyncHandler(
- async (req, res) => {
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const groups = await this.monitorService.getGroupsByTeamId({ teamId });
-
- return res.success({
- msg: "OK",
- data: groups,
- });
- },
- SERVICE_NAME,
- "getGroupsByTeamId"
- );
-}
-
-export default MonitorController;
diff --git a/server/src/controllers/v1/notificationController.js b/server/src/controllers/v1/notificationController.js
deleted file mode 100644
index 37c2b4e60..000000000
--- a/server/src/controllers/v1/notificationController.js
+++ /dev/null
@@ -1,180 +0,0 @@
-import { createNotificationBodyValidation } from "../../validation/joi.js";
-import BaseController from "./baseController.js";
-
-const SERVICE_NAME = "NotificationController";
-
-class NotificationController extends BaseController {
- static SERVICE_NAME = SERVICE_NAME;
- constructor(commonDependencies, { notificationService, statusService }) {
- super(commonDependencies);
- this.notificationService = notificationService;
- this.statusService = statusService;
- }
-
- get serviceName() {
- return NotificationController.SERVICE_NAME;
- }
-
- testNotification = this.asyncHandler(
- async (req, res) => {
- const notification = req.body;
-
- const success = await this.notificationService.sendTestNotification(notification);
-
- if (!success) {
- throw this.errorService.createServerError("Sending notification failed");
- }
-
- return res.success({
- msg: "Notification sent successfully",
- });
- },
- SERVICE_NAME,
- "testNotification"
- );
-
- createNotification = this.asyncHandler(
- async (req, res) => {
- await createNotificationBodyValidation.validateAsync(req.body, {
- abortEarly: false,
- });
-
- const body = req.body;
-
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const userId = req?.user?._id;
- if (!userId) {
- throw this.errorService.createBadRequestError("User ID is required");
- }
- body.userId = userId;
- body.teamId = teamId;
-
- const notification = await this.db.notificationModule.createNotification(body);
- return res.success({
- msg: "Notification created successfully",
- data: notification,
- });
- },
- SERVICE_NAME,
- "createNotification"
- );
-
- getNotificationsByTeamId = this.asyncHandler(
- async (req, res) => {
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const notifications = await this.db.notificationModule.getNotificationsByTeamId(teamId);
-
- return res.success({
- msg: "Notifications fetched successfully",
- data: notifications,
- });
- },
- SERVICE_NAME,
- "getNotificationsByTeamId"
- );
-
- deleteNotification = this.asyncHandler(
- async (req, res) => {
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const notification = await this.db.notificationModule.getNotificationById(req.params.id);
- if (!notification.teamId.equals(teamId)) {
- throw this.errorService.createAuthorizationError();
- }
-
- await this.db.notificationModule.deleteNotificationById(req.params.id);
- return res.success({
- msg: "Notification deleted successfully",
- });
- },
- SERVICE_NAME,
- "deleteNotification"
- );
-
- getNotificationById = this.asyncHandler(
- async (req, res) => {
- const notification = await this.db.notificationModule.getNotificationById(req.params.id);
-
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- if (!notification.teamId.equals(teamId)) {
- throw this.errorService.createAuthorizationError();
- }
- return res.success({
- msg: "Notification fetched successfully",
- data: notification,
- });
- },
- SERVICE_NAME,
- "getNotificationById"
- );
-
- editNotification = this.asyncHandler(
- async (req, res) => {
- await createNotificationBodyValidation.validateAsync(req.body, {
- abortEarly: false,
- });
-
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const notification = await this.db.notificationModule.getNotificationById(req.params.id);
-
- if (!notification.teamId.equals(teamId)) {
- throw this.errorService.createAuthorizationError();
- }
-
- const editedNotification = await this.db.notificationModule.editNotification(req.params.id, req.body);
- return res.success({
- msg: "Notification updated successfully",
- data: editedNotification,
- });
- },
- SERVICE_NAME,
- "editNotification"
- );
-
- testAllNotifications = this.asyncHandler(
- async (req, res) => {
- const monitorId = req.body.monitorId;
- const teamId = req?.user?.teamId;
- if (!teamId) {
- throw this.errorService.createBadRequestError("Team ID is required");
- }
-
- const monitor = await this.db.monitorModule.getMonitorById(monitorId);
-
- if (!monitor.teamId.equals(teamId)) {
- throw this.errorService.createAuthorizationError();
- }
-
- const notifications = monitor.notifications;
- if (notifications.length === 0) throw this.errorService.createBadRequestError("No notifications");
- const result = await this.notificationService.testAllNotifications(notifications);
- if (!result) throw this.errorService.createServerError("Failed to send all notifications");
- return res.success({
- msg: "All notifications sent successfully",
- });
- },
- SERVICE_NAME,
- "testAllNotifications"
- );
-}
-
-export default NotificationController;
diff --git a/server/src/controllers/v1/queueController.js b/server/src/controllers/v1/queueController.js
deleted file mode 100755
index d2c3fd06f..000000000
--- a/server/src/controllers/v1/queueController.js
+++ /dev/null
@@ -1,87 +0,0 @@
-import BaseController from "./baseController.js";
-const SERVICE_NAME = "JobQueueController";
-
-class JobQueueController extends BaseController {
- static SERVICE_NAME = SERVICE_NAME;
- constructor(commonDependencies, { jobQueue }) {
- super(commonDependencies);
- this.jobQueue = jobQueue;
- }
-
- get serviceName() {
- return JobQueueController.SERVICE_NAME;
- }
-
- getMetrics = this.asyncHandler(
- async (req, res) => {
- const metrics = await this.jobQueue.getMetrics();
- res.success({
- msg: this.stringService.queueGetMetrics,
- data: metrics,
- });
- },
- SERVICE_NAME,
- "getMetrics"
- );
-
- getJobs = this.asyncHandler(
- async (req, res) => {
- const jobs = await this.jobQueue.getJobs();
- return res.success({
- msg: this.stringService.queueGetJobs,
- data: jobs,
- });
- },
- SERVICE_NAME,
- "getJobs"
- );
-
- getAllMetrics = this.asyncHandler(
- async (req, res) => {
- const jobs = await this.jobQueue.getJobs();
- const metrics = await this.jobQueue.getMetrics();
- return res.success({
- msg: this.stringService.queueGetAllMetrics,
- data: { jobs, metrics },
- });
- },
- SERVICE_NAME,
- "getAllMetrics"
- );
-
- addJob = this.asyncHandler(
- async (req, res) => {
- await this.jobQueue.addJob(Math.random().toString(36).substring(7));
- return res.success({
- msg: this.stringService.queueAddJob,
- });
- },
- SERVICE_NAME,
- "addJob"
- );
-
- flushQueue = this.asyncHandler(
- async (req, res) => {
- const result = await this.jobQueue.flushQueues();
- return res.success({
- msg: this.stringService.jobQueueFlush,
- data: result,
- });
- },
- SERVICE_NAME,
- "flushQueue"
- );
-
- checkQueueHealth = this.asyncHandler(
- async (req, res) => {
- const stuckQueues = await this.jobQueue.checkQueueHealth();
- return res.success({
- msg: this.stringService.queueHealthCheck,
- data: stuckQueues,
- });
- },
- SERVICE_NAME,
- "checkQueueHealth"
- );
-}
-export default JobQueueController;
diff --git a/server/src/controllers/v1/statusPageController.js b/server/src/controllers/v1/statusPageController.js
deleted file mode 100644
index de6a1ca7c..000000000
--- a/server/src/controllers/v1/statusPageController.js
+++ /dev/null
@@ -1,108 +0,0 @@
-import { createStatusPageBodyValidation, getStatusPageParamValidation, getStatusPageQueryValidation, imageValidation } from "../../validation/joi.js";
-import BaseController from "./baseController.js";
-
-const SERVICE_NAME = "statusPageController";
-
-class StatusPageController extends BaseController {
- static SERVICE_NAME = SERVICE_NAME;
- constructor(commonDependencies) {
- super(commonDependencies);
- }
-
- get serviceName() {
- return StatusPageController.SERVICE_NAME;
- }
-
- createStatusPage = this.asyncHandler(
- async (req, res) => {
- await createStatusPageBodyValidation.validateAsync(req.body);
- await imageValidation.validateAsync(req.file);
-
- const { _id, teamId } = req.user;
- const statusPage = await this.db.statusPageModule.createStatusPage({
- statusPageData: req.body,
- image: req.file,
- userId: _id,
- teamId,
- });
- return res.success({
- msg: this.stringService.statusPageCreate,
- data: statusPage,
- });
- },
- SERVICE_NAME,
- "createStatusPage"
- );
-
- updateStatusPage = this.asyncHandler(
- async (req, res) => {
- await createStatusPageBodyValidation.validateAsync(req.body);
- await imageValidation.validateAsync(req.file);
-
- const statusPage = await this.db.statusPageModule.updateStatusPage(req.body, req.file);
- if (statusPage === null) {
- throw this.errorService.createNotFoundError(this.stringService.statusPageNotFound);
- }
- return res.success({
- msg: this.stringService.statusPageUpdate,
- data: statusPage,
- });
- },
- SERVICE_NAME,
- "updateStatusPage"
- );
-
- getStatusPage = this.asyncHandler(
- async (req, res) => {
- const statusPage = await this.db.statusPageModule.getStatusPage();
- return res.success({
- msg: this.stringService.statusPageByUrl,
- data: statusPage,
- });
- },
- SERVICE_NAME,
- "getStatusPage"
- );
-
- getStatusPageByUrl = this.asyncHandler(
- async (req, res) => {
- await getStatusPageParamValidation.validateAsync(req.params);
- await getStatusPageQueryValidation.validateAsync(req.query);
-
- const statusPage = await this.db.statusPageModule.getStatusPageByUrl(req.params.url);
- return res.success({
- msg: this.stringService.statusPageByUrl,
- data: statusPage,
- });
- },
- SERVICE_NAME,
- "getStatusPageByUrl"
- );
-
- getStatusPagesByTeamId = this.asyncHandler(
- async (req, res) => {
- const teamId = req.user.teamId;
- const statusPages = await this.db.statusPageModule.getStatusPagesByTeamId(teamId);
-
- return res.success({
- msg: this.stringService.statusPageByTeamId,
- data: statusPages,
- });
- },
- SERVICE_NAME,
- "getStatusPagesByTeamId"
- );
-
- deleteStatusPage = this.asyncHandler(
- async (req, res) => {
- await this.db.statusPageModule.deleteStatusPage(req.params.url);
- return res.success({
- msg: this.stringService.statusPageDelete,
- });
- },
- SERVICE_NAME,
- "deleteStatusPage"
- );
-}
-
-export default StatusPageController;
diff --git a/server/src/controllers/v2/AuthController.ts b/server/src/controllers/v2/AuthController.ts
deleted file mode 100644
index 3afed5312..000000000
--- a/server/src/controllers/v2/AuthController.ts
+++ /dev/null
@@ -1,150 +0,0 @@
-import { Request, Response, NextFunction } from "express";
-import { encode, decode } from "../../utils/JWTUtils.js";
-import AuthService from "../../service/v2/business/AuthService.js";
-import ApiError from "../../utils/ApiError.js";
-import InviteService from "../../service/v2/business/InviteService.js";
-import { IInvite } from "../../db/v2/models/index.js";
-
-class AuthController {
- private authService: AuthService;
- private inviteService: InviteService;
- constructor(authService: AuthService, inviteService: InviteService) {
- this.authService = authService;
- this.inviteService = inviteService;
- }
-
- register = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const { email, firstName, lastName, password } = req.body;
-
- if (!email || !firstName || !lastName || !password) {
- throw new Error("Email, firstName, lastName, and password are required");
- }
-
- const result = await this.authService.register({
- email,
- firstName,
- lastName,
- password,
- });
-
- const token = encode(result);
-
- res.cookie("token", token, {
- httpOnly: true,
- secure: process.env.NODE_ENV === "production",
- sameSite: "strict",
- maxAge: 7 * 24 * 60 * 60 * 1000, // 1 week
- });
-
- res.status(201).json({
- message: "User created successfully",
- });
- } catch (error) {
- next(error);
- }
- };
-
- registerWithInvite = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const token = req.params.token;
- if (!token) {
- throw new ApiError("Invite token is required", 400);
- }
-
- const invite: IInvite = await this.inviteService.get(token);
-
- const { firstName, lastName, password } = req.body;
- const email = invite?.email;
- const roles = invite?.roles;
-
- if (!email || !firstName || !lastName || !password || !roles || roles.length === 0) {
- throw new Error("Email, firstName, lastName, password, and roles are required");
- }
-
- const result = await this.authService.registerWithInvite({
- email,
- firstName,
- lastName,
- password,
- roles,
- });
-
- if (!result) {
- throw new Error("Registration failed");
- }
-
- await this.inviteService.delete(invite._id.toString());
-
- const jwt = encode(result);
-
- res.cookie("token", jwt, {
- httpOnly: true,
- secure: process.env.NODE_ENV === "production",
- sameSite: "strict",
- maxAge: 7 * 24 * 60 * 60 * 1000, // 1 week
- });
-
- res.status(201).json({ message: "User created successfully" });
- } catch (error) {
- next(error);
- }
- };
-
- login = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const { email, password } = req.body;
- // Validation
- if (!email || !password) {
- return res.status(400).json({ message: "Email and password are required" });
- }
- const result = await this.authService.login({ email, password });
-
- const token = encode(result);
-
- res.cookie("token", token, {
- httpOnly: true,
- secure: process.env.NODE_ENV === "production",
- sameSite: "strict",
- maxAge: 7 * 24 * 60 * 60 * 1000, // 1 week
- });
-
- res.status(200).json({
- message: "Login successful",
- });
- } catch (error) {
- next(error);
- }
- };
-
- logout = (req: Request, res: Response) => {
- res.clearCookie("token", {
- httpOnly: true,
- secure: process.env.NODE_ENV === "production",
- sameSite: "strict",
- });
- res.status(200).json({ message: "Logout successful" });
- };
-
- me = (req: Request, res: Response, next: NextFunction) => {
- return res.status(200).json({ message: "OK" });
- };
-
- cleanup = async (req: Request, res: Response) => {
- try {
- await this.authService.cleanup();
- res.status(200).json({ message: "Cleanup successful" });
- } catch (error) {}
- };
-
- cleanMonitors = async (req: Request, res: Response) => {
- try {
- await this.authService.cleanMonitors();
- res.status(200).json({ message: "Monitors cleanup successful" });
- } catch (error) {
- res.status(500).json({ message: "Internal server error" });
- }
- };
-}
-
-export default AuthController;
diff --git a/server/src/controllers/v2/InviteController.ts b/server/src/controllers/v2/InviteController.ts
deleted file mode 100644
index 4d72bf8d4..000000000
--- a/server/src/controllers/v2/InviteController.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import { Request, Response, NextFunction } from "express";
-import InviteService from "../../service/v2/business/InviteService.js";
-
-class InviteController {
- private inviteService: InviteService;
- constructor(inviteService: InviteService) {
- this.inviteService = inviteService;
- }
-
- create = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const tokenizedUser = req.user;
- if (!tokenizedUser) {
- return res.status(401).json({ message: "Unauthorized" });
- }
- const invite = await this.inviteService.create(tokenizedUser, req.body);
- res.status(201).json({ message: "OK", data: invite });
- } catch (error: any) {
- next(error);
- }
- };
-
- getAll = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const invites = await this.inviteService.getAll();
- res.status(200).json({
- message: "OK",
- data: invites,
- });
- } catch (error) {
- next(error);
- }
- };
-
- get = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const token = req.params.token;
- if (!token) {
- return res.status(400).json({ message: "Token parameter is required" });
- }
- const invite = await this.inviteService.get(token);
- res.status(200).json({ message: "OK", data: invite });
- } catch (error) {
- next(error);
- }
- };
-
- delete = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const id = req.params.id;
- if (!id) {
- return res.status(400).json({ message: "ID parameter is required" });
- }
- await this.inviteService.delete(id);
- res.status(204).json({ message: "OK" });
- } catch (error: any) {
- next(error);
- }
- };
-}
-
-export default InviteController;
diff --git a/server/src/controllers/v2/MaintenanceController.ts b/server/src/controllers/v2/MaintenanceController.ts
deleted file mode 100644
index 7c6be11d5..000000000
--- a/server/src/controllers/v2/MaintenanceController.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-import { Request, Response, NextFunction } from "express";
-import MaintenanceService from "../../service/v2/business/MaintenanceService.js";
-
-class MaintenanceController {
- private maintenanceService: MaintenanceService;
- constructor(maintenanceService: MaintenanceService) {
- this.maintenanceService = maintenanceService;
- }
-
- create = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const tokenizedUser = req.user;
- if (!tokenizedUser) {
- return res.status(401).json({ message: "Unauthorized" });
- }
- const maintenance = await this.maintenanceService.create(tokenizedUser, req.body);
- res.status(201).json({ message: "OK", data: maintenance });
- } catch (error) {
- next(error);
- }
- };
-
- getAll = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const maintenances = await this.maintenanceService.getAll();
- res.status(200).json({
- message: "OK",
- data: maintenances,
- });
- } catch (error) {
- next(error);
- }
- };
-
- toggleActive = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const tokenizedUser = req.user;
- if (!tokenizedUser) {
- return res.status(401).json({ message: "Unauthorized" });
- }
- const id = req.params.id;
- if (!id) {
- return res.status(400).json({ message: "ID parameter is required" });
- }
- const maintenance = await this.maintenanceService.toggleActive(tokenizedUser, id);
- res.status(200).json({ message: "OK", data: maintenance });
- } catch (error) {
- next(error);
- }
- };
-
- update = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const tokenizedUser = req.user;
- if (!tokenizedUser) {
- return res.status(401).json({ message: "Unauthorized" });
- }
- const id = req.params.id;
- if (!id) {
- return res.status(400).json({ message: "ID parameter is required" });
- }
- const updatedMaintenance = await this.maintenanceService.update(tokenizedUser, id, req.body);
- res.status(200).json({ message: "OK", data: updatedMaintenance });
- } catch (error) {
- next(error);
- }
- };
-
- get = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const id = req.params.id;
- if (!id) {
- return res.status(400).json({ message: "ID parameter is required" });
- }
- const maintenance = await this.maintenanceService.get(id);
- res.status(200).json({ message: "OK", data: maintenance });
- } catch (error) {
- next(error);
- }
- };
-
- delete = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const id = req.params.id;
- if (!id) {
- return res.status(400).json({ message: "ID parameter is required" });
- }
- await this.maintenanceService.delete(id);
- res.status(204).json({ message: "OK" });
- } catch (error) {
- next(error);
- }
- };
-}
-
-export default MaintenanceController;
diff --git a/server/src/controllers/v2/MonitorController.ts b/server/src/controllers/v2/MonitorController.ts
deleted file mode 100644
index 7c0c63d2c..000000000
--- a/server/src/controllers/v2/MonitorController.ts
+++ /dev/null
@@ -1,191 +0,0 @@
-import { Request, Response, NextFunction } from "express";
-import ApiError from "../../utils/ApiError.js";
-import MonitorService from "../../service/v2/business/MonitorService.js";
-import { MonitorType } from "../../db/v2/models/monitors/Monitor.js";
-import CheckService from "../../service/v2/business/CheckService.js";
-class MonitorController {
- private monitorService: MonitorService;
- private checkService: CheckService;
- constructor(monitorService: MonitorService, checkService: CheckService) {
- this.monitorService = monitorService;
- this.checkService = checkService;
- }
-
- create = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const tokenizedUser = req.user;
- if (!tokenizedUser) {
- return res.status(401).json({ message: "Unauthorized" });
- }
-
- const monitor = await this.monitorService.create(tokenizedUser, req.body);
- res.status(201).json({
- message: "Monitor created successfully",
- data: monitor,
- });
- } catch (error) {
- next(error);
- }
- };
-
- getAll = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const tokenizedUser = req.user;
- if (!tokenizedUser) {
- return res.status(401).json({ message: "Unauthorized" });
- }
-
- let monitors;
- if (req.query.embedChecks === "true") {
- const page = Math.max(1, Number(req.query.page) || 1);
- const limit = Math.max(1, Number(req.query.limit) || 10);
- const type: MonitorType[] = req.query.type as MonitorType[];
-
- monitors = await this.monitorService.getAllEmbedChecks(page, limit, type);
- } else {
- monitors = await this.monitorService.getAll();
- }
-
- res.status(200).json({
- message: "Monitors retrieved successfully",
- data: monitors,
- });
- } catch (error) {
- next(error);
- }
- };
-
- getChecks = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const tokenizedUser = req.user;
- if (!tokenizedUser) {
- return res.status(401).json({ message: "Unauthorized" });
- }
-
- const id = req.params.id;
- if (!id) {
- throw new ApiError("Monitor ID is required", 400);
- }
-
- const page = Number(req.query.page);
- const rowsPerPage = Number(req.query.rowsPerPage);
-
- if (isNaN(page)) throw new ApiError("Page query parameter must be a number", 400);
- if (isNaN(rowsPerPage)) throw new ApiError("rowsPerPage query parameter must be a number", 400);
-
- if (page < 0) throw new ApiError("Page must be greater than 0", 400);
- if (rowsPerPage < 0) throw new ApiError("rowsPerPage must be greater than 0", 400);
-
- const { count, checks } = await this.checkService.getChecks(id, page, rowsPerPage);
- res.status(200).json({
- message: "Checks retrieved successfully",
- data: { count, checks },
- });
- } catch (error) {
- next(error);
- }
- };
-
- toggleActive = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const tokenizedUser = req.user;
- if (!tokenizedUser) {
- return res.status(401).json({ message: "Unauthorized" });
- }
-
- const id = req.params.id;
- if (!id) {
- throw new ApiError("Monitor ID is required", 400);
- }
-
- const monitor = await this.monitorService.toggleActive(id, tokenizedUser);
- res.status(200).json({
- message: "Monitor paused/unpaused successfully",
- data: monitor,
- });
- } catch (error) {
- next(error);
- }
- };
-
- get = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const tokenizedUser = req.user;
- if (!tokenizedUser) {
- return res.status(401).json({ message: "Unauthorized" });
- }
-
- const id = req.params.id;
- if (!id) {
- throw new ApiError("Monitor ID is required", 400);
- }
-
- const range = req.query.range;
- if (!range || typeof range !== "string") throw new ApiError("Range query parameter is required", 400);
-
- let monitor;
-
- const status = req.query.status;
- if (status && typeof status !== "string") {
- throw new ApiError("Status query parameter must be a string", 400);
- }
-
- if (req.query.embedChecks === "true") {
- monitor = await this.monitorService.getEmbedChecks(id, range, status);
- } else {
- monitor = await this.monitorService.get(id);
- }
-
- res.status(200).json({
- message: "Monitor retrieved successfully",
- data: monitor,
- });
- } catch (error) {
- next(error);
- }
- };
-
- update = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const tokenizedUser = req.user;
- if (!tokenizedUser) {
- return res.status(401).json({ message: "Unauthorized" });
- }
-
- const id = req.params.id;
- if (!id) {
- throw new ApiError("Monitor ID is required", 400);
- }
-
- const monitor = await this.monitorService.update(tokenizedUser, id, req.body);
- res.status(200).json({
- message: "Monitor updated successfully",
- data: monitor,
- });
- } catch (error) {
- next(error);
- }
- };
-
- delete = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const tokenizedUser = req.user;
- if (!tokenizedUser) {
- return res.status(401).json({ message: "Unauthorized" });
- }
- const id = req.params.id;
- if (!id) {
- throw new ApiError("Monitor ID is required", 400);
- }
- await this.monitorService.delete(id);
-
- res.status(200).json({
- message: "Monitor deleted successfully",
- });
- } catch (error) {
- next(error);
- }
- };
-}
-
-export default MonitorController;
diff --git a/server/src/controllers/v2/NotificationChannelController.ts b/server/src/controllers/v2/NotificationChannelController.ts
deleted file mode 100644
index e13f572f4..000000000
--- a/server/src/controllers/v2/NotificationChannelController.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-import { Request, Response, NextFunction } from "express";
-import NotificationService from "../../service/v2/business/NotificationChannelService.js";
-
-class NotificationChannelController {
- private notificationService: NotificationService;
- constructor(notificationService: NotificationService) {
- this.notificationService = notificationService;
- }
-
- create = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const tokenizedUser = req.user;
- if (!tokenizedUser) {
- return res.status(401).json({ message: "Unauthorized" });
- }
- const channel = await this.notificationService.create(tokenizedUser, req.body);
- res.status(201).json({ message: "OK", data: channel });
- } catch (error) {
- next(error);
- }
- };
-
- getAll = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const notificationChannels = await this.notificationService.getAll();
- res.status(200).json({
- message: "OK",
- data: notificationChannels,
- });
- } catch (error) {
- next(error);
- }
- };
-
- toggleActive = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const tokenizedUser = req.user;
- if (!tokenizedUser) {
- return res.status(401).json({ message: "Unauthorized" });
- }
- const id = req.params.id;
- if (!id) {
- return res.status(400).json({ message: "ID parameter is required" });
- }
- const notificationChannel = await this.notificationService.toggleActive(tokenizedUser, id);
- res.status(200).json({ message: "OK", data: notificationChannel });
- } catch (error) {
- next(error);
- }
- };
-
- update = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const tokenizedUser = req.user;
- if (!tokenizedUser) {
- return res.status(401).json({ message: "Unauthorized" });
- }
- const id = req.params.id;
- if (!id) {
- return res.status(400).json({ message: "ID parameter is required" });
- }
- const updatedChannel = await this.notificationService.update(tokenizedUser, id, req.body);
- res.status(200).json({ message: "OK", data: updatedChannel });
- } catch (error) {
- next(error);
- }
- };
-
- get = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const id = req.params.id;
- if (!id) {
- return res.status(400).json({ message: "ID parameter is required" });
- }
- const notificationChannel = await this.notificationService.get(id);
- res.status(200).json({ message: "OK", data: notificationChannel });
- } catch (error) {
- next(error);
- }
- };
-
- delete = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const id = req.params.id;
- if (!id) {
- return res.status(400).json({ message: "ID parameter is required" });
- }
- await this.notificationService.delete(id);
- res.status(204).json({ message: "OK" });
- } catch (error) {
- next(error);
- }
- };
-}
-
-export default NotificationChannelController;
diff --git a/server/src/controllers/v2/QueueController.ts b/server/src/controllers/v2/QueueController.ts
deleted file mode 100644
index 510703894..000000000
--- a/server/src/controllers/v2/QueueController.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { Request, Response, NextFunction } from "express";
-import QueueService from "../../service/v2/business/QueueService.js";
-class QueueController {
- private queueService: QueueService;
- constructor(queueService: QueueService) {
- this.queueService = queueService;
- }
-
- getJobs = async (req: Request, res: Response, next: NextFunction) => {
- try {
- const jobs = await this.queueService.getJobs();
- res.status(200).json({ message: "ok", data: jobs });
- } catch (error) {
- next(error);
- }
- };
-
- getMetrics = async (req: Request, res: Response, next: NextFunction) => {
- const metrics = await this.queueService.getMetrics();
- res.status(200).json({ message: "ok", data: metrics });
- };
-
- flush = async (req: Request, res: Response, next: NextFunction) => {
- const result = await this.queueService.flush();
- res.status(200).json({ message: "ok", flushed: result });
- };
-}
-
-export default QueueController;
diff --git a/server/src/db/v1/MongoDB.js b/server/src/db/MongoDB.ts
similarity index 55%
rename from server/src/db/v1/MongoDB.js
rename to server/src/db/MongoDB.ts
index 3bdae3525..d62f4bbad 100755
--- a/server/src/db/v1/MongoDB.js
+++ b/server/src/db/MongoDB.ts
@@ -1,39 +1,18 @@
import mongoose from "mongoose";
import AppSettings from "./models/AppSettings.js";
import { runMigrations } from "./migration/index.js";
+import { ILogger } from "@/utils/logger.js";
+import { EnvConfig } from "@/service/system/settingsService.js";
+const SERVICE_NAME = "MongoDB";
class MongoDB {
- static SERVICE_NAME = "MongoDB";
+ static SERVICE_NAME = SERVICE_NAME;
- constructor({
- logger,
- envSettings,
- checkModule,
- inviteModule,
- statusPageModule,
- userModule,
- hardwareCheckModule,
- maintenanceWindowModule,
- monitorModule,
- networkCheckModule,
- notificationModule,
- pageSpeedCheckModule,
- recoveryModule,
- settingsModule,
- }) {
+ private logger: ILogger;
+ private envSettings: EnvConfig;
+
+ constructor(logger: ILogger, envSettings: EnvConfig) {
this.logger = logger;
this.envSettings = envSettings;
- this.userModule = userModule;
- this.inviteModule = inviteModule;
- this.recoveryModule = recoveryModule;
- this.pageSpeedCheckModule = pageSpeedCheckModule;
- this.hardwareCheckModule = hardwareCheckModule;
- this.checkModule = checkModule;
- this.maintenanceWindowModule = maintenanceWindowModule;
- this.monitorModule = monitorModule;
- this.notificationModule = notificationModule;
- this.settingsModule = settingsModule;
- this.statusPageModule = statusPageModule;
- this.networkCheckModule = networkCheckModule;
}
get serviceName() {
@@ -62,15 +41,15 @@ class MongoDB {
this.logger.info({
message: "Connected to MongoDB",
- service: this.SERVICE_NAME,
+ service: SERVICE_NAME,
method: "connect",
});
await runMigrations();
- } catch (error) {
+ } catch (error: any) {
this.logger.error({
message: error.message,
- service: this.SERVICE_NAME,
+ service: SERVICE_NAME,
method: "connect",
stack: error.stack,
});
@@ -80,14 +59,14 @@ class MongoDB {
disconnect = async () => {
try {
- this.logger.info({ message: "Disconnecting from MongoDB" });
+ this.logger.info({ message: "Disconnecting from MongoDB", service: SERVICE_NAME, method: "disconnect" });
await mongoose.disconnect();
- this.logger.info({ message: "Disconnected from MongoDB" });
+ this.logger.info({ message: "Disconnected from MongoDB", service: SERVICE_NAME, method: "disconnect" });
return;
- } catch (error) {
+ } catch (error: any) {
this.logger.error({
message: error.message,
- service: this.SERVICE_NAME,
+ service: SERVICE_NAME,
method: "disconnect",
stack: error.stack,
});
diff --git a/server/src/db/migration/0001_migrateStatusWindowThreshold.ts b/server/src/db/migration/0001_migrateStatusWindowThreshold.ts
new file mode 100644
index 000000000..d9dd12aa1
--- /dev/null
+++ b/server/src/db/migration/0001_migrateStatusWindowThreshold.ts
@@ -0,0 +1,10 @@
+import Monitor from "@/db/models/Monitor.js";
+async function migrateStatusWindowThreshold() {
+ const monitors = await Monitor.find({ statusWindowThreshold: { $lt: 1 } });
+ for (const monitor of monitors) {
+ monitor.statusWindowThreshold = monitor.statusWindowThreshold * 100;
+ await monitor.save();
+ }
+}
+
+export { migrateStatusWindowThreshold };
diff --git a/server/src/db/migration/0002_convertChecksToTimeSeries.ts b/server/src/db/migration/0002_convertChecksToTimeSeries.ts
new file mode 100644
index 000000000..222d210b0
--- /dev/null
+++ b/server/src/db/migration/0002_convertChecksToTimeSeries.ts
@@ -0,0 +1,184 @@
+import mongoose from "mongoose";
+import { BulkWriteResult } from "mongodb";
+
+const CHECKS_COLLECTION = "checks";
+const BACKUP_COLLECTION = "checks_backup";
+const FAILED_DOCS_COLLECTION = "checks_migration_failed";
+const BATCH_SIZE = 1000;
+
+interface MigrationStats {
+ totalSource: number;
+ totalMigrated: number;
+ totalFailed: number;
+}
+
+const getDb = () => {
+ const db = mongoose.connection.db;
+ if (!db) {
+ throw new Error("Database connection is not initialized");
+ }
+ return db;
+};
+
+const restoreFromBackup = async () => {
+ const db = getDb();
+ const backupExists = await db.listCollections({ name: BACKUP_COLLECTION }).toArray();
+ if (backupExists.length === 0) {
+ throw new Error("Cannot restore: backup collection does not exist");
+ }
+
+ const checksExists = await db.listCollections({ name: CHECKS_COLLECTION }).toArray();
+ if (checksExists.length > 0) {
+ await db.collection(CHECKS_COLLECTION).drop();
+ }
+
+ await db.collection(BACKUP_COLLECTION).rename(CHECKS_COLLECTION);
+};
+
+const backupAndDropExistingCollection = async () => {
+ const db = getDb();
+ const collections = await db.listCollections({ name: CHECKS_COLLECTION }).toArray();
+ if (collections.length === 0) {
+ return false;
+ }
+ const backupExists = await db.listCollections({ name: BACKUP_COLLECTION }).toArray();
+ if (backupExists.length > 0) {
+ throw new Error(`Backup collection "${BACKUP_COLLECTION}" already exists. ` + `Please remove it manually before running migration.`);
+ }
+
+ await db
+ .collection(CHECKS_COLLECTION)
+ .aggregate([{ $match: {} }, { $out: BACKUP_COLLECTION }])
+ .toArray();
+ await db.collection(CHECKS_COLLECTION).drop();
+ return true;
+};
+
+const createTimeSeriesCollection = async (backedUp: boolean) => {
+ const db = getDb();
+ try {
+ const existing = await db.listCollections({ name: CHECKS_COLLECTION }).toArray();
+ if (existing.length === 0) {
+ await db.createCollection(CHECKS_COLLECTION, {
+ timeseries: {
+ timeField: "createdAt",
+ metaField: "metadata",
+ granularity: "seconds",
+ },
+ });
+ }
+
+ await db
+ .collection(CHECKS_COLLECTION)
+ .createIndexes([
+ { key: { createdAt: 1 } },
+ { key: { "metadata.monitorId": 1, createdAt: 1 } },
+ { key: { "metadata.monitorId": 1, createdAt: -1 } },
+ { key: { "metadata.teamId": 1, createdAt: -1 } },
+ { key: { "metadata.monitorId": 1, "metadata.type": 1, createdAt: -1 } },
+ { key: { "metadata.teamId": 1, status: 1, createdAt: -1 } },
+ ]);
+ } catch (error) {
+ if (backedUp) {
+ await restoreFromBackup();
+ }
+ throw error;
+ }
+};
+
+const validateDocument = (doc: any): boolean => {
+ if (!doc.createdAt) {
+ return false;
+ }
+ if (!doc.monitorId || !doc.teamId || !doc.type) {
+ return false;
+ }
+ return true;
+};
+
+const storeFailedDocument = async (doc: any, reason: string) => {
+ const db = getDb();
+ await db.collection(FAILED_DOCS_COLLECTION).insertOne({
+ originalDoc: doc,
+ reason,
+ failedAt: new Date(),
+ });
+};
+
+const migrateBackupData = async (backedUp: boolean): Promise => {
+ const db = getDb();
+ const backupExists = await db.listCollections({ name: BACKUP_COLLECTION }).toArray();
+ if (backupExists.length === 0) {
+ return { totalSource: 0, totalMigrated: 0, totalFailed: 0 };
+ }
+
+ const stats: MigrationStats = {
+ totalSource: 0,
+ totalMigrated: 0,
+ totalFailed: 0,
+ };
+
+ try {
+ const source = db.collection(BACKUP_COLLECTION);
+ const target = db.collection(CHECKS_COLLECTION);
+
+ stats.totalSource = await source.countDocuments();
+
+ const cursor = source.find().addCursorFlag("noCursorTimeout", true);
+ const operations: any[] = [];
+ const invalidDocs: any[] = [];
+
+ while (await cursor.hasNext()) {
+ const doc = await cursor.next();
+ if (!doc) {
+ continue;
+ }
+
+ if (!validateDocument(doc)) {
+ invalidDocs.push(doc);
+ continue;
+ }
+
+ const { _id, monitorId, teamId, type, ...rest } = doc;
+ const metadata = { monitorId, teamId, type };
+ operations.push({ insertOne: { document: { ...rest, metadata } } });
+
+ if (operations.length >= BATCH_SIZE) {
+ const result: BulkWriteResult = await target.bulkWrite(operations, { ordered: false });
+ stats.totalMigrated += result.insertedCount;
+ operations.length = 0;
+ }
+ }
+
+ if (operations.length) {
+ const result: BulkWriteResult = await target.bulkWrite(operations, { ordered: false });
+ stats.totalMigrated += result.insertedCount;
+ }
+
+ for (const doc of invalidDocs) {
+ await storeFailedDocument(doc, "Missing required fields (createdAt, monitorId, teamId, or type)");
+ stats.totalFailed++;
+ }
+
+ await cursor.close();
+
+ return stats;
+ } catch (error) {
+ if (backedUp) {
+ await restoreFromBackup();
+ }
+ throw error;
+ }
+};
+
+export const convertChecksToTimeSeries = async () => {
+ const backedUp = await backupAndDropExistingCollection();
+ await createTimeSeriesCollection(backedUp);
+ if (backedUp) {
+ const stats = await migrateBackupData(backedUp);
+ console.log(
+ `Migration completed: ${stats.totalMigrated} documents migrated, ` +
+ `${stats.totalFailed} documents failed (stored in ${FAILED_DOCS_COLLECTION})`
+ );
+ }
+};
diff --git a/server/src/db/migration/index.ts b/server/src/db/migration/index.ts
new file mode 100644
index 000000000..c8e9e7c75
--- /dev/null
+++ b/server/src/db/migration/index.ts
@@ -0,0 +1,47 @@
+import { migrateStatusWindowThreshold } from "./0001_migrateStatusWindowThreshold.js";
+import { convertChecksToTimeSeries } from "./0002_convertChecksToTimeSeries.js";
+import MigrationModel from "../models/Migration.js";
+
+type MigrationEntry = {
+ name: string;
+ execute: () => Promise;
+};
+
+const migrations: MigrationEntry[] = [
+ { name: "0001_migrateStatusWindowThreshold", execute: migrateStatusWindowThreshold },
+ { name: "0002_convertChecksToTimeSeries", execute: convertChecksToTimeSeries },
+];
+
+const runMigrations = async (logger?: { info: Function; error: Function }) => {
+ try {
+ logger?.info({ message: "Running migrations", service: "Migrations" });
+ for (const migration of migrations) {
+ const exists = await MigrationModel.findOne({ name: migration.name, status: "completed" });
+ if (exists) {
+ logger?.info({ message: `Skipping ${migration.name}`, service: "Migrations" });
+ continue;
+ }
+
+ try {
+ await migration.execute();
+ await MigrationModel.findOneAndUpdate(
+ { name: migration.name },
+ { status: "completed", completedAt: new Date(), error: undefined },
+ { upsert: true }
+ );
+ logger?.info({ message: `Completed ${migration.name}`, service: "Migrations" });
+ } catch (error) {
+ const err = error as Error;
+ await MigrationModel.findOneAndUpdate({ name: migration.name }, { status: "failed", error: err?.message }, { upsert: true });
+ throw error;
+ }
+ }
+ logger?.info({ message: "Migrations completed", service: "Migrations" });
+ } catch (error) {
+ const err = error as Error;
+ logger?.error({ message: "Migration failed", service: "Migrations", details: err?.message, stack: err?.stack });
+ throw error;
+ }
+};
+
+export { runMigrations };
diff --git a/server/src/db/models/AppSettings.ts b/server/src/db/models/AppSettings.ts
new file mode 100644
index 000000000..9eeaa640a
--- /dev/null
+++ b/server/src/db/models/AppSettings.ts
@@ -0,0 +1,50 @@
+import { Schema, model, type Types } from "mongoose";
+import type { Settings, SettingsThresholds } from "@/types/settings.js";
+
+interface AppSettingsDocument extends Omit {
+ _id: Types.ObjectId;
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+const thresholdsSchema = new Schema(
+ {
+ cpu: { type: Number },
+ memory: { type: Number },
+ disk: { type: Number },
+ temperature: { type: Number },
+ },
+ { _id: false }
+);
+
+const AppSettingsSchema = new Schema(
+ {
+ checkTTL: { type: Number, default: 30 },
+ language: { type: String, default: "gb" },
+ jwtSecret: { type: String },
+ pagespeedApiKey: { type: String },
+ systemEmailHost: { type: String },
+ systemEmailPort: { type: Number },
+ systemEmailAddress: { type: String },
+ systemEmailPassword: { type: String },
+ systemEmailUser: { type: String },
+ systemEmailConnectionHost: { type: String, default: "localhost" },
+ systemEmailTLSServername: { type: String },
+ systemEmailSecure: { type: Boolean, default: false },
+ systemEmailPool: { type: Boolean, default: false },
+ systemEmailIgnoreTLS: { type: Boolean, default: false },
+ systemEmailRequireTLS: { type: Boolean, default: false },
+ systemEmailRejectUnauthorized: { type: Boolean, default: true },
+ showURL: { type: Boolean, default: false },
+ singleton: { type: Boolean, required: true, unique: true, default: true },
+ version: { type: Number, default: 1 },
+ globalThresholds: { type: thresholdsSchema },
+ },
+ { timestamps: true }
+);
+
+const AppSettingsModel = model("AppSettings", AppSettingsSchema);
+
+export type { AppSettingsDocument };
+export { AppSettingsModel };
+export default AppSettingsModel;
diff --git a/server/src/db/models/Check.ts b/server/src/db/models/Check.ts
new file mode 100644
index 000000000..4d9c499de
--- /dev/null
+++ b/server/src/db/models/Check.ts
@@ -0,0 +1,298 @@
+import { Schema, model, Types } from "mongoose";
+import { MonitorTypes, type MonitorType } from "@/types/monitor.js";
+import type {
+ Check,
+ CheckAudits,
+ CheckCaptureInfo,
+ CheckCpuInfo,
+ CheckDiskInfo,
+ CheckErrorInfo,
+ CheckHostInfo,
+ CheckMemoryInfo,
+ CheckMetadata,
+ CheckNetworkInterfaceInfo,
+ GotTimings,
+ ILighthouseAudit,
+} from "@/types/check.js";
+
+type CheckMetadataDocument = Omit & {
+ monitorId: Types.ObjectId;
+ teamId: Types.ObjectId;
+ type: MonitorType;
+};
+
+type CheckDocumentBase = Omit & {
+ metadata: CheckMetadataDocument;
+ expiry: Date;
+ ackAt?: Date | null;
+ createdAt: Date;
+ updatedAt: Date;
+ __v: number;
+};
+
+interface CheckDocument extends CheckDocumentBase {
+ _id: Types.ObjectId;
+}
+
+const timingPhasesSchema = new Schema(
+ {
+ wait: { type: Number, default: 0 },
+ dns: { type: Number, default: 0 },
+ tcp: { type: Number, default: 0 },
+ tls: { type: Number, default: 0 },
+ request: { type: Number, default: 0 },
+ firstByte: { type: Number, default: 0 },
+ download: { type: Number, default: 0 },
+ total: { type: Number, default: 0 },
+ },
+ { _id: false }
+);
+
+const timingsSchema = new Schema(
+ {
+ start: { type: Number, default: 0 },
+ socket: { type: Number, default: 0 },
+ lookup: { type: Number, default: 0 },
+ connect: { type: Number, default: 0 },
+ secureConnect: { type: Number, default: 0 },
+ upload: { type: Number, default: 0 },
+ response: { type: Number, default: 0 },
+ end: { type: Number, default: 0 },
+ phases: {
+ type: timingPhasesSchema,
+ default: () => ({}),
+ },
+ },
+ { _id: false }
+);
+
+const cpuSchema = new Schema(
+ {
+ physical_core: { type: Number, default: 0 },
+ logical_core: { type: Number, default: 0 },
+ frequency: { type: Number, default: 0 },
+ temperature: { type: [Number], default: [] },
+ free_percent: { type: Number, default: 0 },
+ usage_percent: { type: Number, default: 0 },
+ },
+ { _id: false }
+);
+
+const memorySchema = new Schema(
+ {
+ total_bytes: { type: Number, default: 0 },
+ available_bytes: { type: Number, default: 0 },
+ used_bytes: { type: Number, default: 0 },
+ usage_percent: { type: Number, default: 0 },
+ },
+ { _id: false }
+);
+
+const diskSchema = new Schema(
+ {
+ device: { type: String, default: "" },
+ mountpoint: { type: String, default: "" },
+ read_speed_bytes: { type: Number, default: 0 },
+ write_speed_bytes: { type: Number, default: 0 },
+ total_bytes: { type: Number, default: 0 },
+ free_bytes: { type: Number, default: 0 },
+ usage_percent: { type: Number, default: 0 },
+ },
+ { _id: false }
+);
+
+const hostSchema = new Schema(
+ {
+ os: { type: String, default: "" },
+ platform: { type: String, default: "" },
+ kernel_version: { type: String, default: "" },
+ },
+ { _id: false }
+);
+
+const errorSchema = new Schema(
+ {
+ metric: { type: [String], default: [] },
+ err: { type: String, default: "" },
+ },
+ { _id: false }
+);
+
+const captureSchema = new Schema(
+ {
+ version: { type: String, default: "" },
+ mode: { type: String, default: "" },
+ },
+ { _id: false }
+);
+
+const networkInterfaceSchema = new Schema(
+ {
+ name: { type: String, default: "" },
+ bytes_sent: { type: Number, default: 0 },
+ bytes_recv: { type: Number, default: 0 },
+ packets_sent: { type: Number, default: 0 },
+ packets_recv: { type: Number, default: 0 },
+ err_in: { type: Number, default: 0 },
+ err_out: { type: Number, default: 0 },
+ drop_in: { type: Number, default: 0 },
+ drop_out: { type: Number, default: 0 },
+ fifo_in: { type: Number, default: 0 },
+ fifo_out: { type: Number, default: 0 },
+ },
+ { _id: false }
+);
+
+const lighthouseAuditSchema = new Schema(
+ {
+ id: { type: String },
+ title: { type: String },
+ score: { type: Number },
+ displayValue: { type: String },
+ numericValue: { type: Number },
+ numericUnit: { type: String },
+ },
+ { _id: false }
+);
+
+const auditsSchema = new Schema(
+ {
+ cls: { type: lighthouseAuditSchema, default: undefined },
+ si: { type: lighthouseAuditSchema, default: undefined },
+ fcp: { type: lighthouseAuditSchema, default: undefined },
+ lcp: { type: lighthouseAuditSchema, default: undefined },
+ tbt: { type: lighthouseAuditSchema, default: undefined },
+ },
+ { _id: false }
+);
+
+const metadataSchema = new Schema(
+ {
+ monitorId: {
+ type: Schema.Types.ObjectId,
+ ref: "Monitor",
+ required: true,
+ immutable: true,
+ index: true,
+ },
+ teamId: {
+ type: Schema.Types.ObjectId,
+ ref: "Team",
+ required: true,
+ immutable: true,
+ index: true,
+ },
+ type: {
+ type: String,
+ enum: MonitorTypes,
+ required: true,
+ index: true,
+ },
+ },
+ { _id: false }
+);
+
+const CheckSchema = new Schema(
+ {
+ metadata: {
+ type: metadataSchema,
+ required: true,
+ },
+ status: {
+ type: Boolean,
+ index: true,
+ },
+ responseTime: {
+ type: Number,
+ },
+ timings: {
+ type: timingsSchema,
+ default: undefined,
+ },
+ statusCode: {
+ type: Number,
+ index: true,
+ },
+ message: {
+ type: String,
+ },
+ expiry: {
+ type: Date,
+ default: Date.now,
+ },
+ ack: {
+ type: Boolean,
+ default: false,
+ },
+ ackAt: {
+ type: Date,
+ default: undefined,
+ },
+ cpu: {
+ type: cpuSchema,
+ default: () => ({}),
+ },
+ memory: {
+ type: memorySchema,
+ default: () => ({}),
+ },
+ disk: {
+ type: [diskSchema],
+ default: () => [],
+ },
+ host: {
+ type: hostSchema,
+ default: () => ({}),
+ },
+ errors: {
+ type: [errorSchema],
+ default: () => [],
+ },
+ capture: {
+ type: captureSchema,
+ default: () => ({}),
+ },
+ net: {
+ type: [networkInterfaceSchema],
+ default: () => [],
+ },
+ accessibility: {
+ type: Number,
+ },
+ bestPractices: {
+ type: Number,
+ },
+ seo: {
+ type: Number,
+ },
+ performance: {
+ type: Number,
+ },
+ audits: {
+ type: auditsSchema,
+ default: undefined,
+ },
+ },
+ {
+ timestamps: true,
+ strict: false,
+ timeseries: {
+ timeField: "createdAt",
+ metaField: "metadata",
+ granularity: "seconds",
+ },
+ }
+);
+
+CheckSchema.index({ "metadata.monitorId": 1, createdAt: -1 });
+CheckSchema.index({ "metadata.monitorId": 1, createdAt: 1 });
+CheckSchema.index({ createdAt: 1 });
+CheckSchema.index({ "metadata.teamId": 1, createdAt: -1 });
+CheckSchema.index({ "metadata.monitorId": 1, "metadata.type": 1, createdAt: -1 });
+CheckSchema.index({ "metadata.teamId": 1, status: 1, createdAt: -1 });
+
+const CheckModel = model("Check", CheckSchema);
+
+export type { CheckDocument, CheckMetadataDocument };
+export { CheckModel };
+export default CheckModel;
diff --git a/server/src/db/models/Incident.ts b/server/src/db/models/Incident.ts
new file mode 100644
index 000000000..8de1c7c2c
--- /dev/null
+++ b/server/src/db/models/Incident.ts
@@ -0,0 +1,86 @@
+import { Schema, model, type Types } from "mongoose";
+import { IncidentResolutionTypes, type Incident } from "@/types/incident.js";
+
+type IncidentDocumentBase = Omit & {
+ monitorId: Types.ObjectId;
+ teamId: Types.ObjectId;
+ resolvedBy?: Types.ObjectId | null;
+ startTime: Date;
+ endTime: Date | null;
+ createdAt: Date;
+ updatedAt: Date;
+};
+
+export interface IncidentDocument extends IncidentDocumentBase {
+ _id: Types.ObjectId;
+}
+
+const IncidentSchema = new Schema(
+ {
+ monitorId: {
+ type: Schema.Types.ObjectId,
+ ref: "Monitor",
+ required: true,
+ immutable: true,
+ index: true,
+ },
+ teamId: {
+ type: Schema.Types.ObjectId,
+ ref: "Team",
+ required: true,
+ immutable: true,
+ index: true,
+ },
+ startTime: {
+ type: Date,
+ immutable: true,
+ required: true,
+ },
+ endTime: {
+ type: Date,
+ default: null,
+ },
+ status: {
+ type: Boolean,
+ default: true,
+ index: true,
+ },
+ message: {
+ type: String,
+ default: null,
+ },
+ statusCode: {
+ type: Number,
+ default: null,
+ index: true,
+ },
+ resolutionType: {
+ type: String,
+ enum: IncidentResolutionTypes,
+ default: null,
+ },
+ resolvedBy: {
+ type: Schema.Types.ObjectId,
+ ref: "User",
+ default: null,
+ },
+ comment: {
+ type: String,
+ default: null,
+ },
+ },
+ { timestamps: true }
+);
+
+IncidentSchema.index({ monitorId: 1, status: 1 });
+IncidentSchema.index({ teamId: 1, status: 1 });
+IncidentSchema.index({ teamId: 1, startTime: -1 });
+IncidentSchema.index({ status: 1, startTime: -1 });
+IncidentSchema.index({ resolutionType: 1, status: 1 });
+IncidentSchema.index({ resolvedBy: 1, status: 1 });
+IncidentSchema.index({ createdAt: -1 });
+
+const IncidentModel = model("Incident", IncidentSchema);
+
+export { IncidentModel };
+export default IncidentModel;
diff --git a/server/src/db/models/Invite.ts b/server/src/db/models/Invite.ts
new file mode 100755
index 000000000..25ddd9a72
--- /dev/null
+++ b/server/src/db/models/Invite.ts
@@ -0,0 +1,30 @@
+import { Schema, model, type Types } from "mongoose";
+import type { Invite } from "@/types/invite.js";
+
+type InviteDocumentBase = Omit & {
+ teamId: Types.ObjectId;
+ expiry: Date;
+};
+
+interface InviteDocument extends InviteDocumentBase {
+ _id: Types.ObjectId;
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+const InviteSchema = new Schema(
+ {
+ email: { type: String, required: true, unique: true },
+ teamId: { type: Schema.Types.ObjectId, ref: "Team", immutable: true, required: true },
+ role: { type: [String], required: true, default: ["user"] },
+ token: { type: String, required: true },
+ expiry: { type: Date, default: Date.now, expires: 3600 },
+ },
+ { timestamps: true }
+);
+
+const InviteModel = model("Invite", InviteSchema);
+
+export type { InviteDocument };
+export { InviteModel };
+export default InviteModel;
diff --git a/server/src/db/models/MaintenanceWindow.ts b/server/src/db/models/MaintenanceWindow.ts
new file mode 100644
index 000000000..b68de206e
--- /dev/null
+++ b/server/src/db/models/MaintenanceWindow.ts
@@ -0,0 +1,60 @@
+import { Schema, model, type Types } from "mongoose";
+import type { MaintenanceWindow } from "@/types/maintenanceWindow.js";
+
+type MaintenanceWindowDocumentBase = Omit & {
+ monitorId: Types.ObjectId;
+ teamId: Types.ObjectId;
+ start: Date;
+ end: Date;
+ expiry?: Date;
+ createdAt: Date;
+ updatedAt: Date;
+};
+
+interface MaintenanceWindowDocument extends MaintenanceWindowDocumentBase {
+ _id: Types.ObjectId;
+}
+
+const MaintenanceWindowSchema = new Schema(
+ {
+ monitorId: {
+ type: Schema.Types.ObjectId,
+ ref: "Monitor",
+ immutable: true,
+ },
+ teamId: {
+ type: Schema.Types.ObjectId,
+ ref: "Team",
+ immutable: true,
+ },
+ active: {
+ type: Boolean,
+ default: true,
+ },
+ name: {
+ type: String,
+ },
+ repeat: {
+ type: Number,
+ },
+ start: {
+ type: Date,
+ },
+ end: {
+ type: Date,
+ },
+ expiry: {
+ type: Date,
+ index: { expires: "0s" },
+ },
+ },
+ {
+ timestamps: true,
+ }
+);
+
+const MaintenanceWindowModel = model("MaintenanceWindow", MaintenanceWindowSchema);
+
+export type { MaintenanceWindowDocument };
+export { MaintenanceWindowModel };
+export default MaintenanceWindowModel;
diff --git a/server/src/db/models/Migration.ts b/server/src/db/models/Migration.ts
new file mode 100644
index 000000000..ba928d910
--- /dev/null
+++ b/server/src/db/models/Migration.ts
@@ -0,0 +1,13 @@
+import mongoose from "mongoose";
+
+const MigrationSchema = new mongoose.Schema(
+ {
+ name: { type: String, required: true, unique: true },
+ status: { type: String, enum: ["completed", "failed"], default: "completed" },
+ completedAt: { type: Date },
+ error: { type: String },
+ },
+ { timestamps: true }
+);
+
+export default mongoose.model("Migration", MigrationSchema);
diff --git a/server/src/db/v1/models/Monitor.js b/server/src/db/models/Monitor.ts
old mode 100755
new mode 100644
similarity index 58%
rename from server/src/db/v1/models/Monitor.js
rename to server/src/db/models/Monitor.ts
index e6e1a0024..24c8ef0bf
--- a/server/src/db/v1/models/Monitor.js
+++ b/server/src/db/models/Monitor.ts
@@ -1,18 +1,61 @@
-import mongoose from "mongoose";
+import { Schema, model, Types, type UpdateQuery } from "mongoose";
+import type { Monitor, MonitorMatchMethod, MonitorThresholds, CheckSnapshot } from "@/types/monitor.js";
+import { MonitorTypes } from "@/types/monitor.js";
import Check from "./Check.js";
import MonitorStats from "./MonitorStats.js";
import StatusPage from "./StatusPage.js";
-const MonitorSchema = mongoose.Schema(
+type CheckSnapshotDocument = Omit & { createdAt: Date };
+
+type MonitorDocumentBase = Omit<
+ Monitor,
+ "id" | "userId" | "teamId" | "notifications" | "selectedDisks" | "statusWindow" | "recentChecks" | "createdAt" | "updatedAt"
+> & {
+ statusWindow: boolean[];
+ recentChecks: CheckSnapshotDocument[];
+ notifications: Types.ObjectId[];
+ selectedDisks: string[];
+ matchMethod?: MonitorMatchMethod;
+ thresholds?: MonitorThresholds;
+};
+
+interface MonitorDocument extends MonitorDocumentBase {
+ _id: Types.ObjectId;
+ userId: Types.ObjectId;
+ teamId: Types.ObjectId;
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+const thresholdsSchema = new Schema(
+ {
+ usage_cpu: { type: Number },
+ usage_memory: { type: Number },
+ usage_disk: { type: Number },
+ usage_temperature: { type: Number },
+ },
+ { _id: false }
+);
+
+const checkSnapshotSchema = new Schema(
+ {
+ id: { type: String, required: true },
+ status: { type: Boolean, required: true },
+ createdAt: { type: Date, required: true },
+ },
+ { _id: false, strict: false }
+);
+
+const MonitorSchema = new Schema(
{
userId: {
- type: mongoose.Schema.Types.ObjectId,
+ type: Schema.Types.ObjectId,
ref: "User",
immutable: true,
required: true,
},
teamId: {
- type: mongoose.Schema.Types.ObjectId,
+ type: Schema.Types.ObjectId,
ref: "Team",
immutable: true,
required: true,
@@ -43,7 +86,7 @@ const MonitorSchema = mongoose.Schema(
type: {
type: String,
required: true,
- enum: ["http", "ping", "pagespeed", "hardware", "docker", "port", "game"],
+ enum: MonitorTypes,
},
ignoreTlsErrors: {
type: Boolean,
@@ -71,7 +114,6 @@ const MonitorSchema = mongoose.Schema(
default: true,
},
interval: {
- // in milliseconds
type: Number,
default: 60000,
},
@@ -81,7 +123,7 @@ const MonitorSchema = mongoose.Schema(
},
notifications: [
{
- type: mongoose.Schema.Types.ObjectId,
+ type: Schema.Types.ObjectId,
ref: "Notification",
},
],
@@ -89,13 +131,7 @@ const MonitorSchema = mongoose.Schema(
type: String,
},
thresholds: {
- type: {
- usage_cpu: { type: Number },
- usage_memory: { type: Number },
- usage_disk: { type: Number },
- usage_temperature: { type: Number },
- },
- _id: false,
+ type: thresholdsSchema,
},
alertThreshold: {
type: Number,
@@ -125,6 +161,10 @@ const MonitorSchema = mongoose.Schema(
return this.alertThreshold;
},
},
+ selectedDisks: {
+ type: [String],
+ default: [],
+ },
gameId: {
type: String,
},
@@ -133,49 +173,20 @@ const MonitorSchema = mongoose.Schema(
trim: true,
maxLength: 50,
default: null,
- set: function (value) {
+ set(value: string | null) {
return value && value.trim() ? value.trim() : null;
},
},
+ recentChecks: {
+ type: [checkSnapshotSchema],
+ default: [],
+ },
},
{
timestamps: true,
}
);
-MonitorSchema.pre("findOneAndDelete", async function (next) {
- // Delete checks and stats
- try {
- const doc = await this.model.findOne(this.getFilter());
-
- if (!doc) {
- throw new Error("Monitor not found");
- }
-
- await Check.deleteMany({ monitorId: doc._id });
-
- // Deal with status pages
- await StatusPage.updateMany({ monitors: doc?._id }, { $pull: { monitors: doc?._id } });
-
- await MonitorStats.deleteMany({ monitorId: doc?._id.toString() });
- next();
- } catch (error) {
- next(error);
- }
-});
-
-MonitorSchema.pre("deleteMany", async function (next) {
- const filter = this.getFilter();
- const monitors = await this.model.find(filter).select(["_id", "type"]).lean();
-
- for (const monitor of monitors) {
- await Check.deleteMany({ monitorId: monitor._id });
- await StatusPage.updateMany({ monitors: monitor._id }, { $pull: { monitors: monitor._id } });
- await MonitorStats.deleteMany({ monitorId: monitor._id.toString() });
- }
- next();
-});
-
MonitorSchema.pre("save", function (next) {
if (!this.cpuAlertThreshold || this.isModified("alertThreshold")) {
this.cpuAlertThreshold = this.alertThreshold;
@@ -193,8 +204,8 @@ MonitorSchema.pre("save", function (next) {
});
MonitorSchema.pre("findOneAndUpdate", function (next) {
- const update = this.getUpdate();
- if (update.alertThreshold) {
+ const update = this.getUpdate() as UpdateQuery | null;
+ if (update && !Array.isArray(update) && update.alertThreshold !== undefined) {
update.cpuAlertThreshold = update.alertThreshold;
update.memoryAlertThreshold = update.alertThreshold;
update.diskAlertThreshold = update.alertThreshold;
@@ -205,4 +216,8 @@ MonitorSchema.pre("findOneAndUpdate", function (next) {
MonitorSchema.index({ teamId: 1, type: 1 });
-export default mongoose.model("Monitor", MonitorSchema);
+const MonitorModel = model("Monitor", MonitorSchema);
+
+export type { MonitorDocument, CheckSnapshotDocument };
+export { MonitorModel };
+export default MonitorModel;
diff --git a/server/src/db/models/MonitorStats.ts b/server/src/db/models/MonitorStats.ts
new file mode 100755
index 000000000..d1d49ca9f
--- /dev/null
+++ b/server/src/db/models/MonitorStats.ts
@@ -0,0 +1,63 @@
+import { Schema, model, type Types } from "mongoose";
+import type { MonitorStats as MonitorStatsEntity } from "@/types/monitorStats.js";
+
+type MonitorStatsDocumentBase = Omit & {
+ monitorId: Types.ObjectId;
+};
+
+interface MonitorStatsDocument extends MonitorStatsDocumentBase {
+ _id: Types.ObjectId;
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+const MonitorStatsSchema = new Schema(
+ {
+ monitorId: {
+ type: Schema.Types.ObjectId,
+ ref: "Monitor",
+ immutable: true,
+ index: true,
+ required: true,
+ },
+ avgResponseTime: {
+ type: Number,
+ default: 0,
+ },
+ totalChecks: {
+ type: Number,
+ default: 0,
+ },
+ totalUpChecks: {
+ type: Number,
+ default: 0,
+ },
+ totalDownChecks: {
+ type: Number,
+ default: 0,
+ },
+ uptimePercentage: {
+ type: Number,
+ default: 0,
+ },
+ lastCheckTimestamp: {
+ type: Number,
+ default: 0,
+ },
+ lastResponseTime: {
+ type: Number,
+ default: 0,
+ },
+ timeOfLastFailure: {
+ type: Number,
+ default: undefined,
+ },
+ },
+ { timestamps: true }
+);
+
+const MonitorStatsModel = model("MonitorStats", MonitorStatsSchema);
+
+export type { MonitorStatsDocument };
+export { MonitorStatsModel };
+export default MonitorStatsModel;
diff --git a/server/src/db/models/Notification.ts b/server/src/db/models/Notification.ts
new file mode 100755
index 000000000..97e2d43fb
--- /dev/null
+++ b/server/src/db/models/Notification.ts
@@ -0,0 +1,50 @@
+import { Schema, model, type Types } from "mongoose";
+import type { Notification, NotificationChannel } from "@/types/notification.js";
+
+interface NotificationDocument extends Omit {
+ _id: Types.ObjectId;
+ userId: Types.ObjectId;
+ teamId: Types.ObjectId;
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+const NotificationSchema = new Schema(
+ {
+ userId: {
+ type: Schema.Types.ObjectId,
+ ref: "User",
+ immutable: true,
+ required: true,
+ },
+ teamId: {
+ type: Schema.Types.ObjectId,
+ ref: "Team",
+ immutable: true,
+ required: true,
+ },
+ type: {
+ type: String,
+ enum: ["email", "slack", "discord", "webhook", "pager_duty", "matrix"] as NotificationChannel[],
+ required: true,
+ },
+ notificationName: {
+ type: String,
+ required: true,
+ },
+ address: { type: String },
+ phone: { type: String },
+ homeserverUrl: { type: String },
+ roomId: { type: String },
+ accessToken: { type: String },
+ },
+ {
+ timestamps: true,
+ }
+);
+
+const NotificationModel = model("Notification", NotificationSchema);
+
+export type { NotificationDocument };
+export { NotificationModel };
+export default NotificationModel;
diff --git a/server/src/db/models/RecoveryToken.ts b/server/src/db/models/RecoveryToken.ts
new file mode 100644
index 000000000..07b697d9e
--- /dev/null
+++ b/server/src/db/models/RecoveryToken.ts
@@ -0,0 +1,27 @@
+import { Schema, model, type Types } from "mongoose";
+import type { RecoveryToken as RecoveryTokenEntity } from "@/types/recoveryToken.js";
+
+type RecoveryTokenDocumentBase = Omit