Compare commits

...
144 Commits
Author SHA1 Message Date
Alexander Holliday a924227805 Merge pull request #2964 from bluewave-labs/develop
develop -> master
2025-09-18 13:40:45 -07:00
Alexander Holliday e4e75632c5 Merge pull request #2963 from bluewave-labs/translation-update-78
🌐 Updated Translations from POEditor
2025-09-18 13:40:10 -07:00
GitHub Action ddb7c24832 feat: translations updated from POEditor 2025-09-18 20:25:11 +00:00
Alexander Holliday 2144d3f8c0 Merge pull request #2962 from bluewave-labs/develop
develop -> master
2025-09-18 12:04:51 -07:00
Alexander Holliday 4a824dec81 Merge pull request #2960 from bluewave-labs/feat/bump-version
bump version
2025-09-18 12:03:26 -07:00
Alex Holliday b35fee1be8 bump version 2025-09-18 12:02:49 -07:00
Alexander Holliday 1b167af12b Merge pull request #2959 from Owaiseimdad/Addition-of-sliding-window-implementation
Addition of incident tab in creation and config page which is same.
2025-09-18 11:50:55 -07:00
Owaise f2962e34ae Fixed the formatting. 2025-09-19 00:06:31 +05:30
Owaise 386db5029a Addition of incident tab in creation and config page which is same. 2025-09-19 00:03:18 +05:30
Alexander Holliday 0e6e54a124 Merge pull request #2957 from FrancocDev/develop
Allow jsonPath to be used along expectedValue and validate the response.
2025-09-18 09:47:53 -07:00
Franco Carrara c666cb3879 fix formatting issues 2025-09-17 23:46:20 -03:00
Franco Carrara 4039517683 Add response messages to string service 2025-09-17 23:33:45 -03:00
Franco Carrara 00fc7b8038 Allow jsonPath to be used along expectedValue and validate the response. 2025-09-17 23:23:16 -03:00
Alexander Holliday 81d283ec8d Merge pull request #2956 from ajhollid/bump-dependencies
chore: bump deps
2025-09-17 15:17:14 -07:00
Alex Holliday 105100e349 bump deps 2025-09-17 15:07:55 -07:00
Alexander Holliday 4598690dbf Merge pull request #2954 from brownyroll/develop
feat: Support Thailand language
2025-09-16 09:00:51 -07:00
Bamz Kittisak Udomsri f68341c315 feat: thailang 2025-09-16 20:41:13 +07:00
Alexander Holliday e8a8cea909 Merge pull request #2953 from bluewave-labs/fix-skip-maintenance-notifications
Fix: Skip notifications during maintenance windows
2025-09-15 11:13:28 -07:00
karenvicent bf53f107e3 Merge branch 'develop' into fix-skip-maintenance-notifications 2025-09-15 12:29:47 -04:00
karenvicent 00de974528 Fix: Skip notifications for monitors during active maintenance windows 2025-09-15 12:28:37 -04:00
Alexander Holliday 0445398e94 Merge pull request #2952 from bluewave-labs/feat-notifications-body
Add monitor name and URL formatting for hardware notifications
2025-09-15 09:15:10 -07:00
karenvicent 272e2ba955 add monitor name and url to infrastructure alert 2025-09-15 08:34:51 -04:00
Alexander Holliday 0a2adc2d92 Merge pull request #2948 from bluewave-labs/feat-discord-format
Add Discord embed message formatting for notifications
2025-09-12 09:03:19 -07:00
karenvicent 7008fc1770 Merge branch 'develop' into feat-discord-format 2025-09-12 10:14:07 -04:00
karenvicent 041a261540 add llama sugestions 2025-09-12 10:13:38 -04:00
Gorkem Cetin (BWL) 1008d819f7 Update README.md 2025-09-11 22:40:11 -04:00
karenvicent fb84a79a04 Add Discord embed message formatting for notifications 2025-09-11 16:29:08 -04:00
Alexander Holliday fcd6786149 Merge pull request #2936 from bluewave-labs/develop
develop -> master
2025-09-08 20:43:21 -07:00
Alexander Holliday 45bfdbdce0 Merge pull request #2935 from bluewave-labs/fix/mono-dist-ping
fix vite.config
2025-09-08 20:40:33 -07:00
Alex Holliday 1537c2141f fix vite.config 2025-09-08 20:39:45 -07:00
Alexander Holliday ea968ebf3c Merge pull request #2933 from bluewave-labs/develop
develop -> master
2025-09-08 20:33:35 -07:00
Alexander Holliday 3ba1a52644 Merge pull request #2932 from bluewave-labs/fix/mono-dist-ping
fix: mono dist ping
2025-09-08 20:31:01 -07:00
Alex Holliday a015cc6706 fix dist-arm, bump version 2025-09-08 20:29:38 -07:00
Alex Holliday 5bad362793 fix server build order 2025-09-08 20:27:20 -07:00
Alexander Holliday 5520756edd Merge pull request #2923 from bluewave-labs/feat-page-initialization-hook
Add PageStateWrapper and fix initial render flash
2025-09-08 09:12:14 -07:00
karenvicent ba954a1d6e run format 2025-09-08 11:15:14 -04:00
karenvicent 2944ff298c Merge branch 'develop' into feat-page-initialization-hook 2025-09-08 10:50:15 -04:00
karenvicent 80d121e87a add llama suggestions 2025-09-08 10:49:42 -04:00
Alexander Holliday 8a7b852974 Merge pull request #2929 from bluewave-labs/develop
develop -> master
2025-09-08 07:24:16 -07:00
Alexander Holliday ed8352d857 Merge pull request #2928 from bluewave-labs/fix/install-ping
fix: install ping
2025-09-08 07:11:34 -07:00
Alex Holliday 41f64ca9fe bump version 2025-09-08 07:10:29 -07:00
Alex Holliday 738e49dc92 Merge branch 'develop' into fix/install-ping 2025-09-08 07:10:15 -07:00
Alex Holliday 68a16257c6 add ping to dist 2025-09-08 07:09:39 -07:00
Alex Holliday 5dcbae55f4 install ping 2025-09-08 06:57:10 -07:00
Alexander Holliday e0d963256c Merge pull request #2926 from bluewave-labs/fix/bump-version
fix: bump version
2025-09-08 06:45:15 -07:00
Alexander Holliday 2bff4bbd15 Merge pull request #2927 from bluewave-labs/fix/ping-error-logging
fix: throw ping error if present
2025-09-08 06:45:00 -07:00
Alex Holliday a0eba89f34 bump version 2025-09-08 06:43:35 -07:00
Alex Holliday 4e78de9da2 throw error if present 2025-09-08 06:42:36 -07:00
karenvicent 85f00eb7e8 Merge branch 'develop' into feat-page-initialization-hook 2025-09-05 15:59:14 -04:00
karenvicent 8e13855b66 feat create pagestate wrapper 2025-09-05 15:53:22 -04:00
Alexander Holliday 06f49e24c2 Merge pull request #2867 from antonstrover/fix/uptime%-charts-toggles
fix(status-page): toggles for charts and uptime %
2025-09-05 09:08:40 -07:00
antonstrover e598ae45f7 Merge branch 'develop' into fix/uptime%-charts-toggles 2025-09-05 16:46:04 +01:00
anton.strover ad7f469e06 add display name for bar 2025-09-05 16:27:48 +01:00
Alexander Holliday c8d3e3b70c Merge pull request #2921 from bluewave-labs/develop
develop -> master
2025-09-04 15:46:30 -07:00
Alexander Holliday cb57ff5cc2 Merge pull request #2920 from bluewave-labs/fix/arm
fix/arm
2025-09-04 15:44:44 -07:00
Alex Holliday 72f070fbcb fix multiarch build 2025-09-04 15:15:09 -07:00
Alex Holliday fb0a86d37c update docker compoes and docker file 2025-09-04 13:39:53 -07:00
Alexander Holliday 36402dfefa Merge pull request #2870 from bluewave-labs/feat/add-team-member
Add direct user registration for team management
2025-09-03 09:58:35 -07:00
karenvicent c61874dbdd fix format error 2025-09-03 12:34:35 -04:00
Alex Holliday 20848f28d5 package 2025-09-03 09:21:24 -07:00
Alexander Holliday 5d23966f00 Merge pull request #2919 from bluewave-labs/fix/format-action
update format action, no need for ci
2025-09-03 09:20:43 -07:00
Alex Holliday 294cc66e4a update format action, no need for ci 2025-09-03 09:20:15 -07:00
Alex Holliday 0fd2e7032a update package-lock 2025-09-03 09:17:19 -07:00
Alex Holliday 340b85043e Merge branch 'feat/add-team-member' of github.com:bluewave-labs/checkmate into feat/add-team-member 2025-09-03 09:15:22 -07:00
Alex Holliday 1821ae3c2b add root gitignore 2025-09-03 09:14:30 -07:00
karenvicent c536cb2afa run format 2025-09-03 10:18:09 -04:00
karenvicent 3f79b4c2f6 Merge branch 'develop' into feat/add-team-member 2025-09-03 09:56:31 -04:00
karenvicent 137ba9185e fix details 2025-09-03 09:55:53 -04:00
Alexander Holliday 2f6a0913dc Merge pull request #2917 from bluewave-labs/fix/bump-scheduler
bump scheduler
2025-09-02 10:35:09 -07:00
Alex Holliday 2c7336d440 bump scheduler 2025-09-02 10:34:33 -07:00
Gorkem Cetin (BWL) 5a022ff3f6 Create .coderabbit.yml so it doesnt add summary to the PR 2025-09-01 19:33:33 -04:00
Alexander Holliday fe4855500e Merge pull request #2896 from PaiJi/feat/update-i18n-scripts
feat(i18n): add zh-CN
2025-09-01 10:46:21 -07:00
Alexander Holliday ce8f5cbe0c Merge pull request #2878 from antonstrover/fix/failed-prop-type-status
Fixed Bar component
2025-09-01 10:43:43 -07:00
Alexander Holliday 7a90a6f9f4 Merge pull request #2915 from bluewave-labs/develop
develop->master
2025-09-01 10:33:09 -07:00
Alexander Holliday b6bebe33fe Merge branch 'master' into develop 2025-09-01 10:32:59 -07:00
Alexander Holliday 9cf8072c1e Merge pull request #2914 from bluewave-labs/fix/bump-multiarch-node
Fix/bump multiarch node
2025-09-01 10:30:51 -07:00
Alex Holliday 0ca00ca6a1 esbuild 2025-09-01 10:28:41 -07:00
Alex Holliday 619c5242db bump versions 2025-09-01 10:14:57 -07:00
Alexander Holliday ff07854590 Merge pull request #2913 from bluewave-labs/revert-2910-fix/alpine-to-slim
Revert "pin dependency"
2025-09-01 09:27:03 -07:00
Alexander Holliday ba99989c66 Revert "pin dependency" 2025-09-01 09:26:51 -07:00
Alexander Holliday 3c75933cf2 Merge pull request #2912 from bluewave-labs/revert-2911-develop
Revert "develop->master"
2025-09-01 09:26:35 -07:00
Alexander Holliday ea9ef7afd3 Revert "develop->master" 2025-09-01 09:26:23 -07:00
Alexander Holliday f5e633a781 Merge pull request #2911 from bluewave-labs/develop
develop->master
2025-09-01 09:23:01 -07:00
Alexander Holliday 90b94816dd Merge pull request #2910 from bluewave-labs/fix/alpine-to-slim
pin dependency
2025-09-01 09:22:26 -07:00
Alex Holliday 11dcaef3ff pin 2025-09-01 09:21:56 -07:00
Alex Holliday 3f3e7f71ec pin dependency 2025-09-01 09:21:11 -07:00
Alexander Holliday b6070572cf Merge pull request #2909 from bluewave-labs/develop
develop->master
2025-09-01 09:15:48 -07:00
Alexander Holliday e818a02a2a Merge pull request #2908 from bluewave-labs/fix/alpine-to-slim
fix build
2025-09-01 09:15:17 -07:00
Alex Holliday d8c8e222c8 fix build 2025-09-01 09:14:35 -07:00
Alexander Holliday f12b9715bc Merge pull request #2907 from bluewave-labs/develop
develop -> master
2025-09-01 09:07:57 -07:00
Alexander Holliday 31c65d3e0b Merge pull request #2906 from bluewave-labs/fix/alpine-to-slim
add omit optional flag
2025-09-01 09:07:21 -07:00
Alex Holliday fcd6741cf9 add omit optional flag 2025-09-01 09:06:42 -07:00
Alexander Holliday d663b9230f Merge pull request #2905 from bluewave-labs/develop
Develop -> Master
2025-09-01 09:00:25 -07:00
Alexander Holliday 5f2d61ad7a Merge pull request #2904 from bluewave-labs/fix/alpine-to-slim
remove alpine fix
2025-09-01 08:58:42 -07:00
Alex Holliday ec80a2dc0b remove alpine fix 2025-09-01 08:57:55 -07:00
Alexander Holliday 2cece3de59 Merge pull request #2902 from bluewave-labs/develop
develop -> master
2025-09-01 08:54:38 -07:00
Alexander Holliday 79eb580802 Merge pull request #2901 from bluewave-labs/fix/alpine-to-slim
alpine -> slim
2025-09-01 08:54:10 -07:00
Alex Holliday b93e3327a7 alpine -> slim 2025-09-01 08:53:03 -07:00
anton.strover f01030a47a move width out of sx object 2025-09-01 11:44:10 +01:00
JiPai 3cbd907c09 feat(i18n): add zh-CN 2025-09-01 04:53:24 +08:00
karenvicent 8fae69b7a4 Merge branch 'develop' into feat/add-team-member 2025-08-31 10:19:01 -04:00
karenvicent e45dd4240e fix errors 2025-08-31 10:18:15 -04:00
Alexander Holliday 16a8552e6c Merge pull request #2890 from bluewave-labs/develop
develop->master
2025-08-30 19:31:37 -07:00
Alexander Holliday c953ec6152 Merge pull request #2889 from bluewave-labs/fix/package-lock
regenerate package-lock
2025-08-30 19:31:02 -07:00
Alex Holliday a433f10240 regenerate package-lock 2025-08-30 19:30:24 -07:00
Alexander Holliday b73b97b5e9 Merge pull request #2887 from bluewave-labs/feat/add-maintenance-window-description
Update maintenance window description
2025-08-30 19:01:45 -07:00
gorkem-bwl 0607e0b228 refactor: simplify maintenance window description per feedback
- Remove redundant words 'active' and 'completely'
- Remove technical details about specific check types
- Keep the description concise while maintaining clarity
- Maintain all key information about maintenance behavior
2025-08-30 21:57:17 -04:00
gorkem-bwl 3bd4f67ec7 docs: add comprehensive maintenance window behavior description
- Update maintenance window description to clearly explain what happens during maintenance
- Clarify that ALL monitoring types are suspended (pings, HTTP, port checks, etc.)
- Explain that no notifications or alerts will be triggered
- Describe status page behavior (maintenance indicator shown)
- Note that monitoring resumes automatically when maintenance ends
- Clarify that maintenance periods don't affect uptime calculations
- Provide users with complete understanding of maintenance window impact
2025-08-30 21:31:44 -04:00
Alexander Holliday 845d5a5d51 Merge pull request #2885 from bluewave-labs/develop
develop->master
2025-08-30 16:23:40 -07:00
Alexander Holliday 0101da731f Merge pull request #2884 from bluewave-labs/fix/bump-version
bump version
2025-08-30 16:23:03 -07:00
Alex Holliday 68592acd39 bump version 2025-08-30 16:22:16 -07:00
Alexander Holliday b86fc5edb3 Merge pull request #2883 from bluewave-labs/develop
develop -> master
2025-08-30 16:21:19 -07:00
Alexander Holliday 32b54ddf11 Merge pull request #2882 from bluewave-labs/feat/migration
add migration for statusWindowThreshold
2025-08-30 16:20:23 -07:00
Alex Holliday f8a0741e6c <= -> < 2025-08-30 16:19:17 -07:00
Alex Holliday 652d231771 add migration for statusWindowThreshold 2025-08-30 16:07:03 -07:00
Alexander Holliday 1c3c5f4fec Merge pull request #2881 from bluewave-labs/fix/queue
fix: queue
2025-08-30 11:30:38 -07:00
Alex Holliday e7006a4a44 fix default value 2025-08-30 11:28:54 -07:00
Alex Holliday 0f81d54f25 remove success toast 2025-08-30 11:18:05 -07:00
Alex Holliday f3121c24c6 add interval to queue table 2025-08-30 11:17:34 -07:00
Alex Holliday 7abd0f2cd4 add root level gitignore 2025-08-30 11:13:53 -07:00
Alex Holliday 46b342005a update monitor interval and data 2025-08-30 11:11:58 -07:00
Alex Holliday 4e0349bd58 bump queue 2025-08-30 11:11:19 -07:00
Alexander Holliday 30a569233b Merge pull request #2873 from bluewave-labs/feat/docker-container-name-monitoring
feat: Enable Docker container monitoring by name in addition to ID
2025-08-29 09:33:45 -07:00
Alex Holliday 44ba6b4d0f early returns 2025-08-29 09:22:02 -07:00
Alexander Holliday 9065070eb2 Merge pull request #2871 from bluewave-labs/feat/incident-tooltip
feat:incident tooltip
2025-08-29 09:07:35 -07:00
anton.strover fd951bfdd5 Fixed Bar component
Moved width prop from direct prop to sx prop, updated PropTypes to accept string|object.
2025-08-29 11:24:57 +01:00
anton.strover 27d613b594 remove carried away-ness 2025-08-29 10:49:22 +01:00
gorkem-bwl ba757b9aa4 style: fix prettier formatting in validation.js 2025-08-29 00:58:03 -04:00
gorkem-bwlandClaude c445463c0c refactor: improve Docker container matching with deterministic priority-based logic
Address code review feedback to eliminate ambiguity issues and improve reliability:

**Backend Improvements:**
- Implement priority-based matching: exact ID → exact name → partial ID
- Add input normalization (strip leading slashes, case-insensitive)
- Add ambiguity detection with warning logs for debugging
- Maintain full backwards compatibility with existing monitors

**Frontend Improvements:**
- Update validation regex to support leading slashes (/container-name)
- Improve error message clarity for user guidance

**Resolves Ambiguity Issues:**
- Container name "abc" vs Container ID "abc123..." now deterministic
- Multiple containers with similar names handled predictably
- Case-insensitive matching for better user experience

**Priority Order:**
1. Exact full ID match (64-char) - highest priority
2. Exact container name match - case-insensitive
3. Partial ID match - backwards compatibility fallback

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-29 00:53:40 -04:00
gorkem-bwlandClaude 4693177e4b feat: add Docker container monitoring by name
- Update Docker monitoring to support both container names and IDs
- Maintain backward compatibility with existing container ID monitoring
- Add logic to find containers by name (with or without "/" prefix)
- Update frontend UI labels and placeholders to reflect name/ID support
- Update instructions to clarify both name and ID are supported

Resolves user request: "It would be great if we could monitor docker containers by name and not just ID - given that the ID changes when containers are taken down for any reason"

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-29 00:27:01 -04:00
Alex Holliday 30b135eba3 add tooltip to incident table 2025-08-28 12:21:34 -07:00
Alex Holliday bda48e07f1 add optional tooltip 2025-08-28 12:21:23 -07:00
karenvicent 7c8ab64e34 fix sucess toast msg 2025-08-28 13:38:05 -04:00
karenvicent ec7855bd02 Merge branch 'develop' into feat/add-team-member 2025-08-28 13:21:20 -04:00
karenvicent d401eefd6b Add direct user registration 2025-08-28 12:58:38 -04:00
Alexander Holliday 0ad759a384 Merge pull request #2865 from bluewave-labs/fix/status-page-deletion-error
Fix status page deletion error
2025-08-27 14:49:45 -07:00
gorkem-bwl d3d20b29c3 Fix status page deletion error - missing module reference
- Fix deleteStatusPage method call to use statusPageModule
- Change this.db.deleteStatusPage to this.db.statusPageModule.deleteStatusPage
- Ensures consistency with other controller methods
- Resolves "this.db.deleteStatusPage is not a function" error

Fixes bug where status page deletion failed due to incorrect database module reference.
2025-08-27 17:47:11 -04:00
Alexander Holliday fbb570c0d5 Merge pull request #2862 from bluewave-labs/fix/advanced-matching
fix: advanced matching
2025-08-26 14:56:20 -07:00
Alex Holliday 2318082bb8 fix advanced matching 2025-08-26 14:54:24 -07:00
Alexander Holliday 05dd5f7de2 Merge pull request #2858 from bluewave-labs/develop
develop -> master
2025-08-25 15:06:19 -07:00
Alexander Holliday 841251d09e Merge pull request #2857 from bluewave-labs/fix/model-types
fix/model types
2025-08-25 15:05:41 -07:00
Alexander Holliday a4dd8f70f0 Merge branch 'develop' into fix/model-types 2025-08-25 15:05:30 -07:00
Alex Holliday 9d0d1329b6 bump version 2025-08-25 15:04:44 -07:00
Alex Holliday 7a929777b4 fix model types 2025-08-25 15:03:31 -07:00
83 changed files with 4563 additions and 1584 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
release_notes: false
+1 -1
View File
@@ -7,7 +7,7 @@ import { URLSearchParams } from "url";
const API_TOKEN = process.env.POEDITOR_API;
const PROJECT_ID = process.env.POEDITOR_PROJECT_ID;
const LANGUAGES = (
process.env.LANGUAGES || "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja"
process.env.LANGUAGES || "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja,zh-cn,th"
).split(",");
const EXPORT_FORMAT = process.env.EXPORT_FORMAT || "key_value_json";
+2 -2
View File
@@ -16,7 +16,7 @@ jobs:
- name: Install client dependencies
working-directory: client
run: npm ci
run: npm install
- name: Check client formatting
working-directory: client
@@ -34,7 +34,7 @@ jobs:
- name: Install server dependencies
working-directory: server
run: npm ci
run: npm install
- name: Check server formatting
working-directory: server
+1 -1
View File
@@ -7,7 +7,7 @@ on:
languages:
description: "Languages to synchronize (comma separated, e.g.: tr,en,es)"
required: false
default: "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja"
default: "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja,zh-cn,th"
format:
description: "Export format (key_value_json or json)"
required: false
+1
View File
@@ -0,0 +1 @@
.vscode
+1 -1
View File
@@ -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) 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 [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.
### Using a Custom CA
+20 -627
View File
@@ -21,7 +21,7 @@
"dayjs": "1.11.13",
"flag-icons": "7.3.2",
"html2canvas": "^1.4.1",
"i18next": "^24.2.2",
"i18next": "25.4.2",
"joi": "17.13.3",
"mui-color-input": "^6.0.0",
"react": "18.3.1",
@@ -45,10 +45,7 @@
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.6",
"prettier": "^3.3.3",
"vite": "6.3.5"
},
"optionalDependencies": {
"@rollup/rollup-linux-arm64-musl": "4.41.0"
"vite": "6.3.6"
}
},
"node_modules/@ampproject/remapping": {
@@ -283,9 +280,9 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz",
"integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz",
"integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -491,262 +488,6 @@
"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",
@@ -763,134 +504,6 @@
"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",
@@ -2063,123 +1676,6 @@
}
}
},
"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",
@@ -2193,71 +1689,6 @@
"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",
@@ -2284,45 +1715,6 @@
"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",
@@ -3030,13 +2422,13 @@
}
},
"node_modules/axios": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"form-data": "^4.0.4",
"proxy-from-env": "^1.1.0"
}
},
@@ -4342,14 +3734,15 @@
}
},
"node_modules/form-data": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
@@ -4690,9 +4083,9 @@
}
},
"node_modules/i18next": {
"version": "24.2.3",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-24.2.3.tgz",
"integrity": "sha512-lfbf80OzkocvX7nmZtu7nSTNbrTYR52sLWxPtlXX1zAhVw8WEnFk4puUkCR4B1dNQwbSpEHHHemcZu//7EcB7A==",
"version": "25.4.2",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.4.2.tgz",
"integrity": "sha512-gD4T25a6ovNXsfXY1TwHXXXLnD/K2t99jyYMCSimSCBnBRJVQr5j+VAaU83RJCPzrTGhVQ6dqIga66xO2rtd5g==",
"funding": [
{
"type": "individual",
@@ -4709,7 +4102,7 @@
],
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.10"
"@babel/runtime": "^7.27.6"
},
"peerDependencies": {
"typescript": "^5"
@@ -6996,9 +6389,9 @@
}
},
"node_modules/vite": {
"version": "6.3.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
"integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
"version": "6.3.6",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz",
"integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==",
"license": "MIT",
"dependencies": {
"esbuild": "^0.25.0",
+2 -5
View File
@@ -26,7 +26,7 @@
"dayjs": "1.11.13",
"flag-icons": "7.3.2",
"html2canvas": "^1.4.1",
"i18next": "^24.2.2",
"i18next": "25.4.2",
"joi": "17.13.3",
"mui-color-input": "^6.0.0",
"react": "18.3.1",
@@ -58,9 +58,6 @@
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.6",
"prettier": "^3.3.3",
"vite": "6.3.5"
},
"optionalDependencies": {
"@rollup/rollup-linux-arm64-musl": "4.41.0"
"vite": "6.3.6"
}
}
@@ -1,7 +1,7 @@
import { useTheme } from "@emotion/react";
import { Box, Stack, Tooltip, Typography } from "@mui/material";
import { formatDateWithTz } from "../../../Utils/timeUtils";
import { useEffect, useState } from "react";
import { useEffect, useState, forwardRef } from "react";
import { useSelector } from "react-redux";
import PropTypes from "prop-types";
@@ -17,26 +17,32 @@ import PropTypes from "prop-types";
* @returns {JSX.Element} The Bar component.
*/
const Bar = ({ width, height, backgroundColor, borderRadius, children }) => {
const theme = useTheme();
const Bar = forwardRef(
({ width, height, backgroundColor, borderRadius, children, ...otherProps }, ref) => {
const theme = useTheme();
return (
<Box
position="relative"
width={width}
height={height}
backgroundColor={backgroundColor}
sx={{
borderRadius: borderRadius || theme.spacing(1.5),
}}
>
{children}
</Box>
);
};
return (
<Box
ref={ref}
position="relative"
width={width}
height={height}
backgroundColor={backgroundColor}
sx={{
borderRadius: borderRadius || theme.spacing(1.5),
}}
{...otherProps}
>
{children}
</Box>
);
}
);
Bar.displayName = "Bar";
Bar.propTypes = {
width: PropTypes.string.isRequired,
width: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
height: PropTypes.string.isRequired,
backgroundColor: PropTypes.string.isRequired,
borderRadius: PropTypes.string,
+1 -1
View File
@@ -65,7 +65,7 @@ const Check = ({ text, noHighlightText, variant = "info", outlined = false }) =>
};
Check.propTypes = {
text: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
text: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
noHighlightText: PropTypes.string,
variant: PropTypes.oneOf(["info", "error", "success"]),
outlined: PropTypes.bool,
@@ -2,7 +2,7 @@ import { useId } from "react";
import PropTypes from "prop-types";
import { Modal, Stack, Typography } from "@mui/material";
const GenericDialog = ({ title, description, open, onClose, theme, children }) => {
const GenericDialog = ({ title, description, open, onClose, theme, children, width }) => {
const titleId = useId();
const descriptionId = useId();
const ariaDescribedBy = description?.length > 0 ? descriptionId : "";
@@ -16,6 +16,7 @@ const GenericDialog = ({ title, description, open, onClose, theme, children }) =
>
<Stack
gap={theme.spacing(2)}
width={width}
sx={{
position: "absolute",
top: "50%",
@@ -39,6 +40,7 @@ const GenericDialog = ({ title, description, open, onClose, theme, children }) =
fontSize={16}
color={theme.palette.primary.contrastText}
fontWeight={600}
marginBottom={theme.spacing(4)}
>
{title}
</Typography>
@@ -46,6 +48,7 @@ const GenericDialog = ({ title, description, open, onClose, theme, children }) =
<Typography
id={descriptionId}
color={theme.palette.primary.contrastTextTertiary}
marginBottom={theme.spacing(4)}
>
{description}
</Typography>
@@ -64,6 +67,7 @@ GenericDialog.propTypes = {
theme: PropTypes.object.isRequired,
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node])
.isRequired,
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]),
};
export { GenericDialog };
@@ -0,0 +1,66 @@
import GenericFallback from "../GenericFallback";
import Fallback from "../Fallback";
import { Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
import { useIsAdmin } from "../../Hooks/useIsAdmin";
import PropTypes from "prop-types";
const PageStateWrapper = ({
networkError,
isLoading,
items,
type,
fallbackLink,
fallbackChildren,
children,
}) => {
const theme = useTheme();
const { t } = useTranslation();
const isAdmin = useIsAdmin();
const isEmpty = items === null || (Array.isArray(items) && items.length === 0);
const hasInitialized = items !== undefined && !(isLoading && isEmpty);
const showEmpty = !isLoading && isEmpty;
if (networkError) {
return (
<GenericFallback>
<Typography
variant="h1"
marginY={theme.spacing(4)}
color={theme.palette.primary.contrastTextTertiary}
>
{t("common.toasts.networkError")}
</Typography>
<Typography>{t("common.toasts.checkConnection")}</Typography>
</GenericFallback>
);
}
if (!hasInitialized) {
return null;
}
if (showEmpty) {
return (
<Fallback
type={type}
title={t(`${type}.fallback.title`)}
checks={t(`${type}.fallback.checks`, { returnObjects: true })}
link={fallbackLink}
isAdmin={isAdmin}
>
{fallbackChildren}
</Fallback>
);
}
return <>{children}</>;
};
PageStateWrapper.propTypes = {
networkError: PropTypes.any,
isLoading: PropTypes.bool,
items: PropTypes.oneOfType([PropTypes.array, PropTypes.oneOf([null])]),
type: PropTypes.string,
fallbackLink: PropTypes.string,
fallbackChildren: PropTypes.node,
children: PropTypes.node,
};
export default PageStateWrapper;
+48 -16
View File
@@ -7,6 +7,7 @@ import {
TableHead,
TableRow,
} from "@mui/material";
import Tooltip from "@mui/material/Tooltip";
import SkeletonLayout from "./skeleton";
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
@@ -41,6 +42,7 @@ const DataTable = ({
data = [],
config = {
emptyView: "No data",
tooltipContent: null,
onRowClick: () => {},
},
}) => {
@@ -101,24 +103,54 @@ const DataTable = ({
data.map((row) => {
const key = row.id || row._id || Math.random();
return (
<TableRow
<Tooltip
key={key}
sx={config?.rowSX ?? {}}
onClick={config?.onRowClick ? () => config.onRowClick(row) : null}
followCursor
enterDelay={500}
enterNextDelay={500}
title={
typeof config.tooltipContent === "function"
? config.tooltipContent(row)
: config.tooltipContent
}
slotProps={{
tooltip: {
sx: {
background: "unset",
},
},
popper: {
modifiers: [
{
name: "offset",
options: {
offset: ({ popper }) => {
return [popper.width / 2 + 20, -popper.height / 8];
},
},
},
],
},
}}
>
{headers.map((header, index) => {
return (
<TableCell
align={index === 0 ? "left" : "center"}
key={header.id}
onClick={header.onClick ? (e) => header.onClick(e, row) : null}
sx={header.getCellSx ? header.getCellSx(row) : {}}
>
{header.render(row)}
</TableCell>
);
})}
</TableRow>
<TableRow
sx={config?.rowSX ?? {}}
onClick={config?.onRowClick ? () => config.onRowClick(row) : null}
>
{headers.map((header, index) => {
return (
<TableCell
align={index === 0 ? "left" : "center"}
key={header.id}
onClick={header.onClick ? (e) => header.onClick(e, row) : null}
sx={header.getCellSx ? header.getCellSx(row) : {}}
>
{header.render(row)}
</TableCell>
);
})}
</TableRow>
</Tooltip>
);
})
)}
+31 -12
View File
@@ -14,25 +14,25 @@ const useGetInviteToken = () => {
const clearToken = () => {
setToken(undefined);
};
const fetchToken = async (email, role) => {
const response = await networkService.requestInvitationToken({ email, role });
const token = response?.data?.data?.token;
if (typeof token === "undefined") {
throw new Error(t("inviteNoTokenFound"));
}
return token;
};
const getInviteToken = async ({ email, role }) => {
try {
const response = await networkService.requestInvitationToken({
email,
role,
});
const token = response?.data?.data?.token;
if (typeof token === "undefined") {
throw new Error(t("inviteNoTokenFound"));
}
setIsLoading(true);
const token = await fetchToken(email, role);
let inviteLink = token;
if (typeof CLIENT_HOST !== "undefined") {
inviteLink = `${CLIENT_HOST}/register/${token}`;
}
setToken(inviteLink);
return token;
} catch (error) {
setError(error);
} finally {
@@ -40,7 +40,26 @@ const useGetInviteToken = () => {
}
};
return [getInviteToken, clearToken, isLoading, error, token];
const addTeamMember = async (formData, role) => {
try {
setIsLoading(true);
const token = await fetchToken(formData.email, role);
const toSubmit = {
...formData,
inviteToken: token,
};
delete toSubmit.confirm;
const responseRegister = await networkService.registerUser(toSubmit);
return responseRegister;
} catch (error) {
setError(error);
throw error;
} finally {
setIsLoading(false);
}
};
return [getInviteToken, clearToken, isLoading, error, token, addTeamMember];
};
export { useGetInviteToken };
-3
View File
@@ -15,9 +15,6 @@ const useFetchLogs = () => {
setIsLoading(true);
const response = await networkService.getLogs();
setLogs(response.data.data);
createToast({
body: t("logsPage.toast.fetchLogsSuccess"),
});
} catch (error) {
setError(error);
createToast({
+1 -1
View File
@@ -34,7 +34,7 @@ const useCreateNotification = () => {
const useGetNotificationsByTeamId = (updateTrigger) => {
const [notifications, setNotifications] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
const { t } = useTranslation();
@@ -0,0 +1,68 @@
import { useState } from "react";
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 { useTranslation } from "react-i18next";
import Proptypes from "prop-types";
const AddMemberMenu = ({ handleInviteOpen, handleIsRegisterOpen }) => {
const [anchorEl, setAnchorEl] = useState(null);
const open = Boolean(anchorEl);
const { t } = useTranslation();
const theme = useTheme();
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
return (
<>
<Button
variant="contained"
color="accent"
endIcon={<ArrowDropDownIcon sx={{ color: theme.palette.secondary.light }} />}
onClick={handleClick}
>
{t("teamPanel.addTeamMember.addMemberMenu")}
</Button>
<Menu
anchorEl={anchorEl}
open={open}
onClose={handleClose}
sx={{
"& .MuiPaper-root": {
minWidth: anchorEl?.offsetWidth || "auto",
},
}}
>
<MenuItem
onClick={() => {
handleClose();
handleInviteOpen();
}}
>
{t("teamPanel.inviteTeamMember")}
</MenuItem>
<MenuItem
onClick={() => {
handleClose();
handleIsRegisterOpen(true);
}}
>
{t("teamPanel.register")}
</MenuItem>
</Menu>
</>
);
};
AddMemberMenu.propTypes = {
handleInviteOpen: Proptypes.func.isRequired,
handleIsRegisterOpen: Proptypes.func.isRequired,
};
export default AddMemberMenu;
@@ -0,0 +1,48 @@
import { useState } from "react";
import { newOrChangedCredentials } from "../../../../../Validation/validation";
import { useTranslation } from "react-i18next";
const useAddTeamMember = () => {
const { t } = useTranslation();
const [errors, setErrors] = useState({});
const clearErrors = () => setErrors({});
const validateFields = (name, value, formData) => {
const { error } = newOrChangedCredentials.validate(
{ [name]: value },
{ abortEarly: false, context: { password: formData.password } }
);
setErrors((prev) => ({
...prev,
[name]: error?.details?.[0]?.message || "",
}));
};
const validateForm = (formData, role) => {
const { error } = newOrChangedCredentials.validate(formData, {
abortEarly: false,
context: { password: formData.password },
});
const formErrors = {};
if (error) {
for (const err of error.details) {
formErrors[err.path[0]] = err.message;
}
}
if (!role[0] || role.length === 0) {
formErrors.role = t(
"teamPanel.registerTeamMember.auth.common.inputs.role.errors.empty"
);
}
if (Object.keys(formErrors).length > 0) {
setErrors(formErrors);
return false;
}
setErrors({});
return true;
};
return { errors, setErrors, clearErrors, validateFields, validateForm };
};
export default useAddTeamMember;
@@ -0,0 +1,213 @@
import { Button, Stack } from "@mui/material";
import { GenericDialog } from "../../../../Components/Dialog/genericDialog";
import TextInput from "../../../../Components/Inputs/TextInput";
import Select from "../../../../Components/Inputs/Select";
import { useGetInviteToken } from "../../../../Hooks/inviteHooks";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
import { createToast } from "../../../../Utils/toastUtils";
import { useState } from "react";
import PasswordTooltip from "../../../Auth/components/PasswordTooltip";
import useAddTeamMember from "./hooks/useAddTeamMember";
import usePasswordFeedback from "../../../Auth/hooks/usePasswordFeedback";
import { PasswordEndAdornment } from "../../../../Components/Inputs/TextInput/Adornments";
import PropTypes from "prop-types";
const INITIAL_FORM_STATE = {
firstName: "",
lastName: "",
email: "",
password: "",
confirm: "",
teamId: "",
};
const INITIAL_ROLE_STATE = ["user"];
const AddTeamMember = ({ handleIsRegisterOpen, isRegisterOpen, onMemberAdded }) => {
const theme = useTheme();
const { t } = useTranslation();
const { errors, setErrors, clearErrors, validateFields, validateForm } =
useAddTeamMember();
const { feedback, handlePasswordFeedback } = usePasswordFeedback();
const [getInviteToken, clearToken, isLoading, error, token, addTeamMember] =
useGetInviteToken();
const [form, setForm] = useState(INITIAL_FORM_STATE);
const [role, setRole] = useState(INITIAL_ROLE_STATE);
const [isLoadingSubmit, setIsLoadingSubmit] = useState(false);
const closeAddMemberModal = () => {
handleIsRegisterOpen(false);
setForm(INITIAL_FORM_STATE);
setRole(INITIAL_ROLE_STATE);
clearErrors();
clearToken();
};
const onChange = (e) => {
let { name, value } = e.target;
if (name === "email") value = value.toLowerCase();
const updatedForm = { ...form, [name]: value };
validateFields(name, value, updatedForm);
setForm(updatedForm);
if (name === "password" || name === "confirm") {
handlePasswordFeedback(updatedForm, name, value, form, errors, setErrors);
}
};
const onsubmitAddMember = async (event) => {
event.preventDefault();
if (!validateForm(form, role)) return;
try {
setIsLoadingSubmit(true);
await addTeamMember(form, role);
createToast({
body: t("teamPanel.registerToast.success"),
});
onMemberAdded();
closeAddMemberModal();
} catch (error) {
const errorMsg = error.response?.data?.msg || error.message || "unknownError";
createToast({
type: "error",
body: t(errorMsg),
});
} finally {
setIsLoadingSubmit(false);
}
};
const tErr = (key) => (key ? t([`teamPanel.registerTeamMember.${key}`, key]) : "");
return (
<>
<GenericDialog
title={t("teamPanel.addTeamMember.title")}
description={t("teamPanel.addTeamMember.description")}
open={isRegisterOpen}
onClose={closeAddMemberModal}
theme={theme}
width={{ sm: "55%", md: "50%", lg: "40%", xl: "30%" }}
>
<TextInput
name="firstName"
label={t("auth.common.inputs.firstName.label")}
isRequired={true}
gap={theme.spacing(4)}
placeholder={t("auth.common.inputs.firstName.placeholder")}
value={form.firstName}
onChange={onChange}
error={errors.firstName ? true : false}
helperText={errors.firstName ? tErr(errors.firstName) : null}
sx={{ mb: errors.firstName ? theme.spacing(15) : theme.spacing(5) }}
/>
<TextInput
name="lastName"
label={t("auth.common.inputs.lastName.label")}
isRequired={true}
gap={theme.spacing(4)}
placeholder={t("auth.common.inputs.lastName.placeholder")}
value={form.lastName}
onChange={onChange}
error={errors.lastName ? true : false}
helperText={errors.lastName ? tErr(errors.lastName) : null}
sx={{ mb: errors.lastName ? theme.spacing(15) : theme.spacing(5) }}
/>
<TextInput
type="email"
label={t("auth.common.inputs.email.label")}
name="email"
gap={theme.spacing(4)}
isRequired={true}
id="input-team-member"
placeholder={t("teamPanel.email")}
value={form.email}
onChange={onChange}
error={errors.email ? true : false}
helperText={errors.email ? tErr(errors.email) : null}
sx={{ mb: errors.email ? theme.spacing(15) : theme.spacing(5) }}
/>
<Select
label={t("teamPanel.role")}
id="team-member-role"
name="role"
required={true}
placeholder={t("teamPanel.selectRole")}
isHidden={true}
value={role[0] || ""}
sx={{ mb: theme.spacing(5) }}
onChange={(event) => setRole([event.target.value])}
items={[
{ _id: "admin", name: t("roles.admin") },
{ _id: "user", name: t("roles.teamMember") },
]}
/>
<PasswordTooltip
feedback={feedback}
form={form}
>
<TextInput
type="password"
id="register-password-input"
name="password"
label={t("auth.common.inputs.password.label")}
isRequired={true}
placeholder="••••••••••"
value={form.password}
onChange={onChange}
error={errors.password && errors.password[0] ? true : false}
endAdornment={<PasswordEndAdornment />}
sx={{ mb: theme.spacing(5) }}
/>
</PasswordTooltip>
<TextInput
type="password"
id="register-confirm-input"
name="confirm"
label={t("auth.common.inputs.passwordConfirm.label")}
gap={theme.spacing(4)}
isRequired={true}
placeholder={t("auth.common.inputs.passwordConfirm.placeholder")}
autoComplete="current-password"
value={form.confirm}
onChange={onChange}
error={errors.confirm && errors.confirm[0] ? true : false}
endAdornment={<PasswordEndAdornment />}
sx={{ mb: theme.spacing(5) }}
/>
<Stack
direction="row"
spacing={theme.spacing(10)}
mt={theme.spacing(8)}
justifyContent="flex-end"
>
<Button
variant="contained"
color="error"
onClick={closeAddMemberModal}
disabled={isLoadingSubmit}
>
{t("teamPanel.cancel")}
</Button>
<Button
variant="contained"
color="accent"
onClick={onsubmitAddMember}
disabled={isLoadingSubmit}
>
{t("teamPanel.addTeamMember.addButton")}
</Button>
</Stack>
</GenericDialog>
</>
);
};
AddTeamMember.propTypes = {
handleIsRegisterOpen: PropTypes.func.isRequired,
isRegisterOpen: PropTypes.bool.isRequired,
onMemberAdded: PropTypes.func.isRequired,
};
export default AddTeamMember;
@@ -9,10 +9,12 @@ import { networkService } from "../../../main";
import { createToast } from "../../../Utils/toastUtils";
import Select from "../../../Components/Inputs/Select";
import { GenericDialog } from "../../../Components/Dialog/genericDialog";
import AddTeamMember from "../components/AddTeamMember";
import DataTable from "../../../Components/Table";
import { useGetInviteToken } from "../../../Hooks/inviteHooks";
import { useNavigate } from "react-router-dom";
import { useIsSuperAdmin } from "../../../Hooks/useIsAdmin";
import AddMemberMenu from "./AddMemberMenu";
/**
* TeamPanel component manages the organization and team members,
* providing functionalities like renaming the organization, managing team members,
@@ -65,7 +67,10 @@ const TeamPanel = () => {
render: (row) => row.role,
},
];
const [refreshTrigger, setRefreshTrigger] = useState(false);
const refreshTeamList = () => {
setRefreshTrigger((prev) => !prev);
};
useEffect(() => {
const fetchTeam = async () => {
try {
@@ -79,7 +84,7 @@ const TeamPanel = () => {
};
fetchTeam();
}, []);
}, [refreshTrigger]);
useEffect(() => {
const ROLE_MAP = {
@@ -109,6 +114,10 @@ const TeamPanel = () => {
setIsDisabled(Object.keys(errors).length !== 0 || toInvite.email === "");
}, [errors, toInvite.email]);
const [isOpen, setIsOpen] = useState(false);
const [isRegisterOpen, setIsRegisterOpen] = useState(false);
const handleIsRegisterOpen = (open) => {
setIsRegisterOpen(open);
};
const handleChange = (event) => {
const { value } = event.target;
@@ -138,7 +147,6 @@ const TeamPanel = () => {
const handleGetToken = async () => {
await getInviteToken({ email: toInvite.email, role: toInvite.role });
};
const handleInviteMember = async () => {
if (!toInvite.email) {
setErrors((prev) => ({ ...prev, email: "Email is required." }));
@@ -239,13 +247,16 @@ const TeamPanel = () => {
</Button>
</ButtonGroup>
</Stack>
<Button
variant="contained"
color="accent"
onClick={() => setIsOpen(true)}
>
{t("teamPanel.inviteTeamMember")}
</Button>
<AddTeamMember
handleIsRegisterOpen={handleIsRegisterOpen}
isRegisterOpen={isRegisterOpen}
onMemberAdded={refreshTeamList}
/>
<AddMemberMenu
handleInviteOpen={() => setIsOpen(true)}
handleIsRegisterOpen={handleIsRegisterOpen}
/>
</Stack>
<DataTable
@@ -264,7 +275,6 @@ const TeamPanel = () => {
}}
/>
</Stack>
<GenericDialog
title={t("teamPanel.inviteNewTeamMember")}
description={t("teamPanel.inviteDescription")}
@@ -93,14 +93,14 @@ const PasswordTooltip = ({ feedback, form, children }) => {
},
}}
>
{children}
<span style={{ display: "inline-block", width: "100%" }}>{children}</span>
</Tooltip>
);
};
PasswordTooltip.propTypes = {
feedback: PropTypes.shape({
length: PropTypes.string.isRequired,
length: PropTypes.string,
special: PropTypes.string,
number: PropTypes.string,
uppercase: PropTypes.string,
@@ -0,0 +1,71 @@
import { useState } from "react";
import { newOrChangedCredentials } from "../../../Validation/validation";
const usePasswordFeedback = () => {
const [feedback, setFeedback] = useState({});
const getFeedbackStatus = (form, errors, field, criteria) => {
const fieldErrors = errors?.[field];
const isFieldEmpty = form?.[field]?.length === 0;
const hasError = fieldErrors?.includes(criteria) || fieldErrors?.includes("empty");
const isCorrect = !isFieldEmpty && !hasError;
if (isCorrect) {
return "success";
} else if (hasError) {
return "error";
} else {
return "info";
}
};
const handlePasswordFeedback = (updatedForm, name, value, form, errors, setErrors) => {
const validateValue = { [name]: value };
const validateOptions = { abortEarly: false, context: { password: form.password } };
if (name === "password" && form.confirm.length > 0) {
validateValue.confirm = form.confirm;
validateOptions.context = { password: value };
} else if (name === "confirm") {
validateValue.password = form.password;
}
const { error } = newOrChangedCredentials.validate(validateValue, validateOptions);
const pwdErrors = error?.details.map((error) => ({
path: error.path[0],
type: error.type,
}));
const errorsByPath =
pwdErrors &&
pwdErrors.reduce((acc, { path, type }) => {
if (!acc[path]) {
acc[path] = [];
}
acc[path].push(type);
return acc;
}, {});
const oldErrors = { ...errors };
if (name === "password") {
oldErrors.password = undefined;
} else if (name === "confirm") {
oldErrors.confirm = undefined;
}
const newErrors = { ...oldErrors, ...errorsByPath };
setErrors(newErrors);
const newFeedback = {
length: getFeedbackStatus(updatedForm, errorsByPath, "password", "string.min"),
special: getFeedbackStatus(updatedForm, errorsByPath, "password", "special"),
number: getFeedbackStatus(updatedForm, errorsByPath, "password", "number"),
uppercase: getFeedbackStatus(updatedForm, errorsByPath, "password", "uppercase"),
lowercase: getFeedbackStatus(updatedForm, errorsByPath, "password", "lowercase"),
confirm: getFeedbackStatus(updatedForm, errorsByPath, "confirm", "different"),
};
setFeedback(newFeedback);
};
return { feedback, handlePasswordFeedback, getFeedbackStatus };
};
export default usePasswordFeedback;
@@ -1,5 +1,10 @@
//Components
import Table from "../../../../Components/Table";
import Stack from "@mui/material/Stack";
import DataTable from "../../../../Components/Table";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableRow from "@mui/material/TableRow";
import TableCell from "@mui/material/TableCell";
import TableSkeleton from "../../../../Components/Table/skeleton";
import Pagination from "../../../../Components/Table/TablePagination";
import { StatusLabel } from "../../../../Components/Label";
@@ -16,7 +21,68 @@ import { useTranslation } from "react-i18next";
import { useFetchChecksTeam } from "../../../../Hooks/checkHooks";
import { useFetchChecksByMonitor } from "../../../../Hooks/checkHooks";
import { useResolveIncident } from "../../../../Hooks/checkHooks";
import { Button, Typography } from "@mui/material";
import { Button, Typography, useTheme } from "@mui/material";
import { lighten } from "@mui/material/styles";
const GetTooltip = (row) => {
const theme = useTheme();
const phases = row?.timings?.phases;
const phaseKeyFormattingMap = {
firstByte: "first byte",
};
return (
<Stack
backgroundColor={lighten(theme.palette.primary.main, 0.1)}
border={`1px solid ${theme.palette.primary.lowContrast}`}
borderRadius={theme.shape.borderRadius}
py={theme.spacing(2)}
px={theme.spacing(4)}
>
<Typography
variant="body2"
color={theme.palette.primary.contrastText}
>{`Status code: ${row?.statusCode}`}</Typography>
<Typography
variant="body2"
color={theme.palette.primary.contrastText}
>{`Response time: ${row?.responseTime} ms`}</Typography>
{phases && (
<>
<Typography
variant="body2"
color={theme.palette.primary.contrastText}
>{`Request timing: `}</Typography>
<Table
size="small"
sx={{ ml: theme.spacing(2), mt: theme.spacing(2) }}
>
<TableBody>
{Object.keys(phases)?.map((phaseKey) => (
<TableRow key={phaseKey}>
<TableCell sx={{ border: "none", p: 0 }}>
<Typography
variant="body2"
color="success"
>
{`${phaseKeyFormattingMap[phaseKey] || phaseKey}:`}
</Typography>
</TableCell>
<TableCell sx={{ border: "none", p: 0 }}>
<Typography
color={theme.palette.primary.contrastText}
variant="body2"
>{`${phases[phaseKey]} ms`}</Typography>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</>
)}
</Stack>
);
};
const IncidentTable = ({
isLoading,
@@ -167,9 +233,10 @@ const IncidentTable = ({
return (
<>
<Table
<DataTable
headers={headers}
data={checks}
config={{ tooltipContent: GetTooltip }}
/>
<Pagination
paginationLabel={t("incidentsTablePaginationLabel")}
@@ -1,4 +1,4 @@
import { useState } from "react";
import { useState, useCallback } from "react";
const useInfrastructureMonitorForm = () => {
const [infrastructureMonitor, setInfrastructureMonitor] = useState({
url: "",
@@ -6,6 +6,8 @@ const useInfrastructureMonitorForm = () => {
notifications: [],
notify_email: false,
interval: 0.25,
statusWindowSize: 5,
statusWindowThreshold: 60,
cpu: false,
usage_cpu: "",
memory: false,
@@ -29,9 +31,10 @@ const useInfrastructureMonitorForm = () => {
[event.target.name]: event.target.checked,
});
};
const initializeInfrastructureMonitorForCreate = (globalSettings) => {
const initializeInfrastructureMonitorForCreate = useCallback((globalSettings) => {
const gt = globalSettings?.data?.settings?.globalThresholds || {};
setInfrastructureMonitor({
setInfrastructureMonitor((prev) => ({
...prev,
url: "",
name: "",
notifications: [],
@@ -45,17 +48,20 @@ const useInfrastructureMonitorForm = () => {
temperature: gt.temperature !== undefined,
usage_temperature: gt.temperature !== undefined ? gt.temperature.toString() : "",
secret: "",
});
};
}));
}, []);
const initializeInfrastructureMonitorForUpdate = (monitor) => {
const initializeInfrastructureMonitorForUpdate = useCallback((monitor) => {
const MS_PER_MINUTE = 60000;
const { thresholds = {} } = monitor;
setInfrastructureMonitor({
setInfrastructureMonitor((prev) => ({
...prev,
url: monitor.url.replace(/^https?:\/\//, ""),
name: monitor.name || "",
notifications: monitor.notifications || [],
interval: monitor.interval / MS_PER_MINUTE,
statusWindowSize: monitor.statusWindowSize,
statusWindowThreshold: monitor.statusWindowThreshold,
cpu: thresholds.usage_cpu !== undefined,
usage_cpu:
thresholds.usage_cpu !== undefined ? (thresholds.usage_cpu * 100).toString() : "",
@@ -75,8 +81,8 @@ const useInfrastructureMonitorForm = () => {
? (thresholds.usage_temperature * 100).toString()
: "",
secret: monitor.secret || "",
});
};
}));
}, []);
return {
infrastructureMonitor,
setInfrastructureMonitor,
@@ -12,6 +12,8 @@ const useInfrastructureSubmit = () => {
? infrastructureMonitor.url
: infrastructureMonitor.name,
interval: infrastructureMonitor.interval * MS_PER_MINUTE,
statusWindowSize: infrastructureMonitor.statusWindowSize,
statusWindowThreshold: infrastructureMonitor.statusWindowThreshold,
cpu: infrastructureMonitor.cpu,
...(infrastructureMonitor.cpu
? { usage_cpu: infrastructureMonitor.usage_cpu }
@@ -86,7 +86,14 @@ const CreateInfrastructureMonitor = () => {
setHttps(monitor.url.startsWith("https"));
initializeInfrastructureMonitorForUpdate(monitor);
}
}, [isCreate, monitor, globalSettings, globalSettingsLoading]);
}, [
isCreate,
monitor,
globalSettings,
globalSettingsLoading,
initializeInfrastructureMonitorForCreate,
initializeInfrastructureMonitorForUpdate,
]);
// Handlers
const onSubmit = async (event) => {
@@ -281,6 +288,39 @@ const CreateInfrastructureMonitor = () => {
setNotifications={infrastructureMonitor.notifications}
/>
</ConfigBox>
<ConfigBox>
<Box>
<Typography
component="h2"
variant="h2"
>
{t("createMonitorPage.incidentConfigTitle")}
</Typography>
<Typography component="p">
{t("createMonitorPage.incidentConfigDescription")}
</Typography>
</Box>
<Stack gap={theme.spacing(20)}>
<TextInput
name="statusWindowSize"
label={t("createMonitorPage.incidentConfigStatusWindowLabel")}
type="number"
value={infrastructureMonitor.statusWindowSize}
onChange={onChange}
error={errors["statusWindowSize"] ? true : false}
helperText={errors["statusWindowSize"]}
/>
<TextInput
name="statusWindowThreshold"
label={t("createMonitorPage.incidentConfigStatusWindowThresholdLabel")}
type="number"
value={infrastructureMonitor.statusWindowThreshold}
onChange={onChange}
error={errors["statusWindowThreshold"] ? true : false}
helperText={errors["statusWindowThreshold"]}
/>
</Stack>
</ConfigBox>
<CustomAlertsSection
errors={errors}
onChange={onChange}
@@ -1,12 +1,11 @@
// Components
import { Stack, Typography } from "@mui/material";
import { Stack } from "@mui/material";
import Breadcrumbs from "../../../Components/Breadcrumbs";
import MonitorCountHeader from "../../../Components/MonitorCountHeader";
import MonitorCreateHeader from "../../../Components/MonitorCreateHeader";
import MonitorsTable from "./Components/MonitorsTable";
import Pagination from "../../..//Components/Table/TablePagination";
import GenericFallback from "../../../Components/GenericFallback";
import Fallback from "../../../Components/Fallback";
import PageStateWrapper from "../../../Components/PageStateWrapper";
import Filter from "./Components/Filters";
import SearchComponent from "../../Uptime/Monitors/Components/SearchComponent";
// Utils
@@ -81,75 +80,58 @@ const InfrastructureMonitors = () => {
updateTrigger,
});
if (networkError === true) {
return (
<GenericFallback>
<Typography
variant="h1"
marginY={theme.spacing(4)}
color={theme.palette.primary.contrastTextTertiary}
>
{t("common.toasts.networkError")}
</Typography>
<Typography>{t("common.toasts.checkConnection")}</Typography>
</GenericFallback>
);
}
if (!isLoading && typeof summary?.totalMonitors === "undefined") {
return (
<Fallback
type="infrastructureMonitor"
title={t("infrastructureMonitor.fallback.title")}
checks={t("infrastructureMonitor.fallback.checks", { returnObjects: true })}
link="/infrastructure/create"
isAdmin={isAdmin}
/>
);
}
return (
<Stack gap={theme.spacing(10)}>
<Breadcrumbs list={BREADCRUMBS} />
<MonitorCreateHeader
isAdmin={isAdmin}
<>
<PageStateWrapper
networkError={networkError}
isLoading={isLoading}
path="/infrastructure/create"
/>
<Stack direction={"row"}>
<MonitorCountHeader
isLoading={isLoading}
monitorCount={summary?.totalMonitors ?? 0}
/>
<Filter
selectedStatus={selectedStatus}
setSelectedStatus={setSelectedStatus}
setToFilterStatus={setToFilterStatus}
handleReset={handleReset}
/>
<SearchComponent
monitors={monitors}
onSearchChange={setSearch}
setIsSearching={setIsSearching}
/>
</Stack>
items={monitors}
type="infrastructureMonitor"
fallbackLink="/infrastructure/create"
>
<Stack gap={theme.spacing(10)}>
<Breadcrumbs list={BREADCRUMBS} />
<MonitorCreateHeader
isAdmin={isAdmin}
isLoading={isLoading}
path="/infrastructure/create"
/>
<Stack direction={"row"}>
<MonitorCountHeader
isLoading={isLoading}
monitorCount={summary?.totalMonitors ?? 0}
/>
<Filter
selectedStatus={selectedStatus}
setSelectedStatus={setSelectedStatus}
setToFilterStatus={setToFilterStatus}
handleReset={handleReset}
/>
<SearchComponent
monitors={monitors}
onSearchChange={setSearch}
setIsSearching={setIsSearching}
/>
</Stack>
<MonitorsTable
isLoading={isLoading}
monitors={monitors}
isAdmin={isAdmin}
handleActionMenuDelete={handleActionMenuDelete}
isSearching={isSearching}
/>
<Pagination
itemCount={summary?.totalMonitors}
paginationLabel={t("monitors")}
page={page}
rowsPerPage={rowsPerPage}
handleChangePage={handleChangePage}
handleChangeRowsPerPage={handleChangeRowsPerPage}
/>
</Stack>
<MonitorsTable
isLoading={isLoading}
monitors={monitors}
isAdmin={isAdmin}
handleActionMenuDelete={handleActionMenuDelete}
isSearching={isSearching}
/>
<Pagination
itemCount={summary?.totalMonitors}
paginationLabel={t("monitors")}
page={page}
rowsPerPage={rowsPerPage}
handleChangePage={handleChangePage}
handleChangeRowsPerPage={handleChangeRowsPerPage}
/>
</Stack>
</PageStateWrapper>
</>
);
};
@@ -46,6 +46,11 @@ const JobTable = ({ jobs = [] }) => {
content: t("queuePage.jobTable.urlHeader"),
render: (row) => row.monitorUrl,
},
{
id: "interval",
content: t("queuePage.jobTable.intervalHeader"),
render: (row) => `${row.monitorInterval} ms`,
},
{
id: "type",
content: t("queuePage.jobTable.typeHeader"),
+47 -66
View File
@@ -1,6 +1,5 @@
import { Stack, Button } from "@mui/material";
import { useTheme } from "@emotion/react";
import Fallback from "../../Components/Fallback";
import { useState, useEffect } from "react";
import "./index.css";
import MaintenanceTable from "./MaintenanceTable";
@@ -8,24 +7,21 @@ import { useSelector } from "react-redux";
import { networkService } from "../../main";
import Breadcrumbs from "../../Components/Breadcrumbs";
import { useNavigate } from "react-router-dom";
import { useIsAdmin } from "../../Hooks/useIsAdmin";
import { useTranslation } from "react-i18next";
import { Typography } from "@mui/material";
import GenericFallback from "../../Components/GenericFallback";
import PageStateWrapper from "../../Components/PageStateWrapper";
const Maintenance = () => {
const theme = useTheme();
const { t } = useTranslation();
const navigate = useNavigate();
const rowsPerPage = useSelector((state) => state?.ui?.maintenance?.rowsPerPage ?? 5);
const isAdmin = useIsAdmin();
const [maintenanceWindows, setMaintenanceWindows] = useState([]);
const [maintenanceWindows, setMaintenanceWindows] = useState(undefined);
const [maintenanceWindowCount, setMaintenanceWindowCount] = useState(0);
const [page, setPage] = useState(0);
const [sort, setSort] = useState({});
const [updateTrigger, setUpdateTrigger] = useState(false);
const [networkError, setNetworkError] = useState(false);
const [isDataFetched, setIsDataFetched] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const handleActionMenuDelete = () => {
setUpdateTrigger((prev) => !prev);
@@ -34,80 +30,65 @@ const Maintenance = () => {
useEffect(() => {
const fetchMaintenanceWindows = async () => {
try {
setNetworkError(false);
setIsLoading(true);
const response = await networkService.getMaintenanceWindowsByTeamId({
page: page,
rowsPerPage: rowsPerPage,
});
const { maintenanceWindows, maintenanceWindowCount } = response.data.data;
setMaintenanceWindows(maintenanceWindows);
setMaintenanceWindowCount(maintenanceWindowCount);
setMaintenanceWindows(maintenanceWindows ?? []);
setMaintenanceWindowCount(maintenanceWindowCount ?? 0);
} catch (error) {
setNetworkError(true);
} finally {
setIsDataFetched(true);
setIsLoading(false);
}
};
fetchMaintenanceWindows();
}, [page, rowsPerPage, updateTrigger]);
if (networkError) {
return (
<GenericFallback>
<Typography
variant="h1"
marginY={theme.spacing(4)}
color={theme.palette.primary.contrastTextTertiary}
>
{t("common.toasts.networkError")}
</Typography>
<Typography>{t("common.toasts.checkConnection")}</Typography>
</GenericFallback>
);
}
// Only show the fallback if we've fetched data and there are no maintenance windows
if (isDataFetched && maintenanceWindows.length === 0) {
return (
<Fallback
type="maintenanceWindow"
title={t("maintenanceWindow.fallback.title")}
checks={t("maintenanceWindow.fallback.checks", { returnObjects: true })}
link="/maintenance/create"
isAdmin={isAdmin}
/>
);
}
return (
<Stack gap={theme.spacing(10)}>
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
mt={theme.spacing(5)}
<>
<PageStateWrapper
networkError={networkError}
isLoading={isLoading}
items={maintenanceWindows}
type="maintenanceWindow"
fallbackLink="/maintenance/create"
>
<Breadcrumbs list={[{ name: "maintenance", path: "/maintenance" }]} />
<Button
variant="contained"
color="accent"
onClick={() => {
navigate("/maintenance/create");
}}
sx={{ fontWeight: 500 }}
>
{t("createMaintenanceWindow")}
</Button>
</Stack>
<MaintenanceTable
page={page}
setPage={setPage}
rowsPerPage={rowsPerPage}
sort={sort}
setSort={setSort}
maintenanceWindows={maintenanceWindows}
maintenanceWindowCount={maintenanceWindowCount}
updateCallback={handleActionMenuDelete}
/>
</Stack>
<Stack gap={theme.spacing(10)}>
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
mt={theme.spacing(5)}
>
<Breadcrumbs list={[{ name: "maintenance", path: "/maintenance" }]} />
<Button
variant="contained"
color="accent"
onClick={() => {
navigate("/maintenance/create");
}}
sx={{ fontWeight: 500 }}
>
{t("createMaintenanceWindow")}
</Button>
</Stack>
<MaintenanceTable
page={page}
setPage={setPage}
rowsPerPage={rowsPerPage}
sort={sort}
setSort={setSort}
maintenanceWindows={maintenanceWindows}
maintenanceWindowCount={maintenanceWindowCount}
updateCallback={handleActionMenuDelete}
/>
</Stack>
</PageStateWrapper>
</>
);
};
+40 -44
View File
@@ -4,11 +4,10 @@ import Typography from "@mui/material/Typography";
import Breadcrumbs from "../../Components/Breadcrumbs";
import Button from "@mui/material/Button";
import DataTable from "../../Components/Table";
import Fallback from "../../Components/Fallback";
import ActionMenu from "./components/ActionMenu";
import PageStateWrapper from "../../Components/PageStateWrapper";
// Utils
import { useIsAdmin } from "../../Hooks/useIsAdmin";
import { useState } from "react";
import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router-dom";
@@ -23,7 +22,6 @@ const Notifications = () => {
const theme = useTheme();
const BREADCRUMBS = [{ name: "notifications", path: "/notifications" }];
const [updateTrigger, setUpdateTrigger] = useState(false);
const isAdmin = useIsAdmin();
const [notifications, isLoading, error] = useGetNotificationsByTeamId(updateTrigger);
const [deleteNotification, isDeleting, deleteError] = useDeleteNotification();
const { t } = useTranslation();
@@ -76,49 +74,47 @@ const Notifications = () => {
},
];
if (notifications?.length === 0) {
return (
<Fallback
type="notifications"
title={t("notifications.fallback.title")}
checks={t("notifications.fallback.checks", { returnObjects: true })}
link="/notifications/create"
isAdmin={isAdmin}
/>
);
}
return (
<Stack gap={theme.spacing(10)}>
<Breadcrumbs list={BREADCRUMBS} />
<Stack
direction="row"
justifyContent="flex-end"
<>
<PageStateWrapper
networkError={error}
isLoading={isLoading}
items={notifications}
type="notifications"
fallbackLink="/notifications/create"
>
<Button
variant="contained"
color="accent"
onClick={() => navigate("/notifications/create")}
>
{t("notifications.createButton")}
</Button>
</Stack>
<Typography variant="h1">{t("notifications.createTitle")}</Typography>
<DataTable
config={{
onRowClick: (row) => navigate(`/notifications/${row._id}`),
rowSX: {
cursor: "pointer",
"&:hover td": {
backgroundColor: theme.palette.tertiary.main,
transition: "background-color .3s ease",
},
},
}}
headers={headers}
data={notifications}
/>
</Stack>
<Stack gap={theme.spacing(10)}>
<Breadcrumbs list={BREADCRUMBS} />
<Stack
direction="row"
justifyContent="flex-end"
>
<Button
variant="contained"
color="accent"
onClick={() => navigate("/notifications/create")}
>
{t("notifications.createButton")}
</Button>
</Stack>
<Typography variant="h1">{t("notifications.createTitle")}</Typography>
<DataTable
config={{
onRowClick: (row) => navigate(`/notifications/${row._id}`),
rowSX: {
cursor: "pointer",
"&:hover td": {
backgroundColor: theme.palette.tertiary.main,
transition: "background-color .3s ease",
},
},
}}
headers={headers}
data={notifications}
/>
</Stack>
</PageStateWrapper>
</>
);
};
+36 -53
View File
@@ -1,18 +1,16 @@
// Components
import { useState } from "react";
import Breadcrumbs from "../../../Components/Breadcrumbs";
import { Stack, Typography } from "@mui/material";
import { Stack } from "@mui/material";
import CreateMonitorHeader from "../../../Components/MonitorCreateHeader";
import MonitorCountHeader from "../../../Components/MonitorCountHeader";
import MonitorGrid from "./Components/MonitorGrid";
import Fallback from "../../../Components/Fallback";
import GenericFallback from "../../../Components/GenericFallback";
import PageStateWrapper from "../../../Components/PageStateWrapper";
import FallbackPageSpeedWarning from "../../../Components/Fallback/FallbackPageSpeedWarning";
// Utils
import { useTheme } from "@emotion/react";
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
import { useTranslation } from "react-i18next";
import { useFetchMonitorsByTeamId } from "../../../Hooks/monitorHooks";
import { useFetchSettings } from "../../../Hooks/settingsHooks";
// Constants
@@ -20,7 +18,6 @@ const BREADCRUMBS = [{ name: `pagespeed`, path: "/pagespeed" }];
const TYPES = ["pagespeed"];
const PageSpeed = () => {
const theme = useTheme();
const { t } = useTranslation();
const isAdmin = useIsAdmin();
const [monitors, monitorsSummary, isLoading, networkError] = useFetchMonitorsByTeamId({
@@ -40,56 +37,42 @@ const PageSpeed = () => {
setIsEmailPasswordSet: () => {},
});
if (networkError === true) {
return (
<GenericFallback>
<Typography
variant="h1"
marginY={theme.spacing(4)}
color={theme.palette.primary.contrastTextTertiary}
>
{t("common.toasts.networkError")}
</Typography>
<Typography>{t("common.toasts.checkConnection")}</Typography>
</GenericFallback>
);
}
if (!isLoading && monitors?.length === 0) {
return (
<Fallback
type="pageSpeed"
title={t("pageSpeed.fallback.title")}
checks={t("pageSpeed.fallback.checks", { returnObjects: true })}
link="/pagespeed/create"
isAdmin={isAdmin}
>
{isAdmin && settingsData && !settingsData.pagespeedApiKey && (
<FallbackPageSpeedWarning settingsData={settingsData} />
)}
</Fallback>
);
}
return (
<Stack gap={theme.spacing(10)}>
<Breadcrumbs list={BREADCRUMBS} />
<CreateMonitorHeader
isAdmin={isAdmin}
<>
<PageStateWrapper
networkError={networkError}
isLoading={isLoading}
path="/pagespeed/create"
/>
<MonitorCountHeader
isLoading={isLoading}
monitorCount={monitorsSummary?.totalMonitors}
sx={{ mb: theme.spacing(8) }}
/>
<MonitorGrid
size={6}
shouldRender={!isLoading}
monitors={monitors}
/>
</Stack>
items={monitors}
type="pageSpeed"
fallbackLink="/pagespeed/create"
fallbackChildren={
isAdmin &&
settingsData &&
!settingsData.pagespeedApiKey && (
<FallbackPageSpeedWarning settingsData={settingsData} />
)
}
>
<Stack gap={theme.spacing(10)}>
<Breadcrumbs list={BREADCRUMBS} />
<CreateMonitorHeader
isAdmin={isAdmin}
isLoading={isLoading}
path="/pagespeed/create"
/>
<MonitorCountHeader
isLoading={isLoading}
monitorCount={monitorsSummary?.totalMonitors}
sx={{ mb: theme.spacing(8) }}
/>
<MonitorGrid
size={6}
shouldRender={!isLoading}
monitors={monitors}
/>
</Stack>
</PageStateWrapper>
</>
);
};
@@ -11,7 +11,12 @@ import PropTypes from "prop-types";
import { useSelector } from "react-redux";
const MonitorsList = ({ isLoading = false, shouldRender = true, monitors = [] }) => {
const MonitorsList = ({
isLoading = false,
shouldRender = true,
monitors = [],
statusPage = {},
}) => {
const theme = useTheme();
const { determineState } = useMonitorUtils();
@@ -40,10 +45,12 @@ const MonitorsList = ({ isLoading = false, shouldRender = true, monitors = [] })
alignItems="center"
gap={theme.spacing(20)}
>
<Box flex={9}>
<StatusPageBarChart checks={monitor?.checks?.slice().reverse()} />
</Box>
<Box flex={1}>
{statusPage.showCharts !== false && (
<Box flex={9}>
<StatusPageBarChart checks={monitor?.checks?.slice().reverse()} />
</Box>
)}
<Box flex={statusPage.showCharts !== false ? 1 : 10}>
<StatusLabel
status={status}
text={status}
@@ -60,6 +67,7 @@ const MonitorsList = ({ isLoading = false, shouldRender = true, monitors = [] })
MonitorsList.propTypes = {
monitors: PropTypes.array.isRequired,
statusPage: PropTypes.object,
};
export default MonitorsList;
+4 -1
View File
@@ -154,7 +154,10 @@ const PublicStatus = () => {
/>
<Typography variant="h2">{t("statusPageStatusServiceStatus")}</Typography>
<StatusBar monitors={monitors} />
<MonitorsList monitors={monitors} />
<MonitorsList
monitors={monitors}
statusPage={statusPage}
/>
{link}
</Stack>
);
@@ -1,14 +1,11 @@
// Components
import { Stack, Typography } from "@mui/material";
import { Stack } from "@mui/material";
import Breadcrumbs from "../../../Components/Breadcrumbs";
import Fallback from "../../../Components/Fallback";
import MonitorCreateHeader from "../../../Components/MonitorCreateHeader";
import GenericFallback from "../../../Components/GenericFallback";
import StatusPagesTable from "./Components/StatusPagesTable";
import SkeletonLayout from "../../../Components/Skeletons/FullPage";
import PageStateWrapper from "../../../Components/PageStateWrapper";
// Utils
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
import { useStatusPagesFetch } from "./Hooks/useStatusPagesFetch";
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
const BREADCRUMBS = [{ name: `Status Pages`, path: "" }];
@@ -16,52 +13,30 @@ const BREADCRUMBS = [{ name: `Status Pages`, path: "" }];
const StatusPages = () => {
// Utils
const theme = useTheme();
const { t } = useTranslation();
const isAdmin = useIsAdmin();
const [isLoading, networkError, statusPages] = useStatusPagesFetch();
if (isLoading) {
return <SkeletonLayout />;
}
if (networkError === true) {
return (
<GenericFallback>
<Typography
variant="h1"
marginY={theme.spacing(4)}
color={theme.palette.primary.contrastTextTertiary}
>
{t("common.toasts.networkError")}
</Typography>
<Typography>{t("common.toasts.checkConnection")}</Typography>
</GenericFallback>
);
}
if (!isLoading && typeof statusPages !== "undefined" && statusPages.length === 0) {
return (
<Fallback
title={t("statusPage.fallback.title")}
checks={t("statusPage.fallback.checks", { returnObjects: true })}
type="statusPage"
link="/status/uptime/create"
isAdmin={isAdmin}
/>
);
}
return (
<Stack gap={theme.spacing(10)}>
<Breadcrumbs list={BREADCRUMBS} />
<MonitorCreateHeader
label="Create status page"
isAdmin={isAdmin}
path="/status/uptime/create"
<>
<PageStateWrapper
networkError={networkError}
isLoading={isLoading}
/>
<StatusPagesTable data={statusPages} />
</Stack>
items={statusPages}
type="statusPage"
fallbackLink="/status/uptime/create"
>
<Stack gap={theme.spacing(10)}>
<Breadcrumbs list={BREADCRUMBS} />
<MonitorCreateHeader
label="Create status page"
isAdmin={isAdmin}
path="/status/uptime/create"
isLoading={isLoading}
/>
<StatusPagesTable data={statusPages} />
</Stack>
</PageStateWrapper>
</>
);
};
+70 -90
View File
@@ -10,16 +10,14 @@ import StatusBoxes from "./Components/StatusBoxes";
import UptimeDataTable from "./Components/UptimeDataTable";
import Pagination from "../../../Components/Table/TablePagination";
import CreateMonitorHeader from "../../../Components/MonitorCreateHeader";
import Fallback from "../../../Components/Fallback";
import GenericFallback from "../../../Components/GenericFallback";
import SearchComponent from "./Components/SearchComponent";
import Filter from "./Components/Filter";
import PageStateWrapper from "../../../Components/PageStateWrapper";
import MonitorCountHeader from "../../../Components/MonitorCountHeader";
// MUI Components
import { Stack, Box, Button, Typography } from "@mui/material";
import { Stack, Box, Button } from "@mui/material";
// Utils
import { useState, useCallback, useEffect } from "react";
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
@@ -153,93 +151,75 @@ const UptimeMonitors = () => {
}, [isSearching]);
const isLoading = monitorsWithSummaryIsLoading || monitorsWithChecksIsLoading;
if (networkError) {
return (
<GenericFallback>
<Typography
variant="h1"
marginY={theme.spacing(4)}
color={theme.palette.primary.contrastTextTertiary}
>
{t("common.toasts.networkError")}
</Typography>
<Typography>{t("common.toasts.checkConnection")}</Typography>
</GenericFallback>
);
}
if (
!isLoading &&
(monitorsSummary?.totalMonitors === 0 ||
typeof monitorsSummary?.totalMonitors === "undefined")
) {
return (
<Fallback
type="uptimeMonitor"
title={t("uptimeMonitor.fallback.title")}
checks={t("uptimeMonitor.fallback.checks", { returnObjects: true })}
link="/uptime/create"
isAdmin={isAdmin}
/>
);
}
return (
<Stack
className="monitors"
gap={theme.spacing(10)}
>
<Breadcrumbs list={BREADCRUMBS} />
<CreateMonitorHeader
isAdmin={isAdmin}
isLoading={isLoading}
path="/uptime/create"
bulkPath="/uptime/bulk-import"
/>
<Greeting type="uptime" />
<StatusBoxes
monitorsSummary={monitorsSummary}
shouldRender={!monitorsWithSummaryIsLoading}
/>
<Stack direction={"row"}>
<MonitorCountHeader
isLoading={monitorsWithSummaryIsLoading}
monitorCount={monitorsSummary?.totalMonitors}
/>
<Filter
selectedTypes={selectedTypes}
setSelectedTypes={setSelectedTypes}
selectedStatus={selectedStatus}
setSelectedStatus={setSelectedStatus}
selectedState={selectedState}
setSelectedState={setSelectedState}
setToFilterStatus={setToFilterStatus}
setToFilterActive={setToFilterActive}
handleReset={handleReset}
/>
<SearchComponent
monitors={monitors}
onSearchChange={setSearch}
setIsSearching={setIsSearching}
/>
</Stack>
<UptimeDataTable
isAdmin={isAdmin}
isSearching={isSearching}
filteredMonitors={monitorsWithChecks}
sort={sort}
setSort={setSort}
monitorsAreLoading={monitorsWithChecksIsLoading}
triggerUpdate={triggerUpdate}
/>
<Pagination
itemCount={monitorsWithChecksCount}
paginationLabel="monitors"
page={page}
rowsPerPage={rowsPerPage}
handleChangePage={handleChangePage}
handleChangeRowsPerPage={handleChangeRowsPerPage}
/>
</Stack>
return (
<>
<PageStateWrapper
networkError={networkError}
isLoading={isLoading}
items={monitors}
type="uptimeMonitor"
fallbackLink="/uptime/create"
>
<Stack
className="monitors"
gap={theme.spacing(10)}
>
<Breadcrumbs list={BREADCRUMBS} />
<CreateMonitorHeader
isAdmin={isAdmin}
isLoading={isLoading}
path="/uptime/create"
bulkPath="/uptime/bulk-import"
/>
<Greeting type="uptime" />
<StatusBoxes
monitorsSummary={monitorsSummary}
shouldRender={!monitorsWithSummaryIsLoading}
/>
<Stack direction={"row"}>
<MonitorCountHeader
isLoading={monitorsWithSummaryIsLoading}
monitorCount={monitorsSummary?.totalMonitors}
/>
<Filter
selectedTypes={selectedTypes}
setSelectedTypes={setSelectedTypes}
selectedStatus={selectedStatus}
setSelectedStatus={setSelectedStatus}
selectedState={selectedState}
setSelectedState={setSelectedState}
setToFilterStatus={setToFilterStatus}
setToFilterActive={setToFilterActive}
handleReset={handleReset}
/>
<SearchComponent
monitors={monitors}
onSearchChange={setSearch}
setIsSearching={setIsSearching}
/>
</Stack>
<UptimeDataTable
isAdmin={isAdmin}
isSearching={isSearching}
filteredMonitors={monitorsWithChecks}
sort={sort}
setSort={setSort}
monitorsAreLoading={monitorsWithChecksIsLoading}
triggerUpdate={triggerUpdate}
/>
<Pagination
itemCount={monitorsWithChecksCount}
paginationLabel="monitors"
page={page}
rowsPerPage={rowsPerPage}
handleChangePage={handleChangePage}
handleChangeRowsPerPage={handleChangeRowsPerPage}
/>
</Stack>
</PageStateWrapper>
</>
);
};
+1 -1
View File
@@ -223,7 +223,7 @@ const Routes = () => {
<Route
exact
path="/register/:token"
element={<AuthRegister />}
element={<AuthRegister superAdminExists={true} />}
/>
<Route
+14 -2
View File
@@ -130,10 +130,12 @@ const monitorValidation = joi.object({
then: joi
.string()
.trim()
.regex(/^[a-z0-9]{64}$/)
.regex(
/^(\/+)?([a-zA-Z0-9][a-zA-Z0-9_.-]*[a-zA-Z0-9]|[a-zA-Z0-9]+|[a-f0-9]{12,64})$/
)
.messages({
"string.empty": "This field is required.",
"string.pattern.base": "Please enter a valid 64-character Docker container ID.",
"string.pattern.base": "Please enter a valid container name or ID.",
}),
otherwise: joi
.string()
@@ -435,6 +437,16 @@ const infrastructureMonitorValidation = joi.object({
"number.base": "Frequency must be a number.",
"any.required": "Frequency is required.",
}),
statusWindowSize: joi.number().min(1).max(20).messages({
"number.base": "Status window size must be a number.",
"number.min": "Status window size must be at least 1.",
"number.max": "Status window size cannot exceed 20.",
}),
statusWindowThreshold: joi.number().min(1).max(100).messages({
"number.base": "Status window threshold must be a number.",
"number.min": "Status window threshold must be at least 1%.",
"number.max": "Status window threshold cannot exceed 100%.",
}),
notifications: joi.array().items(joi.string()),
});
+48 -2
View File
@@ -376,7 +376,52 @@
"email": "",
"role": "",
"created": ""
}
},
"addTeamMember": {
"addMemberMenu": "",
"title": "",
"description": "",
"addButton": ""
},
"register": "",
"registerToast": {
"success": "",
"dbUserExists": "",
"unknownError": ""
},
"registerTeamMember": {
"title": "",
"auth": {
"common": {
"inputs": {
"firstName": {
"errors": {
"empty": "",
"pattern": ""
}
},
"lastName": {
"errors": {
"empty": "",
"pattern": ""
}
},
"email": {
"errors": {
"empty": "",
"invalid": ""
}
},
"role": {
"errors": {
"empty": ""
}
}
}
}
}
},
"role": ""
},
"monitorState": {
"paused": "",
@@ -742,7 +787,8 @@
"failCountHeader": "",
"lastRunHeader": "",
"lastFinishedAtHeader": "",
"lastRunTookHeader": ""
"lastRunTookHeader": "",
"intervalHeader": ""
},
"metricsTable": {
"title": "",
+48 -2
View File
@@ -376,7 +376,52 @@
"email": "",
"role": "",
"created": ""
}
},
"addTeamMember": {
"addMemberMenu": "",
"title": "",
"description": "",
"addButton": ""
},
"register": "",
"registerToast": {
"success": "",
"dbUserExists": "",
"unknownError": ""
},
"registerTeamMember": {
"title": "",
"auth": {
"common": {
"inputs": {
"firstName": {
"errors": {
"empty": "",
"pattern": ""
}
},
"lastName": {
"errors": {
"empty": "",
"pattern": ""
}
},
"email": {
"errors": {
"empty": "",
"invalid": ""
}
},
"role": {
"errors": {
"empty": ""
}
}
}
}
}
},
"role": ""
},
"monitorState": {
"paused": "",
@@ -742,7 +787,8 @@
"failCountHeader": "",
"lastRunHeader": "",
"lastFinishedAtHeader": "",
"lastRunTookHeader": ""
"lastRunTookHeader": "",
"intervalHeader": ""
},
"metricsTable": {
"title": "",
+48 -2
View File
@@ -380,7 +380,52 @@
"email": "Email",
"role": "Rolle",
"created": "Angelegt"
}
},
"addTeamMember": {
"addMemberMenu": "",
"title": "",
"description": "",
"addButton": ""
},
"register": "",
"registerToast": {
"success": "",
"dbUserExists": "",
"unknownError": ""
},
"registerTeamMember": {
"title": "",
"auth": {
"common": {
"inputs": {
"firstName": {
"errors": {
"empty": "",
"pattern": ""
}
},
"lastName": {
"errors": {
"empty": "",
"pattern": ""
}
},
"email": {
"errors": {
"empty": "",
"invalid": ""
}
},
"role": {
"errors": {
"empty": ""
}
}
}
}
}
},
"role": ""
},
"monitorState": {
"paused": "Pausiert",
@@ -746,7 +791,8 @@
"failCountHeader": "Anzahl Fehler",
"lastRunHeader": "Letzter Lauf um",
"lastFinishedAtHeader": "Zuletzt abgeschlossen um",
"lastRunTookHeader": "Letzter Lauf dauerte"
"lastRunTookHeader": "Letzter Lauf dauerte",
"intervalHeader": ""
},
"metricsTable": {
"title": "Warteschlangenmetriken",
+52 -6
View File
@@ -251,7 +251,7 @@
"edit": "Edit",
"createA": "Create a",
"remove": "Remove",
"maintenanceWindowDescription": "Your pings won't be sent during this time frame",
"maintenanceWindowDescription": "During maintenance windows, all monitoring is suspended for selected monitors. No network checks will be performed, preventing any status updates or notifications from being triggered. Your monitors will appear frozen at their last known status, and status pages will display a maintenance indicator. Once the maintenance window ends, monitoring automatically resumes, and alerts will trigger if issues are detected. Maintenance periods do not count against uptime calculations.",
"startTime": "Start time",
"timeZoneInfo": "All dates and times are in GMT+0 time zone.",
"monitorsToApply": "Monitors to apply maintenance window to",
@@ -380,7 +380,52 @@
"email": "Email",
"role": "Role",
"created": "Created"
}
},
"addTeamMember": {
"addMemberMenu": "Add Team Member",
"title": "Register new team member",
"description": "Create a new user and share the credentials with them. This method gives the member immediate access to all monitors.",
"addButton": "Add Member"
},
"register": "Register a team member",
"registerToast": {
"success": "User created, share credentials with the member securely.",
"dbUserExists": "User already exists.",
"unknownError": "Unknown error occurred."
},
"registerTeamMember": {
"title": "Register team member",
"auth": {
"common": {
"inputs": {
"firstName": {
"errors": {
"empty": "Please enter a name",
"pattern": "Name must contain only letters, spaces, apostrophes, or hyphens"
}
},
"lastName": {
"errors": {
"empty": "Please enter a surname",
"pattern": "Surname must contain only letters, spaces, apostrophes, or hyphens"
}
},
"email": {
"errors": {
"empty": "To continue, please enter an email address",
"invalid": "Please recheck validity of entered email address"
}
},
"role": {
"errors": {
"empty": "Role is required"
}
}
}
}
}
},
"role": "Role"
},
"monitorState": {
"paused": "Paused",
@@ -440,7 +485,7 @@
"uptimeGeneralInstructions": {
"http": "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.",
"ping": "Enter the IP address or hostname to ping (e.g., 192.168.1.100 or example.com) and add a clear display name that appears on the dashboard.",
"docker": "Enter the Docker ID of your container. Docker IDs must be the full 64 char Docker ID. You can run docker inspect <short_id> to get the full container ID.",
"docker": "Enter the Docker container name or ID. You can use either the container name (e.g., my-app) or the container ID (full 64-char ID or short ID).",
"port": "Enter the URL or IP of the server, the port number and a clear display name that appears on the dashboard.",
"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."
},
@@ -746,7 +791,8 @@
"failCountHeader": "Fail count",
"lastRunHeader": "Last run at",
"lastFinishedAtHeader": "Last finished at",
"lastRunTookHeader": "Last run took"
"lastRunTookHeader": "Last run took",
"intervalHeader": "Interval"
},
"metricsTable": {
"title": "Queue metrics",
@@ -1014,9 +1060,9 @@
},
"monitorType": {
"docker": {
"label": "Container ID",
"label": "Container Name/ID",
"namePlaceholder": "My Container",
"placeholder": "abcd1234"
"placeholder": "my-app or abcd1234"
},
"http": {
"label": "URL to monitor",
+48 -2
View File
@@ -376,7 +376,52 @@
"email": "",
"role": "",
"created": ""
}
},
"addTeamMember": {
"addMemberMenu": "",
"title": "",
"description": "",
"addButton": ""
},
"register": "",
"registerToast": {
"success": "",
"dbUserExists": "",
"unknownError": ""
},
"registerTeamMember": {
"title": "",
"auth": {
"common": {
"inputs": {
"firstName": {
"errors": {
"empty": "",
"pattern": ""
}
},
"lastName": {
"errors": {
"empty": "",
"pattern": ""
}
},
"email": {
"errors": {
"empty": "",
"invalid": ""
}
},
"role": {
"errors": {
"empty": ""
}
}
}
}
}
},
"role": ""
},
"monitorState": {
"paused": "",
@@ -742,7 +787,8 @@
"failCountHeader": "",
"lastRunHeader": "",
"lastFinishedAtHeader": "",
"lastRunTookHeader": ""
"lastRunTookHeader": "",
"intervalHeader": ""
},
"metricsTable": {
"title": "",
+48 -2
View File
@@ -376,7 +376,52 @@
"email": "Sähköposti",
"role": "Rooli",
"created": "Luotu"
}
},
"addTeamMember": {
"addMemberMenu": "",
"title": "",
"description": "",
"addButton": ""
},
"register": "",
"registerToast": {
"success": "",
"dbUserExists": "",
"unknownError": ""
},
"registerTeamMember": {
"title": "",
"auth": {
"common": {
"inputs": {
"firstName": {
"errors": {
"empty": "",
"pattern": ""
}
},
"lastName": {
"errors": {
"empty": "",
"pattern": ""
}
},
"email": {
"errors": {
"empty": "",
"invalid": ""
}
},
"role": {
"errors": {
"empty": ""
}
}
}
}
}
},
"role": ""
},
"monitorState": {
"paused": "Tauota",
@@ -742,7 +787,8 @@
"failCountHeader": "",
"lastRunHeader": "",
"lastFinishedAtHeader": "",
"lastRunTookHeader": ""
"lastRunTookHeader": "",
"intervalHeader": ""
},
"metricsTable": {
"title": "",
+48 -2
View File
@@ -380,7 +380,52 @@
"email": "Email",
"role": "Rôle",
"created": "Créée"
}
},
"addTeamMember": {
"addMemberMenu": "",
"title": "",
"description": "",
"addButton": ""
},
"register": "",
"registerToast": {
"success": "",
"dbUserExists": "",
"unknownError": ""
},
"registerTeamMember": {
"title": "",
"auth": {
"common": {
"inputs": {
"firstName": {
"errors": {
"empty": "",
"pattern": ""
}
},
"lastName": {
"errors": {
"empty": "",
"pattern": ""
}
},
"email": {
"errors": {
"empty": "",
"invalid": ""
}
},
"role": {
"errors": {
"empty": ""
}
}
}
}
}
},
"role": ""
},
"monitorState": {
"paused": "Pause",
@@ -746,7 +791,8 @@
"failCountHeader": "Nombre d'échecs",
"lastRunHeader": "Dernière tentative le",
"lastFinishedAtHeader": "Dernière finalisation à",
"lastRunTookHeader": "Dernière exécution effectuée"
"lastRunTookHeader": "Dernière exécution effectuée",
"intervalHeader": ""
},
"metricsTable": {
"title": "Métriques des files",
+48 -2
View File
@@ -380,7 +380,52 @@
"email": "メール",
"role": "役割",
"created": "作成日"
}
},
"addTeamMember": {
"addMemberMenu": "",
"title": "",
"description": "",
"addButton": ""
},
"register": "",
"registerToast": {
"success": "",
"dbUserExists": "",
"unknownError": ""
},
"registerTeamMember": {
"title": "",
"auth": {
"common": {
"inputs": {
"firstName": {
"errors": {
"empty": "",
"pattern": ""
}
},
"lastName": {
"errors": {
"empty": "",
"pattern": ""
}
},
"email": {
"errors": {
"empty": "",
"invalid": ""
}
},
"role": {
"errors": {
"empty": ""
}
}
}
}
}
},
"role": ""
},
"monitorState": {
"paused": "一時停止",
@@ -746,7 +791,8 @@
"failCountHeader": "失敗回数",
"lastRunHeader": "最終実行日時",
"lastFinishedAtHeader": "最終完了日時",
"lastRunTookHeader": "最終実行時間"
"lastRunTookHeader": "最終実行時間",
"intervalHeader": ""
},
"metricsTable": {
"title": "キューメトリック",
+48 -2
View File
@@ -380,7 +380,52 @@
"email": "E-mail",
"role": "Cargo",
"created": "Criado"
}
},
"addTeamMember": {
"addMemberMenu": "",
"title": "",
"description": "",
"addButton": ""
},
"register": "",
"registerToast": {
"success": "",
"dbUserExists": "",
"unknownError": ""
},
"registerTeamMember": {
"title": "",
"auth": {
"common": {
"inputs": {
"firstName": {
"errors": {
"empty": "",
"pattern": ""
}
},
"lastName": {
"errors": {
"empty": "",
"pattern": ""
}
},
"email": {
"errors": {
"empty": "",
"invalid": ""
}
},
"role": {
"errors": {
"empty": ""
}
}
}
}
}
},
"role": ""
},
"monitorState": {
"paused": "Pausado",
@@ -746,7 +791,8 @@
"failCountHeader": "Contagem de falhas",
"lastRunHeader": "Última corrida em",
"lastFinishedAtHeader": "Última conclusão em",
"lastRunTookHeader": "A última corrida aconteceu"
"lastRunTookHeader": "A última corrida aconteceu",
"intervalHeader": ""
},
"metricsTable": {
"title": "Métricas de fila",
+111 -65
View File
@@ -376,7 +376,52 @@
"email": "Email",
"role": "Роль",
"created": "Создано"
}
},
"addTeamMember": {
"addMemberMenu": "",
"title": "",
"description": "",
"addButton": ""
},
"register": "",
"registerToast": {
"success": "",
"dbUserExists": "",
"unknownError": ""
},
"registerTeamMember": {
"title": "",
"auth": {
"common": {
"inputs": {
"firstName": {
"errors": {
"empty": "",
"pattern": ""
}
},
"lastName": {
"errors": {
"empty": "",
"pattern": ""
}
},
"email": {
"errors": {
"empty": "",
"invalid": ""
}
},
"role": {
"errors": {
"empty": ""
}
}
}
}
}
},
"role": ""
},
"monitorState": {
"paused": "Остановлено",
@@ -421,23 +466,23 @@
"passwordRequirements": "Новый пароль должен содержать не менее 8 символов и содержать как минимум одну заглавную букву, одну строчную букву, одну цифру и один специальный символ.",
"saving": "Сохранение..."
},
"emailSent": "",
"failedToSendEmail": "",
"settingsTestEmailSuccess": "",
"settingsTestEmailFailed": "",
"settingsTestEmailFailedWithReason": "",
"emailSent": "Письмо успешно отправлено",
"failedToSendEmail": "Не удалось отправить письмо",
"settingsTestEmailSuccess": "Тестовое письмо успешно отправлено",
"settingsTestEmailFailed": "Не удалось отправить тестовое письмо",
"settingsTestEmailFailedWithReason": "Не удалось отправить тестовое письмо: {{reason}}",
"settingsTestEmailUnknownError": "Неизвестная ошибка",
"statusMsg": {
"paused": "",
"paused": "Мониторинг приостановлен",
"up": "",
"down": "",
"pending": ""
"pending": "Ожидание..."
},
"uptimeGeneralInstructions": {
"http": "",
"ping": "",
"docker": "",
"port": "",
"http": "Введите URL-адрес или IP-адрес для мониторинга (например, https://example.com/ или 192.168.1.100) и добавьте понятное имя, которое будет отображаться на панели инструментов.",
"ping": "Ведите IP-адрес или имя хоста для пинга (например, 192.168.1.100 или example.com) и добавьте понятное имя, которое будет отображаться на панели инструментов.",
"docker": "Введите Docker ID вашего контейнера. Docker ID должен быть полным 64-символьным Docker ID. Вы можете запустить docker inspect <short_id>, чтобы получить полный ID контейнера.",
"port": "Введите URL-адрес или IP-адрес сервера, номер порта и понятное имя, которое будет отображаться на панели инструментов.",
"game": ""
},
"common": {
@@ -447,24 +492,24 @@
"toggleTheme": ""
},
"toasts": {
"networkError": "",
"checkConnection": "",
"networkError": "Ошибка сети",
"checkConnection": "Пожалуйста, проверьте свое сетевое соединение",
"unknownError": "Неизвестная ошибка"
}
},
"auth": {
"common": {
"navigation": {
"continue": "",
"continue": "Продолжить",
"back": "Назад"
},
"inputs": {
"email": {
"label": "",
"label": "Электронная почта",
"placeholder": "",
"errors": {
"empty": "Для продолжения введите свой адрес электронной почты",
"invalid": ""
"invalid": "Пожалуйста, проверьте правильность введённого адреса электронной почты"
}
},
"password": {
@@ -491,49 +536,49 @@
"highlighted": "одну прописную букву"
},
"match": {
"beginning": "",
"highlighted": ""
"beginning": "Пароли",
"highlighted": "должны совпадать"
}
},
"errors": {
"empty": "",
"length": "",
"uppercase": "",
"lowercase": "",
"number": "",
"special": "",
"incorrect": ""
"empty": "Пожалуйста, введите пароль",
"length": "Пароль должен быть длиной не менее 8 символов",
"uppercase": "Пароль должен содержать минимум 1 прописную букву",
"lowercase": "Пароль должен содержать минимум 1 строчную букву",
"number": "Пароль должен содержать минимум 1 цифру",
"special": "Пароль должен содержать минимум 1 спецсимвол",
"incorrect": "Предоставленный вами пароль не соответствует нашим записям"
}
},
"passwordConfirm": {
"label": "",
"placeholder": "",
"label": "Подтвердите пароль",
"placeholder": "Введите пароль еще раз для подтверждения",
"errors": {
"empty": "",
"different": ""
"empty": "Пожалуйста, введите свой пароль еще раз для подтверждения (помогает избежать опечаток)",
"different": "Введённые пароли не совпадают, вероятно, один из них напечатан с ошибкой"
}
},
"firstName": {
"label": "",
"label": "Имя",
"placeholder": "",
"errors": {
"empty": "",
"length": "",
"pattern": ""
"empty": "Пожалуйста, введите свое имя",
"length": "Имя должно быть короче 50 символов",
"pattern": "Имя должно содержать только буквы, пробелы, апострофы или дефисы"
}
},
"lastName": {
"label": "",
"label": "Фамилия",
"placeholder": "",
"errors": {
"empty": "",
"length": "",
"pattern": ""
"empty": "Пожалуйста, введите свою фамилию",
"length": "Фамилия должна быть короче 50 символов",
"pattern": "Фамилия должна содержать только буквы, пробелы, апострофы или дефисы"
}
}
},
"errors": {
"validation": ""
"validation": "Ошибка при проверке данных."
},
"fields": {
"password": {
@@ -549,20 +594,20 @@
}
},
"login": {
"heading": "",
"heading": "Войдите, чтобы продолжить",
"subheadings": {
"stepOne": "",
"stepTwo": ""
"stepOne": "Введите свою электронную почту",
"stepTwo": "Введите свой пароль"
},
"links": {
"forgotPassword": "",
"register": "",
"forgotPassword": "Забыли пароль?",
"register": "У вас нет учетной записи?",
"forgotPasswordLink": "",
"registerLink": ""
},
"toasts": {
"success": "",
"incorrectPassword": ""
"success": "С возвращением! Вы успешно вошли в систему.",
"incorrectPassword": "Неверный пароль"
},
"errors": {
"password": {
@@ -573,42 +618,42 @@
},
"registration": {
"heading": {
"superAdmin": "",
"user": ""
"superAdmin": "Создать супер-администратора",
"user": "Зарегистрироваться"
},
"subheadings": {
"stepOne": "",
"stepTwo": "",
"stepThree": ""
"stepOne": "Введите свои личные данные",
"stepTwo": "Введите вашу электронную почту",
"stepThree": "Создайте свой пароль"
},
"description": {
"superAdmin": "",
"user": ""
"superAdmin": "Создайте свой аккаунт супер-администратора, чтобы начать",
"user": "Зарегистрируйтесь как пользователь и обратитесь к супер-администратору для доступа к вашим мониторам"
},
"gettingStartedButton": {
"superAdmin": "",
"user": ""
"superAdmin": "Создать учетную запись супер-администратора",
"user": "Зарегистрироваться как обычный пользователь"
},
"termsAndPolicies": "",
"termsAndPolicies": "Создавая учетную запись, вы соглашаетесь с нашими <a1>Условиями предоставления услуг</a1> и <a2>Политикой конфиденциальности</a2>.",
"links": {
"login": ""
"login": "Уже есть учетная запись? <a>Войти</a>"
},
"toasts": {
"success": ""
"success": "Добро пожаловать! Ваша учетная запись была создана успешно."
},
"welcome": ""
},
"forgotPassword": {
"heading": "",
"heading": "Забыли пароль?",
"subheadings": {
"stepOne": "",
"stepTwo": "",
"stepThree": "",
"stepFour": ""
"stepOne": "Не волнуйтесь, мы вышлем вам инструкции для сброса.",
"stepTwo": "Мы выслали ссылку для сброса пароля на <email/>",
"stepThree": "Ваш новый пароль должен отличаться от ваших предыдущих пароей.",
"stepFour": "Ваш пароль был успешно сброшен. Нажмите ниже, чтобы волшебным образом войти в систему."
},
"buttons": {
"openEmail": "",
"resetPassword": ""
"resetPassword": "Сбросить пароль"
},
"imageAlts": {
"passwordKey": "",
@@ -742,7 +787,8 @@
"failCountHeader": "",
"lastRunHeader": "",
"lastFinishedAtHeader": "",
"lastRunTookHeader": ""
"lastRunTookHeader": "",
"intervalHeader": ""
},
"metricsTable": {
"title": "",
File diff suppressed because it is too large. Load diff
+48 -2
View File
@@ -376,7 +376,52 @@
"email": "Eposta",
"role": "Rol",
"created": "Oluşturulma"
}
},
"addTeamMember": {
"addMemberMenu": "",
"title": "",
"description": "",
"addButton": ""
},
"register": "",
"registerToast": {
"success": "",
"dbUserExists": "",
"unknownError": ""
},
"registerTeamMember": {
"title": "",
"auth": {
"common": {
"inputs": {
"firstName": {
"errors": {
"empty": "",
"pattern": ""
}
},
"lastName": {
"errors": {
"empty": "",
"pattern": ""
}
},
"email": {
"errors": {
"empty": "",
"invalid": ""
}
},
"role": {
"errors": {
"empty": ""
}
}
}
}
}
},
"role": ""
},
"monitorState": {
"paused": "Durduruldu",
@@ -742,7 +787,8 @@
"failCountHeader": "",
"lastRunHeader": "",
"lastFinishedAtHeader": "Son tamamlama",
"lastRunTookHeader": ""
"lastRunTookHeader": "",
"intervalHeader": ""
},
"metricsTable": {
"title": "",
File diff suppressed because it is too large. Load diff
+48 -2
View File
@@ -380,7 +380,52 @@
"email": "",
"role": "",
"created": ""
}
},
"addTeamMember": {
"addMemberMenu": "",
"title": "",
"description": "",
"addButton": ""
},
"register": "",
"registerToast": {
"success": "",
"dbUserExists": "",
"unknownError": ""
},
"registerTeamMember": {
"title": "",
"auth": {
"common": {
"inputs": {
"firstName": {
"errors": {
"empty": "",
"pattern": ""
}
},
"lastName": {
"errors": {
"empty": "",
"pattern": ""
}
},
"email": {
"errors": {
"empty": "",
"invalid": ""
}
},
"role": {
"errors": {
"empty": ""
}
}
}
}
}
},
"role": ""
},
"monitorState": {
"paused": "",
@@ -746,7 +791,8 @@
"failCountHeader": "",
"lastRunHeader": "",
"lastFinishedAtHeader": "",
"lastRunTookHeader": ""
"lastRunTookHeader": "",
"intervalHeader": ""
},
"metricsTable": {
"title": "",
+1 -1
View File
@@ -5,7 +5,7 @@ import { execSync } from "child_process";
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), "");
let version = "3.1.2";
let version = "3.2.0";
return {
base: "/",
+1 -10
View File
@@ -1,18 +1,9 @@
FROM node:20-alpine AS build
FROM node:20-slim AS build
ENV NODE_OPTIONS="--max-old-space-size=4096"
WORKDIR /app
RUN apk add --no-cache \
python3 \
make g++ \
gcc \
libc-dev \
linux-headers \
libusb-dev \
eudev-dev
COPY ./client/package*.json ./
RUN npm install
+6 -1
View File
@@ -1,4 +1,9 @@
FROM node:20-alpine
FROM node:20-slim
# Install ping
RUN apt-get update \
&& apt-get install -y iputils-ping \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
+1 -11
View File
@@ -1,19 +1,9 @@
FROM node:20-alpine AS build
FROM node:20-slim AS build
ENV NODE_OPTIONS="--max-old-space-size=4096"
WORKDIR /app
RUN apk add --no-cache \
python3 \
make g++ \
gcc \
libc-dev \
linux-headers \
libusb-dev \
eudev-dev
COPY ./client/package*.json ./
RUN npm install
+6 -1
View File
@@ -1,4 +1,9 @@
FROM node:20-alpine
FROM node:20-slim
# Install ping
RUN apt-get update \
&& apt-get install -y iputils-ping \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
+2 -1
View File
@@ -2,6 +2,7 @@ services:
server:
image: ghcr.io/bluewave-labs/checkmate-backend-mono-multiarch:latest
restart: always
pull_policy: always
ports:
- "52345:52345"
environment:
@@ -16,7 +17,7 @@ services:
mongodb:
image: mongo:4.4.18
restart: always
command: ["mongod", "--quiet", --bind_ip_all"]
command: ["mongod", "--quiet", "--bind_ip_all"]
ports:
- "27017:27017"
volumes:
+26 -7
View File
@@ -1,25 +1,44 @@
FROM node:20-alpine AS frontend-build
# ---------------------
# Frontend build stage
# ---------------------
FROM node:24-slim AS frontend-build
WORKDIR /app/client
COPY client/package*.json ./
RUN npm ci
COPY client/package.json ./
RUN npm install
RUN npm install esbuild@0.25.5 --build-from-source
COPY client ./
RUN npm run build
FROM node:20-alpine AS backend
# ---------------------
# Backend stage
# ---------------------
FROM node:24-slim AS backend
# Install ping
RUN apt-get update \
&& apt-get install -y iputils-ping \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app/server
COPY server/package.json ./
RUN npm install
COPY server ./
COPY --from=frontend-build /app/client/dist ./public
RUN npm ci
RUN chmod +x ./scripts/inject-vars.sh
EXPOSE 52345
CMD ./scripts/inject-vars.sh && node ./src/index.js
CMD ["sh", "-c", "./scripts/inject-vars.sh && node ./src/index.js"]
+1
View File
@@ -1,6 +1,7 @@
services:
server:
image: ghcr.io/bluewave-labs/checkmate-backend-mono:latest
pull_policy: always
restart: always
ports:
- "52345:52345"
+7 -2
View File
@@ -1,4 +1,4 @@
FROM node:20-alpine AS frontend-build
FROM node:20-slim AS frontend-build
WORKDIR /app/client
@@ -8,7 +8,12 @@ RUN npm install
COPY client ./
RUN npm run build
FROM node:20-alpine AS app
FROM node:20-slim AS app
# Install ping
RUN apt-get update \
&& apt-get install -y iputils-ping \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app/server
+1 -10
View File
@@ -1,18 +1,9 @@
FROM node:20-alpine AS build
FROM node:20-slim AS build
ENV NODE_OPTIONS="--max-old-space-size=4096"
WORKDIR /app
RUN apk add --no-cache \
python3 \
make g++ \
gcc \
libc-dev \
linux-headers \
libusb-dev \
eudev-dev
COPY ./client/package*.json ./
RUN npm install
+6 -1
View File
@@ -1,4 +1,9 @@
FROM node:20-alpine
FROM node:20-slim
# Install ping
RUN apt-get update \
&& apt-get install -y iputils-ping \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
+1 -10
View File
@@ -1,18 +1,9 @@
FROM node:20-alpine AS build
FROM node:20-slim AS build
ENV NODE_OPTIONS="--max-old-space-size=4096"
WORKDIR /app
RUN apk add --no-cache \
python3 \
make g++ \
gcc \
libc-dev \
linux-headers \
libusb-dev \
eudev-dev
COPY ./client/package*.json ./
RUN npm install
+6 -1
View File
@@ -1,4 +1,9 @@
FROM node:20-alpine
FROM node:20-slim
# Install ping
RUN apt-get update \
&& apt-get install -y iputils-ping \
&& rm -rf /var/lib/apt/lists/*
ENV NODE_OPTIONS="--max-old-space-size=2048"
+1 -11
View File
@@ -1,19 +1,9 @@
FROM node:20-alpine AS build
FROM node:20-slim AS build
ENV NODE_OPTIONS="--max-old-space-size=4096"
WORKDIR /app
RUN apk add --no-cache \
python3 \
make g++ \
gcc \
libc-dev \
linux-headers \
libusb-dev \
eudev-dev
COPY ./client/package*.json ./
RUN npm install
+6 -1
View File
@@ -1,4 +1,9 @@
FROM node:20-alpine
FROM node:20-slim
# Install ping
RUN apt-get update \
&& apt-get install -y iputils-ping \
&& rm -rf /var/lib/apt/lists/*
ENV NODE_OPTIONS="--max-old-space-size=2048"
+285 -281
View File
@@ -39,7 +39,7 @@
"ping": "0.4.4",
"sharp": "0.33.5",
"ssl-checker": "2.0.10",
"super-simple-scheduler": "1.3.0",
"super-simple-scheduler": "1.4.1",
"swagger-ui-express": "5.0.1",
"winston": "^3.13.0"
},
@@ -94,9 +94,9 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.27.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz",
"integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz",
"integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -128,9 +128,9 @@
}
},
"node_modules/@csstools/color-helpers": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz",
"integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==",
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
"integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==",
"funding": [
{
"type": "github",
@@ -170,9 +170,9 @@
}
},
"node_modules/@csstools/css-color-parser": {
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz",
"integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz",
"integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==",
"funding": [
{
"type": "github",
@@ -185,7 +185,7 @@
],
"license": "MIT",
"dependencies": {
"@csstools/color-helpers": "^5.0.2",
"@csstools/color-helpers": "^5.1.0",
"@csstools/css-calc": "^2.1.4"
},
"engines": {
@@ -249,9 +249,9 @@
}
},
"node_modules/@emnapi/runtime": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz",
"integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==",
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz",
"integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==",
"license": "MIT",
"optional": true,
"dependencies": {
@@ -259,9 +259,9 @@
}
},
"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",
"integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.8.0.tgz",
"integrity": "sha512-MJQFqrZgcW0UNYLGOuQpey/oTN59vyWwplvCGZztn1cKz9agZPPYpJB7h2OMmuu7VLqkvEjN8feFZJmxNF9D+Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -316,9 +316,9 @@
}
},
"node_modules/@eslint/config-helpers": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz",
"integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==",
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz",
"integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -326,9 +326,9 @@
}
},
"node_modules/@eslint/core": {
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz",
"integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==",
"version": "0.15.2",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz",
"integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -376,9 +376,9 @@
}
},
"node_modules/@eslint/js": {
"version": "9.31.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz",
"integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==",
"version": "9.34.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.34.0.tgz",
"integrity": "sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -399,13 +399,13 @@
}
},
"node_modules/@eslint/plugin-kit": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz",
"integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==",
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz",
"integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@eslint/core": "^0.15.1",
"@eslint/core": "^0.15.2",
"levn": "^0.4.1"
},
"engines": {
@@ -469,33 +469,19 @@
}
},
"node_modules/@humanfs/node": {
"version": "0.16.6",
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
"integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
"version": "0.16.7",
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
"integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@humanfs/core": "^0.19.1",
"@humanwhocodes/retry": "^0.3.0"
"@humanwhocodes/retry": "^0.4.0"
},
"engines": {
"node": ">=18.18.0"
}
},
"node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
"integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=18.18"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/nzakas"
}
},
"node_modules/@humanwhocodes/module-importer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
@@ -886,9 +872,9 @@
}
},
"node_modules/@ioredis/commands": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz",
"integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.3.1.tgz",
"integrity": "sha512-bYtU8avhGIcje3IhvF9aSjsa5URMZBHnwKtOvXsT4sfYy9gppW11gLPT/9oNqlJZD47yPKveQFTAFWpHjKvUoQ==",
"license": "MIT"
},
"node_modules/@isaacs/cliui": {
@@ -929,16 +915,16 @@
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
"integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.29",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz",
"integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
"version": "0.3.30",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz",
"integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1198,14 +1184,13 @@
}
},
"node_modules/@sinonjs/samsam": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.2.tgz",
"integrity": "sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==",
"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",
"lodash.get": "^4.4.2",
"type-detect": "^4.1.0"
}
},
@@ -1238,6 +1223,17 @@
"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,
"peer": true,
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -1266,14 +1262,20 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "24.1.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz",
"integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==",
"version": "24.3.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz",
"integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.8.0"
"undici-types": "~7.10.0"
}
},
"node_modules/@types/relateurl": {
"version": "0.2.33",
"resolved": "https://registry.npmjs.org/@types/relateurl/-/relateurl-0.2.33.tgz",
"integrity": "sha512-bTQCKsVbIdzLqZhLkF5fcJQreE4y1ro4DIyVrlDNSCJRRwHhB8Z+4zXXa8jN6eDvc2HbRsEYgbvrnGvi54EpSw==",
"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",
@@ -1383,9 +1385,9 @@
}
},
"node_modules/ansi-regex": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz",
"integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -1503,12 +1505,6 @@
"readable-stream": "~1.0.2"
}
},
"node_modules/barse/node_modules/isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==",
"license": "MIT"
},
"node_modules/barse/node_modules/readable-stream": {
"version": "1.0.34",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
@@ -1664,9 +1660,9 @@
"license": "ISC"
},
"node_modules/browserslist": {
"version": "4.25.1",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz",
"integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==",
"version": "4.25.4",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.4.tgz",
"integrity": "sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==",
"funding": [
{
"type": "opencollective",
@@ -1683,8 +1679,8 @@
],
"license": "MIT",
"dependencies": {
"caniuse-lite": "^1.0.30001726",
"electron-to-chromium": "^1.5.173",
"caniuse-lite": "^1.0.30001737",
"electron-to-chromium": "^1.5.211",
"node-releases": "^2.0.19",
"update-browserslist-db": "^1.1.3"
},
@@ -1909,9 +1905,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001727",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz",
"integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==",
"version": "1.0.30001739",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001739.tgz",
"integrity": "sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA==",
"funding": [
{
"type": "opencollective",
@@ -2303,6 +2299,12 @@
"typedarray": "^0.0.6"
}
},
"node_modules/concat-stream/node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"license": "MIT"
},
"node_modules/concat-stream/node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
@@ -2362,9 +2364,9 @@
"license": "MIT"
},
"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==",
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
@@ -2383,15 +2385,6 @@
"node": ">= 0.8.0"
}
},
"node_modules/cookie-parser/node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
@@ -2511,6 +2504,21 @@
"url": "https://github.com/sponsors/fb55"
}
},
"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,
"peer": 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/css-what": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
@@ -2536,12 +2544,12 @@
}
},
"node_modules/cssnano": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.0.tgz",
"integrity": "sha512-Pu3rlKkd0ZtlCUzBrKL1Z4YmhKppjC1H9jo7u1o4qaKqyhvixFgu5qLyNIAOjSTg9DjVPtUqdROq2EfpVMEe+w==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.1.tgz",
"integrity": "sha512-fm4D8ti0dQmFPeF8DXSAA//btEmqCOgAc/9Oa3C1LW94h5usNrJEfrON7b4FkPZgnDEn6OUs5NdxiJZmAtGOpQ==",
"license": "MIT",
"dependencies": {
"cssnano-preset-default": "^7.0.8",
"cssnano-preset-default": "^7.0.9",
"lilconfig": "^3.1.3"
},
"engines": {
@@ -2556,9 +2564,9 @@
}
},
"node_modules/cssnano-preset-default": {
"version": "7.0.8",
"resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.8.tgz",
"integrity": "sha512-d+3R2qwrUV3g4LEMOjnndognKirBZISylDZAF/TPeCWVjEwlXS2e4eN4ICkoobRe7pD3H6lltinKVyS1AJhdjQ==",
"version": "7.0.9",
"resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.9.tgz",
"integrity": "sha512-tCD6AAFgYBOVpMBX41KjbvRh9c2uUjLXRyV7KHSIrwHiq5Z9o0TFfUCoM3TwVrRsRteN3sVXGNvjVNxYzkpTsA==",
"license": "MIT",
"dependencies": {
"browserslist": "^4.25.1",
@@ -2566,7 +2574,7 @@
"cssnano-utils": "^5.0.1",
"postcss-calc": "^10.1.1",
"postcss-colormin": "^7.0.4",
"postcss-convert-values": "^7.0.6",
"postcss-convert-values": "^7.0.7",
"postcss-discard-comments": "^7.0.4",
"postcss-discard-duplicates": "^7.0.2",
"postcss-discard-empty": "^7.0.1",
@@ -3012,9 +3020,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.190",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.190.tgz",
"integrity": "sha512-k4McmnB2091YIsdCgkS0fMVMPOJgxl93ltFzaryXqwip1AaxeDqKCGLxkXODDA5Ab/D+tV5EL5+aTx76RvLRxw==",
"version": "1.5.213",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.213.tgz",
"integrity": "sha512-xr9eRzSLNa4neDO0xVFrkXu3vyIzG4Ay08dApecw42Z1NbmCt+keEpXdvlYGVe0wtvY5dhW0Ay0lY0IOfsCg0Q==",
"license": "ISC"
},
"node_modules/emitter-component": {
@@ -3196,20 +3204,20 @@
}
},
"node_modules/eslint": {
"version": "9.31.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz",
"integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==",
"version": "9.34.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.34.0.tgz",
"integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.21.0",
"@eslint/config-helpers": "^0.3.0",
"@eslint/core": "^0.15.0",
"@eslint/config-helpers": "^0.3.1",
"@eslint/core": "^0.15.2",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "9.31.0",
"@eslint/plugin-kit": "^0.3.1",
"@eslint/js": "9.34.0",
"@eslint/plugin-kit": "^0.3.5",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2",
@@ -3290,6 +3298,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/eslint-plugin-mocha/node_modules/type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"dev": true,
"license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/eslint-scope": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
@@ -3503,6 +3524,15 @@
"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",
@@ -3705,9 +3735,9 @@
"license": "MIT"
},
"node_modules/follow-redirects": {
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"version": "1.15.11",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"funding": [
{
"type": "individual",
@@ -3960,6 +3990,18 @@
"node": ">=14"
}
},
"node_modules/gaxios/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==",
"license": "MIT",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/gbxremote": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/gbxremote/-/gbxremote-0.2.1.tgz",
@@ -4037,18 +4079,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/get-stream/node_modules/is-stream": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz",
"integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
@@ -4156,18 +4186,6 @@
"url": "https://github.com/sindresorhus/got?sponsor=1"
}
},
"node_modules/got/node_modules/type-fest": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
"integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
"license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/handlebars": {
"version": "4.7.8",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
@@ -4277,11 +4295,12 @@
"license": "MIT"
},
"node_modules/htmlnano": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.1.2.tgz",
"integrity": "sha512-8Fst+0bhAfU362S6oHVb4wtJj/UYEFr0qiCLAEi8zioqmp1JYBQx5crZAADlFVX0Ly/6s/IQz6G7PL9/hgoJaQ==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.1.3.tgz",
"integrity": "sha512-mzTUHhxdfDw80X36rv5qFvjvor7r0uCwXI5mzo8CW61tImbe5Jpvl3JzPAetVh54wUYVuoa8x3qw8LFn8B3gHQ==",
"license": "MIT",
"dependencies": {
"@types/relateurl": "^0.2.33",
"cosmiconfig": "^9.0.0",
"posthtml": "^0.16.5"
},
@@ -4493,12 +4512,12 @@
"license": "ISC"
},
"node_modules/ioredis": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.1.tgz",
"integrity": "sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==",
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.7.0.tgz",
"integrity": "sha512-NUcA93i1lukyXU+riqEyPtSEkyFq8tX90uL659J+qpCZ3rEdViB/APC58oAhIh3+bJln2hzdlZbBZsGNrlsR8g==",
"license": "MIT",
"dependencies": {
"@ioredis/commands": "^1.1.1",
"@ioredis/commands": "^1.3.0",
"cluster-key-slot": "^1.1.0",
"debug": "^4.3.4",
"denque": "^2.1.0",
@@ -4614,12 +4633,12 @@
"license": "MIT"
},
"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==",
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz",
"integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==",
"license": "MIT",
"engines": {
"node": ">=8"
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -4639,9 +4658,9 @@
}
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==",
"license": "MIT"
},
"node_modules/isexe": {
@@ -4717,9 +4736,9 @@
}
},
"node_modules/istanbul-reports": {
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz",
"integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==",
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
"integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
@@ -5066,14 +5085,6 @@
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
"license": "MIT"
},
"node_modules/lodash.get": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
"integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==",
"deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.",
"dev": true,
"license": "MIT"
},
"node_modules/lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
@@ -5182,9 +5193,9 @@
"license": "Apache-2.0"
},
"node_modules/loupe": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.4.tgz",
"integrity": "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==",
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
"integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==",
"dev": true,
"license": "MIT"
},
@@ -5251,6 +5262,14 @@
"node": ">= 0.4"
}
},
"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,
"peer": true
},
"node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -5919,9 +5938,9 @@
}
},
"node_modules/mongodb": {
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.18.0.tgz",
"integrity": "sha512-fO5ttN9VC8P0F5fqtQmclAkgXZxbIkYRTUi1j8JO6IYwvamkhtYDilJr35jOPELR49zqCJgXZWwCtW7B+TM8vQ==",
"version": "6.19.0",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.19.0.tgz",
"integrity": "sha512-H3GtYujOJdeKIMLKBT9PwlDhGrQfplABNF1G904w6r5ZXKWyv77aB0X9B+rhmaAwjtllHzaEkvi9mkGVZxs2Bw==",
"license": "Apache-2.0",
"dependencies": {
"@mongodb-js/saslprep": "^1.1.9",
@@ -5937,7 +5956,7 @@
"gcp-metadata": "^5.2.0",
"kerberos": "^2.0.1",
"mongodb-client-encryption": ">=6.0.0 <7",
"snappy": "^7.2.2",
"snappy": "^7.3.2",
"socks": "^2.7.1"
},
"peerDependenciesMeta": {
@@ -5975,14 +5994,14 @@
}
},
"node_modules/mongoose": {
"version": "8.16.4",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.16.4.tgz",
"integrity": "sha512-jslgdQ8pY2vcNSKPv3Dbi5ogo/NT8zcvf6kPDyD8Sdsjsa1at3AFAF0F5PT+jySPGSPbvlNaQ49nT9h+Kx2UDA==",
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.18.0.tgz",
"integrity": "sha512-3TixPihQKBdyaYDeJqRjzgb86KbilEH07JmzV8SoSjgoskNTpa6oTBmDxeoF9p8YnWQoz7shnCyPkSV/48y3yw==",
"license": "MIT",
"dependencies": {
"bson": "^6.10.4",
"kareem": "2.6.3",
"mongodb": "~6.17.0",
"mongodb": "~6.18.0",
"mpath": "0.9.0",
"mquery": "5.0.0",
"ms": "2.1.3",
@@ -5997,9 +6016,9 @@
}
},
"node_modules/mongoose/node_modules/mongodb": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.17.0.tgz",
"integrity": "sha512-neerUzg/8U26cgruLysKEjJvoNSXhyID3RvzvdcpsIi2COYM3FS3o9nlH7fxFtefTb942dX3W9i37oPfCVj4wA==",
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.18.0.tgz",
"integrity": "sha512-fO5ttN9VC8P0F5fqtQmclAkgXZxbIkYRTUi1j8JO6IYwvamkhtYDilJr35jOPELR49zqCJgXZWwCtW7B+TM8vQ==",
"license": "Apache-2.0",
"dependencies": {
"@mongodb-js/saslprep": "^1.1.9",
@@ -6187,13 +6206,14 @@
}
},
"node_modules/nise/node_modules/path-to-regexp": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
"integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
"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",
"engines": {
"node": ">=16"
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/node-abort-controller": {
@@ -6751,9 +6771,9 @@
}
},
"node_modules/postcss-convert-values": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.6.tgz",
"integrity": "sha512-MD/eb39Mr60hvgrqpXsgbiqluawYg/8K4nKsqRsuDX9f+xN1j6awZCUv/5tLH8ak3vYp/EMXwdcnXvfZYiejCQ==",
"version": "7.0.7",
"resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.7.tgz",
"integrity": "sha512-HR9DZLN04Xbe6xugRH6lS4ZQH2zm/bFh/ZyRkpedZozhvh+awAfbA0P36InO4fZfDhvYfNJeNvlTf1sjwGbw/A==",
"license": "MIT",
"dependencies": {
"browserslist": "^4.25.1",
@@ -7351,9 +7371,9 @@
"license": "MIT"
},
"node_modules/protobufjs": {
"version": "7.5.3",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.3.tgz",
"integrity": "sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw==",
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz",
"integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==",
"hasInstallScript": true,
"license": "BSD-3-Clause",
"dependencies": {
@@ -7995,9 +8015,9 @@
"license": "ISC"
},
"node_modules/ssh2": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.16.0.tgz",
"integrity": "sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg==",
"version": "1.17.0",
"resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.17.0.tgz",
"integrity": "sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==",
"hasInstallScript": true,
"dependencies": {
"asn1": "^0.2.6",
@@ -8008,7 +8028,7 @@
},
"optionalDependencies": {
"cpu-features": "~0.0.10",
"nan": "^2.20.0"
"nan": "^2.23.0"
}
},
"node_modules/ssl-checker": {
@@ -8077,6 +8097,12 @@
"readable-stream": "^2.1.0"
}
},
"node_modules/string-to-stream/node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"license": "MIT"
},
"node_modules/string-to-stream/node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
@@ -8245,86 +8271,15 @@
}
},
"node_modules/super-simple-scheduler": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.3.0.tgz",
"integrity": "sha512-iEGZ+a9Xv7pIaA+XhVuHSGaasQg2T6/afTRlQhosQiiU/7ykpZBsiZB/L2Hqfem5YUrbr3Q8tzrjjXCXKMg81A==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.4.1.tgz",
"integrity": "sha512-jKVwxdRBXOHrUstA2pvO4jBZIA3CUinKiQ6k5C2Wn5tscoV3srppPxW0zG3VNKTCaGxo3yPn6Zaf3IDVBcAwRA==",
"license": "MIT",
"dependencies": {
"human-interval": "2.0.1",
"ioredis": "5.6.1",
"mongoose": "8.16.1",
"uuid": "11.1.0",
"winston": "3.17.0"
}
},
"node_modules/super-simple-scheduler/node_modules/mongodb": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.17.0.tgz",
"integrity": "sha512-neerUzg/8U26cgruLysKEjJvoNSXhyID3RvzvdcpsIi2COYM3FS3o9nlH7fxFtefTb942dX3W9i37oPfCVj4wA==",
"license": "Apache-2.0",
"dependencies": {
"@mongodb-js/saslprep": "^1.1.9",
"bson": "^6.10.4",
"mongodb-connection-string-url": "^3.0.0"
},
"engines": {
"node": ">=16.20.1"
},
"peerDependencies": {
"@aws-sdk/credential-providers": "^3.188.0",
"@mongodb-js/zstd": "^1.1.0 || ^2.0.0",
"gcp-metadata": "^5.2.0",
"kerberos": "^2.0.1",
"mongodb-client-encryption": ">=6.0.0 <7",
"snappy": "^7.2.2",
"socks": "^2.7.1"
},
"peerDependenciesMeta": {
"@aws-sdk/credential-providers": {
"optional": true
},
"@mongodb-js/zstd": {
"optional": true
},
"gcp-metadata": {
"optional": true
},
"kerberos": {
"optional": true
},
"mongodb-client-encryption": {
"optional": true
},
"snappy": {
"optional": true
},
"socks": {
"optional": true
}
}
},
"node_modules/super-simple-scheduler/node_modules/mongoose": {
"version": "8.16.1",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.16.1.tgz",
"integrity": "sha512-Q+0TC+KLdY4SYE+u9gk9pdW1tWu/pl0jusyEkMGTgBoAbvwQdfy4f9IM8dmvCwb/blSfp7IfLkob7v76x6ZGpQ==",
"license": "MIT",
"dependencies": {
"bson": "^6.10.4",
"kareem": "2.6.3",
"mongodb": "~6.17.0",
"mpath": "0.9.0",
"mquery": "5.0.0",
"ms": "2.1.3",
"sift": "17.1.3"
},
"engines": {
"node": ">=16.20.1"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mongoose"
}
},
"node_modules/super-simple-scheduler/node_modules/uuid": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
@@ -8351,10 +8306,48 @@
"node": ">=8"
}
},
"node_modules/svgo": {
"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,
"peer": true,
"dependencies": {
"@trysound/sax": "0.2.0",
"commander": "^7.2.0",
"css-select": "^5.1.0",
"css-tree": "^2.3.1",
"css-what": "^6.1.0",
"csso": "^5.0.5",
"picocolors": "^1.0.0"
},
"bin": {
"svgo": "bin/svgo"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/svgo"
}
},
"node_modules/svgo/node_modules/commander": {
"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,
"peer": true,
"engines": {
"node": ">= 10"
}
},
"node_modules/swagger-ui-dist": {
"version": "5.27.0",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.27.0.tgz",
"integrity": "sha512-tS6LRyBhY6yAqxrfsA9IYpGWPUJOri6sclySa7TdC7XQfGLvTwDY531KLgfQwHEtQsn+sT4JlUspbeQDBVGWig==",
"version": "5.28.1",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.28.1.tgz",
"integrity": "sha512-IvPrtNi8MvjiuDgoSmPYgg27Lvu38fnLD1OSd8Y103xXsPAqezVNnNeHnVCZ/d+CMXJblflGaIyHxAYIF3O71w==",
"license": "Apache-2.0",
"dependencies": {
"@scarf/scarf": "=1.4.0"
@@ -8588,13 +8581,12 @@
}
},
"node_modules/type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"dev": true,
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
"integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
"license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=10"
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -8640,18 +8632,18 @@
"license": "MIT"
},
"node_modules/undici": {
"version": "7.12.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.12.0.tgz",
"integrity": "sha512-GrKEsc3ughskmGA9jevVlIOPMiiAHJ4OFUtaAH+NhfTUSiZ1wMPIQqQvAJUrJspFXJt3EBWgpAeoHEDVT1IBug==",
"version": "7.15.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.15.0.tgz",
"integrity": "sha512-7oZJCPvvMvTd0OlqWsIxTuItTpJBpU1tcbVl24FMn3xt3+VSunwUasmfPJRE57oNO1KsZ4PgA1xTdAX4hq8NyQ==",
"license": "MIT",
"engines": {
"node": ">=20.18.1"
}
},
"node_modules/undici-types": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
"integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
"version": "7.10.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
"integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
"license": "MIT"
},
"node_modules/unfetch": {
@@ -9031,6 +9023,18 @@
"node": ">= 12.0.0"
}
},
"node_modules/winston/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==",
"license": "MIT",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/word-wrap": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+1 -1
View File
@@ -46,7 +46,7 @@
"ping": "0.4.4",
"sharp": "0.33.5",
"ssl-checker": "2.0.10",
"super-simple-scheduler": "1.3.0",
"super-simple-scheduler": "1.4.1",
"swagger-ui-express": "5.0.1",
"winston": "^3.13.0"
},
@@ -95,7 +95,7 @@ class StatusPageController extends BaseController {
deleteStatusPage = this.asyncHandler(
async (req, res) => {
await this.db.deleteStatusPage(req.params.url);
await this.db.statusPageModule.deleteStatusPage(req.params.url);
return res.success({
msg: this.stringService.statusPageDelete,
});
+1 -1
View File
@@ -72,7 +72,7 @@ const CheckSchema = new mongoose.Schema(
},
type: {
type: String,
enum: ["http", "hardware", "pagespeed", "distributed"],
enum: ["http", "ping", "pagespeed", "hardware", "docker", "port", "game"],
required: true,
index: true,
},
+1 -1
View File
@@ -38,7 +38,7 @@ const MonitorSchema = mongoose.Schema(
},
statusWindowThreshold: {
type: Number,
default: 0.6,
default: 60,
},
type: {
type: String,
+3 -1
View File
@@ -1,6 +1,6 @@
import mongoose from "mongoose";
import AppSettings from "../models/AppSettings.js";
import { runMigrations } from "./migration/index.js";
class MongoDB {
static SERVICE_NAME = "MongoDB";
@@ -65,6 +65,8 @@ class MongoDB {
service: this.SERVICE_NAME,
method: "connect",
});
await runMigrations();
} catch (error) {
this.logger.error({
message: error.message,
@@ -0,0 +1,18 @@
import Monitor from "../../models/Monitor.js";
async function migrateStatusWindowThreshold() {
try {
const monitors = await Monitor.find({ statusWindowThreshold: { $lt: 1 } });
for (const monitor of monitors) {
monitor.statusWindowThreshold = monitor.statusWindowThreshold * 100;
await monitor.save();
console.log(`Migrated monitor ${monitor._id}: statusWindowThreshold set to ${monitor.statusWindowThreshold}`);
}
console.log("StatusWindowThreshold migration complete.");
return true;
} catch (err) {
console.error("Migration error:", err);
return false;
}
}
export { migrateStatusWindowThreshold };
+7
View File
@@ -0,0 +1,7 @@
import { migrateStatusWindowThreshold } from "./0001_migrateStatusWindowThreshold.js";
const runMigrations = async () => {
await migrateStatusWindowThreshold();
};
export { runMigrations };
@@ -162,6 +162,14 @@ class StatusPageModule {
as: "monitors.checks",
},
},
{
$lookup: {
from: "monitorstats",
localField: "monitors._id",
foreignField: "monitorId",
as: "monitors.stats",
},
},
{
$addFields: {
"monitors.orderIndex": {
@@ -181,6 +189,9 @@ class StatusPageModule {
},
},
},
"monitors.uptimePercentage": {
$arrayElemAt: ["$monitors.stats.uptimePercentage", 0],
},
},
},
{ $match: { "monitors.orderIndex": { $ne: -1 } } },
+16 -1
View File
@@ -149,10 +149,12 @@
"statusPageByTeamId": "Got status pages by team id successfully",
"httpNetworkError": "Network error",
"httpNotJson": "Response data is not json",
"httpExpectedValueFail": "Expected value did not match",
"httpJsonPathError": "Failed to parse json data",
"httpEmptyResult": "Result is empty",
"httpMatchSuccess": "Response data match successfully",
"httpMatchFail": "Failed to match response data",
"httpJsonPathFail": "JSONPath did not match expected value",
"webhookSendSuccess": "Webhook notification sent successfully",
"telegramRequiresBotTokenAndChatId": "Telegram notifications require both botToken and chatId",
"webhookUrlRequired": "Webhook URL is required",
@@ -162,5 +164,18 @@
"monitorDownAlert": "Downtime Alert: One of your monitors went offline.\n📌 Monitor: {monitorName}\n📅 Time: {time}\n⚠️ Status: DOWN\n📟 Status Code: {code}\n\u200B\n",
"sendTestEmail": "Test email sent successfully",
"errorForValidEmailAddress": "A valid recipient email address is required.",
"testEmailSubject": "Test E-mail from Checkmate"
"testEmailSubject": "Test E-mail from Checkmate",
"discordNotification": {
"monitor": "Monitor",
"status": "Status",
"statusCode": "Status Code",
"time": "Time",
"up": "UP",
"down": "DOWN",
"checkmate": "Checkmate",
"url": "URL",
"unknown": "Unknown",
"uptimeAlert": "Uptime Alert: One of your monitors is back online",
"downtimeAlert": "Downtime Alert: One of your monitors went offline"
}
}
@@ -88,7 +88,7 @@ class SuperSimpleQueue {
};
updateJob = async (monitor) => {
this.scheduler.updateJob(monitor._id.toString(), monitor.interval);
this.scheduler.updateJob(monitor._id.toString(), { repeat: monitor.interval, data: monitor.toObject() });
};
shutdown = async () => {
@@ -141,6 +141,7 @@ class SuperSimpleQueue {
monitorId: job.id,
monitorUrl: job?.data?.url || null,
monitorType: job?.data?.type || null,
monitorInterval: job?.data?.interval || null,
active: job.active,
lockedAt: job.lockedAt,
runCount: job.runCount || 0,
@@ -28,11 +28,12 @@ class SuperSimpleQueueHelper {
return async (monitor) => {
try {
const monitorId = monitor._id;
const teamId = monitor.teamId;
if (!monitorId) {
throw new Error("No monitor id");
}
const maintenanceWindowActive = await this.isInMaintenanceWindow(monitorId);
const maintenanceWindowActive = await this.isInMaintenanceWindow(monitorId, teamId);
if (maintenanceWindowActive) {
this.logger.info({
message: `Monitor ${monitorId} is in maintenance window`,
@@ -77,8 +78,11 @@ class SuperSimpleQueueHelper {
};
};
async isInMaintenanceWindow(monitorId) {
const maintenanceWindows = await this.db.maintenanceWindowModule.getMaintenanceWindowsByMonitorId(monitorId);
async isInMaintenanceWindow(monitorId, teamId) {
const maintenanceWindows = await this.db.maintenanceWindowModule.getMaintenanceWindowsByMonitorId({
monitorId: monitorId.toString(),
teamId: teamId.toString(),
});
// Check for active maintenance window:
const maintenanceWindowIsActive = maintenanceWindows.reduce((acc, window) => {
if (window.active) {
@@ -73,6 +73,9 @@ class NetworkService {
const { response, responseTime, error } = await this.timeRequest(() => this.ping.promise.probe(monitor.url));
if (!response) {
if (error) {
throw error;
}
throw new Error("Ping failed - no result returned");
}
@@ -102,7 +105,7 @@ class NetworkService {
}
async requestHttp(monitor) {
const { url, secret, _id, name, teamId, type, ignoreTlsErrors, jsonPath, matchMethod, expectedValue } = monitor;
const { url, secret, _id, teamId, type, ignoreTlsErrors, jsonPath, matchMethod, expectedValue } = monitor;
const httpResponse = {
monitorId: _id,
teamId: teamId,
@@ -147,29 +150,65 @@ class NetworkService {
httpResponse.payload = payload;
httpResponse.timings = response.timings || {};
if (!expectedValue) {
if (!expectedValue && !jsonPath) {
return httpResponse;
}
this.logger.info({
service: this.SERVICE_NAME,
method: "requestHttp",
message: `Job: [${name}](${_id}) match result with expected value`,
details: { expectedValue, payload, jsonPath, matchMethod },
});
if (expectedValue && !jsonPath) {
let ok = false;
if (matchMethod === "equal") ok = payload === expectedValue;
if (matchMethod === "include" && typeof payload === "string") ok = payload.includes(expectedValue);
if (matchMethod === "regex" && typeof payload === "string") ok = new RegExp(expectedValue).test(payload);
if (ok === true) {
return httpResponse;
} else {
httpResponse.code = 500;
httpResponse.status = false;
httpResponse.message = this.stringService.httpExpectedValueFail;
return httpResponse;
}
}
if (jsonPath) {
const contentType = response.headers["content-type"];
const isJson = contentType?.includes("application/json");
if (!isJson) {
httpResponse.status = false;
httpResponse.message = this.stringService.httpNotJson;
return httpResponse;
}
try {
this.jmespath.search(payload, jsonPath);
const extracted = this.jmespath.search(payload, jsonPath);
if (expectedValue) {
let ok = false;
if (matchMethod === "equal") ok = extracted === expectedValue;
if (matchMethod === "include" && typeof extracted === "string") ok = extracted.includes(expectedValue);
if (matchMethod === "regex" && typeof extracted === "string") ok = new RegExp(expectedValue).test(extracted);
if (ok) {
httpResponse.extracted = extracted;
return httpResponse;
} else {
httpResponse.status = false;
httpResponse.code = 500;
httpResponse.message = this.stringService.httpJsonPathFail;
httpResponse.extracted = extracted;
return httpResponse;
}
} else {
const isFalsey = extracted === false || extracted === "false" || extracted === undefined || extracted === null;
if (!isFalsey) {
httpResponse.extracted = extracted;
return httpResponse;
} else {
httpResponse.status = false;
httpResponse.code = 500;
httpResponse.message = this.stringService.httpJsonPathFail;
httpResponse.extracted = extracted;
return httpResponse;
}
}
} catch {
httpResponse.status = false;
httpResponse.message = this.stringService.httpJsonPathError;
@@ -243,24 +282,76 @@ class NetworkService {
handleError: true, // Enable error handling
});
const containers = await docker.listContainers({ all: true });
const containerExists = containers.some((c) => c.Id.startsWith(monitor.url));
if (!containerExists) {
throw new Error(this.stringService.dockerNotFound);
}
const container = docker.getContainer(monitor.url);
const { response, responseTime, error } = await this.timeRequest(() => container.inspect());
const dockerResponse = {
monitorId: monitor._id,
type: monitor.type,
responseTime,
status: response?.State?.Status === "running" ? true : false,
code: 200,
message: "Docker container status fetched successfully",
};
const containers = await docker.listContainers({ all: true });
// Normalize input: strip leading slashes and convert to lowercase for comparison
const normalizedInput = monitor.url.replace(/^\/+/, "").toLowerCase();
// Priority-based matching to avoid ambiguity:
// 1. Exact full ID match (64-char)
let exactIdMatch = containers.find((c) => c.Id.toLowerCase() === normalizedInput);
// 2. Exact container name match (case-insensitive)
let exactNameMatch = containers.find((c) =>
c.Names.some((name) => {
const cleanName = name.replace(/^\/+/, "").toLowerCase();
return cleanName === normalizedInput;
})
);
// 3. Partial ID match (fallback for backwards compatibility)
let partialIdMatch = containers.find((c) => c.Id.toLowerCase().startsWith(normalizedInput));
// Select container based on priority
let targetContainer = exactIdMatch || exactNameMatch || partialIdMatch;
// Return negative response if no container
if (!targetContainer) {
this.logger.warn({
message: `No container found for "${monitor.url}".`,
service: this.SERVICE_NAME,
method: "requestDocker",
details: { url: monitor.url },
});
dockerResponse.code = 404;
dockerResponse.status = false;
dockerResponse.message = this.stringService.dockerNotFound;
return dockerResponse;
}
// Return negative response if ambiguous matches exist
const matchTypes = [];
if (exactIdMatch) matchTypes.push("exact ID");
if (exactNameMatch) matchTypes.push("exact name");
if (partialIdMatch && !exactIdMatch) matchTypes.push("partial ID");
if (matchTypes.length > 1) {
this.logger.warn({
message: `Ambiguous container match for "${monitor.url}". Matched by: ${matchTypes.join(", ")}. Using ${exactIdMatch ? "exact ID" : exactNameMatch ? "exact name" : "partial ID"} match.`,
service: this.SERVICE_NAME,
method: "requestDocker",
details: { url: monitor.url },
});
dockerResponse.status = 404;
dockerResponse.status = false;
dockerResponse.message = `Ambiguous container match for "${monitor.url}". Matched by: ${matchTypes.join(", ")}. Using ${exactIdMatch ? "exact ID" : exactNameMatch ? "exact name" : "partial ID"} match.`;
return dockerResponse;
}
const container = docker.getContainer(targetContainer.Id);
const { response, responseTime, error } = await this.timeRequest(() => container.inspect());
dockerResponse.responseTime = responseTime;
dockerResponse.status = response?.State?.Status === "running" ? true : false;
dockerResponse.code = 200;
dockerResponse.message = "Docker container status fetched successfully";
if (error) {
dockerResponse.status = false;
dockerResponse.code = error.statusCode || this.NETWORK_ERROR;
@@ -16,7 +16,7 @@ class NotificationService {
return NotificationService.SERVICE_NAME;
}
sendNotification = async ({ notification, subject, content, html }) => {
sendNotification = async ({ notification, subject, content, html, discordContent = null, webhookBody = null }) => {
const { type, address } = notification;
if (type === "email") {
@@ -28,9 +28,11 @@ class NotificationService {
// Create a body for webhooks
let body = { text: content };
if (type === "discord") {
body = { content };
body = !discordContent ? { content } : discordContent;
}
if (type === "webhook") {
body = !webhookBody ? { content } : webhookBody;
}
if (type === "slack" || type === "discord" || type === "webhook") {
const response = await this.networkService.requestWebhook(type, address, body);
return response.status;
@@ -55,7 +57,6 @@ class NotificationService {
const notificationIDs = networkResponse.monitor?.notifications ?? [];
if (notificationIDs.length === 0) return false;
if (networkResponse.monitor.type === "hardware") {
const thresholds = networkResponse?.monitor?.thresholds;
@@ -63,30 +64,29 @@ class NotificationService {
const metrics = networkResponse?.payload?.data ?? null;
if (metrics === null) return false; // No metrics, we're done
const alerts = await this.notificationUtils.buildHardwareAlerts(networkResponse);
const [alerts, discordContent] = await this.notificationUtils.buildHardwareAlerts(networkResponse);
if (alerts.length === 0) return false;
const { subject, html } = await this.notificationUtils.buildHardwareEmail(networkResponse, alerts);
const content = await this.notificationUtils.buildHardwareNotificationMessage(alerts);
const success = await this.notifyAll({ notificationIDs, subject, html, content });
const content = await this.notificationUtils.buildHardwareNotificationMessage(alerts, monitor);
const webhookBody = await this.notificationUtils.buildHardwareWebhookBody(alerts, monitor);
const success = await this.notifyAll({ notificationIDs, subject, html, content, discordContent, webhookBody });
return success;
}
// Status monitors
const { subject, html } = await this.notificationUtils.buildStatusEmail(networkResponse);
const content = await this.notificationUtils.buildWebhookMessage(networkResponse);
const success = this.notifyAll({ notificationIDs, subject, html, content });
const [content, discordContent] = await this.notificationUtils.buildWebhookMessage(networkResponse);
const success = this.notifyAll({ notificationIDs, subject, html, content, discordContent });
return success;
}
async notifyAll({ notificationIDs, subject, html, content }) {
async notifyAll({ notificationIDs, subject, html, content, discordContent = null, webhookBody = null }) {
const notifications = await this.db.notificationModule.getNotificationsByIds(notificationIDs);
// Map each notification to a test promise
const promises = notifications.map(async (notification) => {
try {
await this.sendNotification({ notification, subject, content, html });
await this.sendNotification({ notification, subject, content, html, discordContent, webhookBody });
return true;
} catch (err) {
return false;
@@ -70,7 +70,25 @@ class NotificationUtils {
.replace("{time}", formattedTime)
.replace("{code}", code || "Unknown");
}
return messageText;
const dn = this.stringService.discordNotification;
let discordMessageText = {
embeds: [
{
title: status ? dn.uptimeAlert : dn.downtimeAlert,
color: status ? 5763719 : 15548997,
fields: [
{ name: dn.monitor, value: monitor?.name ?? dn.unknown, inline: true },
{ name: dn.status, value: status ? dn.up : dn.down, inline: true },
{ name: dn.statusCode, value: String(code ?? dn.unknown), inline: true },
{ name: dn.time, value: formattedTime, inline: true },
{ name: dn.url, value: monitor?.url ?? dn.unknown, inline: false },
],
footer: { text: dn.checkmate },
},
],
};
return [messageText, discordMessageText];
};
buildHardwareAlerts = async (networkResponse) => {
@@ -88,6 +106,43 @@ class NotificationUtils {
};
const alertsToSend = [];
const discordEmbeds = [];
const monitorInfoFields = [
{ name: "Monitor", value: monitor.name, inline: true },
{ name: "URL", value: monitor.url, inline: false },
];
const formatDiscordAlert = {
cpu: () => ({
title: "CPU alert",
description: `Your current CPU usage (${(cpuUsage * 100).toFixed(0)}%) is above your threshold (${(cpuThreshold * 100).toFixed(0)}%)`,
color: 15548997,
fields: monitorInfoFields,
footer: { text: "Checkmate" },
}),
memory: () => ({
title: "Memory alert",
description: `Your current memory usage (${(memoryUsage * 100).toFixed(0)}%) is above your threshold (${(memoryThreshold * 100).toFixed(0)}%)`,
color: 15548997,
fields: monitorInfoFields,
footer: { text: "Checkmate" },
}),
disk: () => ({
title: "Disk alert",
description: `Your current disk usage is above your threshold (${(diskThreshold * 100).toFixed(0)}%)`,
color: 15548997,
footer: { text: "Checkmate" },
fields: [
...monitorInfoFields,
...(Array.isArray(disk) ? disk : []).map((d, idx) => ({
name: `Disk ${idx}`,
value: `${(d?.usage_percent * 100).toFixed(0)}%`,
inline: true,
})),
],
}),
};
const alertTypes = ["cpu", "memory", "disk"];
for (const type of alertTypes) {
// Iterate over each alert type to see if any need to be decremented
@@ -108,11 +163,13 @@ class NotificationUtils {
.join(", ")} is above your threshold (${(diskThreshold * 100).toFixed(0)}%)`,
};
alertsToSend.push(formatAlert[type]());
discordEmbeds.push(formatDiscordAlert[type]());
}
}
}
await monitor.save();
return alertsToSend;
const discordPayload = discordEmbeds.length ? { embeds: discordEmbeds } : null;
return [alertsToSend, discordPayload];
};
buildHardwareEmail = async (networkResponse, alerts) => {
@@ -124,8 +181,15 @@ class NotificationUtils {
return { subject, html };
};
buildHardwareNotificationMessage = (alerts) => {
return alerts.map((alert) => alert).join("\n");
buildHardwareNotificationMessage = (alerts, monitor) => {
const alertsHeader = [`Monitor: ${monitor.name}`, `URL: ${monitor.url}`];
const alertText = alerts.length > 0 ? [...alertsHeader, ...alerts] : [];
return alertText.map((alert) => alert).join("\n");
};
buildHardwareWebhookBody = (alerts, monitor) => {
const content = alerts.map((alert) => alert).join("\n");
const body = { text: content, name: monitor.name, url: monitor.url };
return body;
};
}
@@ -158,7 +158,7 @@ class StatusService {
statusChanged = true;
}
// If the failure rate is below the threshold and the monitor is down, recover:
else if (failureRate <= monitor.statusWindowThreshold && monitor.status === false) {
else if (failureRate < monitor.statusWindowThreshold && monitor.status === false) {
newStatus = true;
statusChanged = true;
}
@@ -216,6 +216,10 @@ class StringService {
return this.translationService.getTranslation("monitorDownAlert");
}
get discordNotification() {
return this.translationService.getTranslation("discordNotification");
}
getWebhookUnsupportedPlatform(platform) {
return this.translationService.getTranslation("webhookUnsupportedPlatform").replace("{platform}", platform);
}