Compare commits
486
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1244ff6d93 | ||
|
|
53f420cf56 | ||
|
|
589825c1c5 | ||
|
|
6efc05879a | ||
|
|
c0ea804c98 | ||
|
|
20dccb140e | ||
|
|
8dfedb8a52 | ||
|
|
c21cf29db6 | ||
|
|
d48cdaca9c | ||
|
|
36a53ee80b | ||
|
|
3d0da70ce6 | ||
|
|
4400ca703d | ||
|
|
c1d0cb568d | ||
|
|
81e73e5adf | ||
|
|
7a84d06a79 | ||
|
|
63d2bb97ee | ||
|
|
0af7cfe714 | ||
|
|
d190f5ab02 | ||
|
|
8cfe1d3c1a | ||
|
|
1bf4c75418 | ||
|
|
7771a0ce35 | ||
|
|
fcd9e78367 | ||
|
|
6f9fefddd2 | ||
|
|
5731283c46 | ||
|
|
420ff292d9 | ||
|
|
290ee7d2df | ||
|
|
50c644231e | ||
|
|
5729b0a263 | ||
|
|
4f3f5408ef | ||
|
|
de7038d6c3 | ||
|
|
4119189c71 | ||
|
|
fd66affea8 | ||
|
|
839dc201df | ||
|
|
bbb0716251 | ||
|
|
bbdfc00eb9 | ||
|
|
ba498534e6 | ||
|
|
f51bf36ddd | ||
|
|
716479c5bb | ||
|
|
f9c85e2d76 | ||
|
|
e26f3facf1 | ||
|
|
fd5f493345 | ||
|
|
e5bb736530 | ||
|
|
d71d16ecd0 | ||
|
|
389716fa77 | ||
|
|
1bcb2ddf91 | ||
|
|
b61077bd3f | ||
|
|
1cffd51bd3 | ||
|
|
451e352458 | ||
|
|
a07e41d8f1 | ||
|
|
90e9b41973 | ||
|
|
1819df5117 | ||
|
|
6380dcc6ba | ||
|
|
6cb45ea6de | ||
|
|
67f74dd7de | ||
|
|
8f58ceafab | ||
|
|
c7aa45cd77 | ||
|
|
d0727f91dd | ||
|
|
967b2b200b | ||
|
|
d71c7c6ee1 | ||
|
|
6daa79f0ed | ||
|
|
93a5eff562 | ||
|
|
11c95f885d | ||
|
|
174665e70a | ||
|
|
64de01437e | ||
|
|
a64885e609 | ||
|
|
e354fa7ebf | ||
|
|
3208cc9e2c | ||
|
|
79dfa886f1 | ||
|
|
8af8efbe3b | ||
|
|
71bd1fa28f | ||
|
|
a2569c50c7 | ||
|
|
f7ae11997d | ||
|
|
0b7a41dc78 | ||
|
|
d42d5f2325 | ||
|
|
369d16c7c3 | ||
|
|
6e8172c126 | ||
|
|
794018a6ab | ||
|
|
8d6bc7bc0b | ||
|
|
30631d6ff8 | ||
|
|
56c5ad419a | ||
|
|
b852293447 | ||
|
|
d877ecf061 | ||
|
|
73e90bfab6 | ||
|
|
a55163de56 | ||
|
|
f5c6abad1b | ||
|
|
3dbbe035a8 | ||
|
|
9904626526 | ||
|
|
61f3aa1d1c | ||
|
|
a0a8ceb0db | ||
|
|
c2dece862c | ||
|
|
69018a4944 | ||
|
|
363d89e4f4 | ||
|
|
f8bb01190e | ||
|
|
00cf0b6521 | ||
|
|
a11d551a99 | ||
|
|
b35be1ecfc | ||
|
|
9d64de57b3 | ||
|
|
de6c4d9c3e | ||
|
|
e0e79fb0da | ||
|
|
3339b6b67f | ||
|
|
b511cde849 | ||
|
|
ce322a8e28 | ||
|
|
d25ca473de | ||
|
|
5292ca8988 | ||
|
|
c98cba2eef | ||
|
|
6b89db131d | ||
|
|
995ae5252d | ||
|
|
e6c268f14e | ||
|
|
5aef405414 | ||
|
|
a5112a7955 | ||
|
|
5404b65ce2 | ||
|
|
34dfe33c29 | ||
|
|
1e24c8841e | ||
|
|
0c04c06781 | ||
|
|
7441169a0e | ||
|
|
3520e5adf4 | ||
|
|
227e0128f9 | ||
|
|
2359255ef7 | ||
|
|
b074f2401f | ||
|
|
eee3caf021 | ||
|
|
f9848e4e1e | ||
|
|
8bd608bee0 | ||
|
|
ea589d6be4 | ||
|
|
5a0078cf92 | ||
|
|
f4a5bd65ad | ||
|
|
4766594413 | ||
|
|
9962866ba5 | ||
|
|
82cc289f53 | ||
|
|
117c22fae4 | ||
|
|
e0cabdb4fc | ||
|
|
155a2577d0 | ||
|
|
d627a25b40 | ||
|
|
b8a5af9c10 | ||
|
|
5b43d9d173 | ||
|
|
801b716a03 | ||
|
|
fdc3983328 | ||
|
|
0cf85f8b3b | ||
|
|
0ea4a5024a | ||
|
|
09513696ee | ||
|
|
b0da1f4259 | ||
|
|
5b96075c03 | ||
|
|
36e02e0a9a | ||
|
|
9fa0113f2b | ||
|
|
371e8cc154 | ||
|
|
7b4286646b | ||
|
|
2a8d2cb06a | ||
|
|
147eff3ede | ||
|
|
81c7c992e2 | ||
|
|
35488fed3e | ||
|
|
760abe69ed | ||
|
|
d242382abf | ||
|
|
ef61a63da0 | ||
|
|
3fa370765e | ||
|
|
3b5e793f8f | ||
|
|
b34dbec3cf | ||
|
|
90eee474d2 | ||
|
|
63416ceb4b | ||
|
|
eb8c9bea76 | ||
|
|
63b4779961 | ||
|
|
d2040495d6 | ||
|
|
f436e15569 | ||
|
|
952c70fca4 | ||
|
|
4957209307 | ||
|
|
cc9850c975 | ||
|
|
c6ac48b4ff | ||
|
|
82584d85c7 | ||
|
|
ffd67150eb | ||
|
|
44079f2eb8 | ||
|
|
f03ded4109 | ||
|
|
fa520e5f88 | ||
|
|
e9fbb472f4 | ||
|
|
de236f03eb | ||
|
|
4f043fe1d4 | ||
|
|
102bd0a5f8 | ||
|
|
118372630e | ||
|
|
27dea6a08e | ||
|
|
dd259fea89 | ||
|
|
9af25a19e5 | ||
|
|
6101fe1bfa | ||
|
|
795146c79b | ||
|
|
a69091bb28 | ||
|
|
6160806963 | ||
|
|
c1b2bda623 | ||
|
|
c0e4893f96 | ||
|
|
476ff39a17 | ||
|
|
f467ef1411 | ||
|
|
36b02e5e66 | ||
|
|
f6fd8729c3 | ||
|
|
cdfc87da09 | ||
|
|
427e5a18ea | ||
|
|
f696b4ef9b | ||
|
|
69d050ff61 | ||
|
|
7afdcb8ebc | ||
|
|
3cf7243c93 | ||
|
|
4ed0033a41 | ||
|
|
bafd82a691 | ||
|
|
adb2a383cf | ||
|
|
e91a54b75c | ||
|
|
33dfedb4d3 | ||
|
|
dde54a4e45 | ||
|
|
5f17520ba0 | ||
|
|
bebb5d6942 | ||
|
|
7c9410f9d7 | ||
|
|
348ca0dd02 | ||
|
|
1d36f40caf | ||
|
|
9006707e57 | ||
|
|
be82a7ee2d | ||
|
|
fd33b903ed | ||
|
|
ab25531758 | ||
|
|
f7f764fd68 | ||
|
|
3b0326457f | ||
|
|
7d6bc4b2dc | ||
|
|
c2487f0134 | ||
|
|
a86588f080 | ||
|
|
4e9839e2e5 | ||
|
|
487f6b222d | ||
|
|
391f42ee53 | ||
|
|
cff5508614 | ||
|
|
a0ebf7d224 | ||
|
|
ec9c79fc94 | ||
|
|
3e8d4cb49b | ||
|
|
288ac08cd6 | ||
|
|
ba35230919 | ||
|
|
048e53ea15 | ||
|
|
e498bd60b1 | ||
|
|
a4cf53ee15 | ||
|
|
3a77c6d7fa | ||
|
|
b47f3710fe | ||
|
|
9c92542653 | ||
|
|
173a4e17d2 | ||
|
|
70b197675e | ||
|
|
11dcccc085 | ||
|
|
e3e08da259 | ||
|
|
6447c315ce | ||
|
|
cb5b8e0673 | ||
|
|
f1a6afb860 | ||
|
|
5c52a208b9 | ||
|
|
4fdc0bde5c | ||
|
|
3d4ebe9588 | ||
|
|
88bb8ef2d3 | ||
|
|
64dc381e0f | ||
|
|
af82dec2ad | ||
|
|
62ea7b0e5b | ||
|
|
8747c38f40 | ||
|
|
d633fc5ccb | ||
|
|
0d7a35367f | ||
|
|
af141ec772 | ||
|
|
5784fec3d1 | ||
|
|
7436c2b5df | ||
|
|
2055943876 | ||
|
|
95cd33ac06 | ||
|
|
2a591ee503 | ||
|
|
f5aa24d77e | ||
|
|
7874439e4c | ||
|
|
7140d436a5 | ||
|
|
ba0c89fc6c | ||
|
|
d91e87f484 | ||
|
|
bf15018173 | ||
|
|
b5caabed20 | ||
|
|
8814596d6e | ||
|
|
c60d07c3a0 | ||
|
|
54ecf02d9a | ||
|
|
1ab31d19f3 | ||
|
|
2f2ce34373 | ||
|
|
0f764ca084 | ||
|
|
8b2ee8d937 | ||
|
|
ed80837810 | ||
|
|
d82877b28d | ||
|
|
cf67c82f5e | ||
|
|
5a7b51bfec | ||
|
|
206dcae196 | ||
|
|
56bfbe2c77 | ||
|
|
8aac3086b8 | ||
|
|
9255500bfc | ||
|
|
e99b87fc1e | ||
|
|
ec953ff262 | ||
|
|
68559741ba | ||
|
|
74a7f0a701 | ||
|
|
75bcf9df58 | ||
|
|
6b5fd8d17f | ||
|
|
a1069f487b | ||
|
|
86a2171988 | ||
|
|
420164d616 | ||
|
|
f0a6fc4fdf | ||
|
|
e306499bce | ||
|
|
1472a09718 | ||
|
|
1c01dcaf31 | ||
|
|
e7e4ce7f57 | ||
|
|
93c24d2f5c | ||
|
|
cdbaad5bd9 | ||
|
|
b1b27be731 | ||
|
|
01f005ca41 | ||
|
|
1326a6222b | ||
|
|
aea0b34533 | ||
|
|
d4af86f26c | ||
|
|
940247b7bf | ||
|
|
2b0c523047 | ||
|
|
4ad09fad4f | ||
|
|
665eb9a67f | ||
|
|
d1faeab042 | ||
|
|
76fa99f7bd | ||
|
|
77764c51ee | ||
|
|
8a986d2e58 | ||
|
|
e57b161be9 | ||
|
|
8de06df8c4 | ||
|
|
8e359cf145 | ||
|
|
52e598a66e | ||
|
|
0a60cc0363 | ||
|
|
75788f7cb5 | ||
|
|
b58a7ad053 | ||
|
|
f763126b38 | ||
|
|
e79bf28bdc | ||
|
|
d0b3bc60df | ||
|
|
117a2e00c4 | ||
|
|
f8e88623a8 | ||
|
|
705475e971 | ||
|
|
2b0a51520a | ||
|
|
55145a38ff | ||
|
|
25d1039376 | ||
|
|
7a6b0d5da4 | ||
|
|
e090d3cfd3 | ||
|
|
1c1ac25857 | ||
|
|
3ce40b1ab8 | ||
|
|
1494df9eb3 | ||
|
|
a70959e4a6 | ||
|
|
b6f48a910f | ||
|
|
e4d101237e | ||
|
|
7556086f71 | ||
|
|
7b619e6b31 | ||
|
|
76ab2695db | ||
|
|
418068e702 | ||
|
|
12b28ffb3f | ||
|
|
526fa24eab | ||
|
|
a13f6a34c4 | ||
|
|
f659876845 | ||
|
|
643a743d70 | ||
|
|
ab4cc80628 | ||
|
|
fb3795bd70 | ||
|
|
5c14c68899 | ||
|
|
5a878e89d0 | ||
|
|
bdb07220ed | ||
|
|
641d5e6dda | ||
|
|
b24339f232 | ||
|
|
ff324fe6e7 | ||
|
|
fee9b8a0de | ||
|
|
ad9488f54e | ||
|
|
ed77fc3a9d | ||
|
|
babd8e672f | ||
|
|
8ceac493ef | ||
|
|
fee808789f | ||
|
|
dd03c554c9 | ||
|
|
3ab4fcb277 | ||
|
|
11e3cac440 | ||
|
|
1d7f3b15a4 | ||
|
|
b7ad3c036b | ||
|
|
7b2ff593a4 | ||
|
|
916f3c0921 | ||
|
|
846f1dfa6c | ||
|
|
3f631ff382 | ||
|
|
43a370e162 | ||
|
|
64320c7aca | ||
|
|
61cedfb2b7 | ||
|
|
a9ce980d03 | ||
|
|
8409f08a2d | ||
|
|
ef2cccf16e | ||
|
|
0285302ecc | ||
|
|
668ea7418d | ||
|
|
b12bdfcc22 | ||
|
|
65de627870 | ||
|
|
03d1dcaefb | ||
|
|
14768bde28 | ||
|
|
9555ff43c7 | ||
|
|
75acb1ade6 | ||
|
|
fd12ca6e21 | ||
|
|
ff2f84d46b | ||
|
|
1ffce112d1 | ||
|
|
382df34f06 | ||
|
|
124f300362 | ||
|
|
f782899768 | ||
|
|
cdb83bb47d | ||
|
|
72bcc0d020 | ||
|
|
ac901b46c3 | ||
|
|
c1b3d325f6 | ||
|
|
7cad2fec27 | ||
|
|
7edc2a71d8 | ||
|
|
913f7a9e42 | ||
|
|
dd98757b2a | ||
|
|
1e3ee23120 | ||
|
|
74d8e0a892 | ||
|
|
50af063f64 | ||
|
|
80a66d74ca | ||
|
|
6c2908681a | ||
|
|
90593c0d7c | ||
|
|
8fb958e772 | ||
|
|
fc39ab3fb9 | ||
|
|
58e7ef2527 | ||
|
|
fec32380ae | ||
|
|
3b8d87bd17 | ||
|
|
131ced10fc | ||
|
|
886752effd | ||
|
|
a5d54a637e | ||
|
|
1b4edf16fb | ||
|
|
4042d34585 | ||
|
|
135c3ff454 | ||
|
|
f8d5791fab | ||
|
|
ffa4b1695b | ||
|
|
98214ff578 | ||
|
|
57ceac0c24 | ||
|
|
a526bc5a3e | ||
|
|
fac4516353 | ||
|
|
4db13fb924 | ||
|
|
c97ee22e66 | ||
|
|
98737f92af | ||
|
|
74637f9d2a | ||
|
|
69dbc2fe7a | ||
|
|
d157e200f6 | ||
|
|
594540cff5 | ||
|
|
8e47e8cd3d | ||
|
|
c5d7625a06 | ||
|
|
7763c5e288 | ||
|
|
957988d977 | ||
|
|
0c04c06d4e | ||
|
|
a7ef014b92 | ||
|
|
3822d971cb | ||
|
|
eb65c41d15 | ||
|
|
0c3fdbc539 | ||
|
|
7cb985116e | ||
|
|
2297494d21 | ||
|
|
19a61b6ce4 | ||
|
|
492aed200a | ||
|
|
be2f33b5d1 | ||
|
|
5d48cecee8 | ||
|
|
f0bfe1a152 | ||
|
|
d2ff386f97 | ||
|
|
42eaa75437 | ||
|
|
7fb4e75956 | ||
|
|
87c549960e | ||
|
|
4ac0f1e87f | ||
|
|
5465bee630 | ||
|
|
60a3d46e59 | ||
|
|
e100d54eed | ||
|
|
848d50ec43 | ||
|
|
a9a8686295 | ||
|
|
30a9309bd8 | ||
|
|
ab64cd192b | ||
|
|
5fc2c8c14b | ||
|
|
ea7ce450f1 | ||
|
|
ce77b43705 | ||
|
|
feb061240b | ||
|
|
bd2ba804ee | ||
|
|
bd75f7b0fc | ||
|
|
32e4f7514d | ||
|
|
c9e1d35daf | ||
|
|
3be6ae91f2 | ||
|
|
931104fbcf | ||
|
|
1d5cec94a5 | ||
|
|
faeec01a6d | ||
|
|
b8db987579 | ||
|
|
1fad85d48d | ||
|
|
eb70c9f5f0 | ||
|
|
ce025df7b7 | ||
|
|
daf5797fed | ||
|
|
0baa7d9891 | ||
|
|
f6cd0f668f | ||
|
|
9ae83cc34d | ||
|
|
06ebb76948 | ||
|
|
3a8ba154b2 | ||
|
|
089801109e | ||
|
|
a8a4b5c2b4 | ||
|
|
089bda2de0 | ||
|
|
e18e2e26da | ||
|
|
104cf194ac | ||
|
|
de7cdb4aef | ||
|
|
6bdf9891e8 | ||
|
|
27d37ece7d | ||
|
|
b9afca3608 | ||
|
|
b796464241 | ||
|
|
8bf3875d9a | ||
|
|
3b22ce75db | ||
|
|
a924227805 | ||
|
|
2144d3f8c0 | ||
|
|
fcd6786149 | ||
|
|
ea968ebf3c | ||
|
|
8a7b852974 | ||
|
|
c8d3e3b70c | ||
|
|
7a90a6f9f4 |
No files matched your search
@@ -88,3 +88,4 @@ jobs:
|
|||||||
docker compose down
|
docker compose down
|
||||||
docker compose pull
|
docker compose pull
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
|
docker system prune -af
|
||||||
+3
-1
@@ -1,3 +1,5 @@
|
|||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
.VSCodeCounter
|
.VSCodeCounter
|
||||||
|
*.sh
|
||||||
|
mongo
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
Checkmate is an open-source uptime and infrastructure monitoring application. It monitors server hardware, uptime, response times, and incidents with real-time alerts. The companion agent [Capture](https://github.com/bluewave-labs/capture) provides infrastructure metrics (CPU, RAM, disk, temperature).
|
||||||
|
|
||||||
|
## Development Commands
|
||||||
|
|
||||||
|
### Client (React/Vite)
|
||||||
|
```bash
|
||||||
|
cd client
|
||||||
|
npm install
|
||||||
|
npm run dev # Start dev server at http://localhost:5173
|
||||||
|
npm run build # TypeScript check + production build
|
||||||
|
npm run lint # ESLint (strict, max-warnings 0)
|
||||||
|
npm run format # Prettier formatting
|
||||||
|
npm run format-check # Check formatting
|
||||||
|
```
|
||||||
|
|
||||||
|
### Server (Node.js/Express)
|
||||||
|
```bash
|
||||||
|
cd server
|
||||||
|
npm install
|
||||||
|
npm run dev # Start with hot-reload (nodemon + tsx) at http://localhost:52345
|
||||||
|
npm run build # TypeScript compile + path alias resolution
|
||||||
|
npm run test # Run Mocha tests with c8 coverage
|
||||||
|
npm run lint # ESLint v9
|
||||||
|
npm run lint-fix # Auto-fix lint issues
|
||||||
|
npm run format # Prettier formatting
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Development
|
||||||
|
```bash
|
||||||
|
cd docker/dev
|
||||||
|
./build_images.sh
|
||||||
|
docker run -d -p 27017:27017 -v uptime_mongo_data:/data/db --name uptime_database_mongo mongo:6.0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Setup
|
||||||
|
|
||||||
|
### Server `.env` (minimum required)
|
||||||
|
```env
|
||||||
|
CLIENT_HOST="http://localhost:5173"
|
||||||
|
JWT_SECRET="my_secret_key_change_this"
|
||||||
|
DB_CONNECTION_STRING="mongodb://localhost:27017/uptime_db"
|
||||||
|
TOKEN_TTL="99d"
|
||||||
|
ORIGIN="localhost"
|
||||||
|
LOG_LEVEL="debug"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Client `.env`
|
||||||
|
```env
|
||||||
|
VITE_APP_API_BASE_URL="http://localhost:52345/api/v1"
|
||||||
|
VITE_APP_LOG_LEVEL="debug"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Monorepo Structure
|
||||||
|
- `/client` - React 18 + TypeScript + Vite + MUI frontend
|
||||||
|
- `/server` - Node.js 20+ + Express + TypeScript backend
|
||||||
|
- `/docker` - Multi-environment Docker configs (dev, staging, prod, arm, mono)
|
||||||
|
|
||||||
|
### Backend Layers
|
||||||
|
```
|
||||||
|
server/src/
|
||||||
|
├── controllers/ # Route handlers (authController, monitorController, etc.)
|
||||||
|
├── service/ # Business logic
|
||||||
|
│ ├── business/ # Core monitoring logic
|
||||||
|
│ ├── infrastructure/ # Server/system utilities
|
||||||
|
│ └── system/ # App-level settings
|
||||||
|
├── db/
|
||||||
|
│ ├── models/ # Mongoose schemas (Monitor, Check, Incident, User, etc.)
|
||||||
|
│ ├── migration/ # Database migrations (run on startup)
|
||||||
|
│ └── modules/ # Database-specific modules
|
||||||
|
├── middleware/v1/ # verifyJWT, rateLimiter, sanitization, responseHandler
|
||||||
|
├── routes/v1/ # API route definitions
|
||||||
|
├── validation/ # Joi input validation schemas
|
||||||
|
└── repositories/ # Data access layer
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend Structure
|
||||||
|
```
|
||||||
|
client/src/
|
||||||
|
├── Components/ # Reusable UI components
|
||||||
|
├── Pages/ # Page components (Auth, Uptime, Infrastructure, Incidents, etc.)
|
||||||
|
├── Features/ # Redux slices (Auth, UI)
|
||||||
|
├── Hooks/ # Custom React hooks
|
||||||
|
├── Utils/ # Utilities (NetworkService.js is main API client)
|
||||||
|
├── Validation/ # Input validation
|
||||||
|
└── locales/ # i18n translations
|
||||||
|
```
|
||||||
|
|
||||||
|
### API
|
||||||
|
- Base URL: `/api/v1`
|
||||||
|
- Documentation: `http://localhost:52345/api-docs` (Swagger UI)
|
||||||
|
- OpenAPI spec: `/server/openapi.json`
|
||||||
|
|
||||||
|
### Key Technologies
|
||||||
|
- **State Management**: Redux Toolkit + Redux-Persist
|
||||||
|
- **Data Fetching**: SWR + Axios
|
||||||
|
- **Database**: MongoDB with Mongoose ODM
|
||||||
|
- **Queue/Cache**: Redis + BullMQ + Pulse (cron scheduling)
|
||||||
|
- **i18n**: i18next + react-i18next (translations via PoEditor)
|
||||||
|
|
||||||
|
## Code Conventions
|
||||||
|
|
||||||
|
### Internationalization
|
||||||
|
All user-facing strings must use the translation function:
|
||||||
|
```javascript
|
||||||
|
t('your.key') // Never hardcode UI strings
|
||||||
|
```
|
||||||
|
|
||||||
|
### Branching
|
||||||
|
- Always branch from `develop` (not master)
|
||||||
|
- Use descriptive names: `feat/add-alerts`, `fix/login-error`
|
||||||
|
- PRs target `develop` branch
|
||||||
|
|
||||||
|
### Formatting
|
||||||
|
- **Client**: Prettier with `printWidth: 90`, tabs, double quotes
|
||||||
|
- **Server**: Prettier with `printWidth: 150`, tabs, double quotes
|
||||||
|
- Both use ESLint with strict settings
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
Server tests use Mocha + Chai + Sinon:
|
||||||
|
```bash
|
||||||
|
npm test # Run all tests with coverage
|
||||||
|
npm test -- --grep "pattern" # Run specific tests
|
||||||
|
```
|
||||||
|
Test files: `server/tests/**/*.test.js`
|
||||||
|
|
||||||
|
## Database Models
|
||||||
|
|
||||||
|
Key Mongoose models in `/server/src/db/models/`:
|
||||||
|
- **Monitor** - Monitoring configuration (website, infrastructure, port, etc.)
|
||||||
|
- **Check** - Individual monitoring check results
|
||||||
|
- **Incident** - Downtime incidents
|
||||||
|
- **User** - User accounts
|
||||||
|
- **Team** - Team/workspace management
|
||||||
|
- **StatusPage** - Public status pages
|
||||||
|
- **Notification** - Alert configuration (email, Discord, Slack, webhooks)
|
||||||
|
- **MaintenanceWindow** - Scheduled maintenance periods
|
||||||
|
- **AppSettings** - Global application settings
|
||||||
+29
-4
@@ -10,6 +10,31 @@ PS: **We work closely with contributors on our [Discord channel](https://discord
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## 🚀 Quick Setup Checklist
|
||||||
|
|
||||||
|
Before you dive in, make sure you have these installed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check Node.js (v16+ required)
|
||||||
|
node --version
|
||||||
|
|
||||||
|
# Check npm
|
||||||
|
npm --version
|
||||||
|
|
||||||
|
# Check Docker
|
||||||
|
docker --version
|
||||||
|
|
||||||
|
# Check Git
|
||||||
|
git --version
|
||||||
|
```
|
||||||
|
|
||||||
|
**New to contributing?** Start here:
|
||||||
|
1. Pick a [`good-first-issue`](https://github.com/bluewave-labs/checkmate/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
|
||||||
|
2. Comment that you'd like to work on it
|
||||||
|
3. Follow the [setup guide](#set-up-checkmate-locally) below
|
||||||
|
4. Join our [Discord](https://discord.com/invite/NAb6H3UTjK) if you get stuck
|
||||||
|
|
||||||
## Table of contents
|
## Table of contents
|
||||||
|
|
||||||
- [How do I...?](#how-do-i)
|
- [How do I...?](#how-do-i)
|
||||||
@@ -66,7 +91,7 @@ cd Checkmate
|
|||||||
Navigate to the Docker dev directory:
|
Navigate to the Docker dev directory:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd server/docker/dev
|
cd docker/dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Build the Docker images:
|
Build the Docker images:
|
||||||
@@ -78,13 +103,13 @@ Build the Docker images:
|
|||||||
Run MongoDB container:
|
Run MongoDB container:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -d -p 27017:27017 -v $(pwd)/mongo/data:/data/db --name uptime_database_mongo uptime_database_mongo
|
docker run -d -p 27017:27017 -v uptime_mongo_data:/data/db --name uptime_database_mongo mongo:6.0
|
||||||
```
|
```
|
||||||
|
|
||||||
Navigate back to the root directory:
|
Navigate back to the root directory:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ../../..
|
cd ../..
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Step 3: Set Up the Backend (Server)
|
#### Step 3: Set Up the Backend (Server)
|
||||||
@@ -161,7 +186,7 @@ Start the frontend:
|
|||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
The client will run at `http://localhost:5173`.
|
The client will run at `http://localhost:5173`.
|
||||||
|
|
||||||
#### Step 5: Access the Application
|
#### Step 5: Access the Application
|
||||||
|
|
||||||
|
|||||||
+11325
File diff suppressed because it is too large.
Load diff
@@ -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).
|
See installation instructions in [Checkmate documentation portal](https://docs.checkmate.so/checkmate-2.1/users-guide/quickstart).
|
||||||
|
|
||||||
Alternatively, you can also use [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (South Africa) or [Pikapods](https://www.pikapods.com/) to quickly spin off a Checkmate instance. If you would like to monitor your server infrastructure, you'll need [Capture agent](https://github.com/bluewave-labs/capture). Capture repository also contains the installation instructions.
|
Alternatively, you can also use [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (South Africa) or [Pikapods](https://www.pikapods.com/) to quickly spin off a Checkmate instance. If you would like to monitor your server infrastructure, you'll need [Capture agent](https://github.com/bluewave-labs/capture). Capture repository also contains the installation instructions.
|
||||||
|
|
||||||
### Using a Custom CA
|
### Using a Custom CA
|
||||||
|
|
||||||
@@ -91,6 +91,7 @@ Feel free to ask questions or share your ideas - we'd love to hear from you!
|
|||||||
- Website monitoring
|
- Website monitoring
|
||||||
- Page speed monitoring
|
- Page speed monitoring
|
||||||
- Infrastructure monitoring (memory, disk usage, CPU performance, network etc) - requires [Capture](https://github.com/bluewave-labs/capture) agent
|
- Infrastructure monitoring (memory, disk usage, CPU performance, network etc) - requires [Capture](https://github.com/bluewave-labs/capture) agent
|
||||||
|
- Selective disk monitoring with mountpoint selection
|
||||||
- Docker monitoring
|
- Docker monitoring
|
||||||
- Ping monitoring
|
- Ping monitoring
|
||||||
- SSL monitoring
|
- SSL monitoring
|
||||||
@@ -162,6 +163,7 @@ Here's how you can contribute:
|
|||||||
4. Open an issue if you believe you've encountered a bug.
|
4. Open an issue if you believe you've encountered a bug.
|
||||||
5. Check for good-first-issue's if you are a newcomer.
|
5. Check for good-first-issue's if you are a newcomer.
|
||||||
6. Make a pull request to add new features/make quality-of-life improvements/fix bugs.
|
6. Make a pull request to add new features/make quality-of-life improvements/fix bugs.
|
||||||
|
7. Check out this interactive walkthrough of the `Checkmate` codebase on CodeCanvas [here](https://www.code-canvas.com/?session=unauthenticatedGithub&repo=Checkmate&owner=bluewave-labs&branch=develop&OnboardingTutorial=true). To refine existing dataflow simulation or create new ones, follow the quick tutorial [here](https://docs.code-canvas.com/updating-diagram).
|
||||||
|
|
||||||
<a href="https://github.com/bluewave-labs/checkmate/graphs/contributors">
|
<a href="https://github.com/bluewave-labs/checkmate/graphs/contributors">
|
||||||
<img src="https://contrib.rocks/image?repo=bluewave-labs/checkmate" />
|
<img src="https://contrib.rocks/image?repo=bluewave-labs/checkmate" />
|
||||||
|
|||||||
@@ -21,7 +21,12 @@ cd checkmate/charts/helm/checkmate
|
|||||||
Edit `values.yaml` to update:
|
Edit `values.yaml` to update:
|
||||||
- `client.ingress.host` and `server.ingress.host` with your domain names
|
- `client.ingress.host` and `server.ingress.host` with your domain names
|
||||||
- `server.protocol` (usually http or https)
|
- `server.protocol` (usually http or https)
|
||||||
|
- **If upgrading**: Migrate persistence settings from flat structure to nested:
|
||||||
|
- Old: `persistence.mongodbSize` → New: `persistence.mongo.size`
|
||||||
|
- Old: `persistence.redisSize` → New: `persistence.redis.size`
|
||||||
|
- Add: `persistence.mongo.storageClass` and `persistence.redis.storageClass` (leave empty for default)
|
||||||
- Secrets under the `secrets` section (`JWT_SECRET`, email credentials, API keys, etc.) — replace all change_me values
|
- Secrets under the `secrets` section (`JWT_SECRET`, email credentials, API keys, etc.) — replace all change_me values
|
||||||
|
- **For TLS/HTTPS**: Configure ingress TLS settings (see section below)
|
||||||
|
|
||||||
### 3. Deploy the Helm chart
|
### 3. Deploy the Helm chart
|
||||||
```bash
|
```bash
|
||||||
@@ -37,3 +42,75 @@ kubectl get svc
|
|||||||
```
|
```
|
||||||
|
|
||||||
Once all pods are `Running` and `Ready`, you can access Checkmate via the configured ingress hosts.
|
Once all pods are `Running` and `Ready`, you can access Checkmate via the configured ingress hosts.
|
||||||
|
|
||||||
|
## Enabling TLS/HTTPS with cert-manager
|
||||||
|
|
||||||
|
If you have [cert-manager](https://cert-manager.io/) installed in your cluster, you can enable automatic TLS certificate provisioning using Let's Encrypt or other certificate issuers.
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
- cert-manager installed in your cluster
|
||||||
|
- A ClusterIssuer or Issuer configured (e.g., `letsencrypt-prod`)
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
Edit `values.yaml` to enable TLS (and update protocols to https):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
client:
|
||||||
|
protocol: https
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
host: checkmate.example.com
|
||||||
|
className: nginx
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||||
|
tls:
|
||||||
|
enabled: true
|
||||||
|
secretName: checkmate-client-tls
|
||||||
|
|
||||||
|
server:
|
||||||
|
protocol: https
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
host: checkmate.example.com
|
||||||
|
className: nginx
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||||
|
tls:
|
||||||
|
enabled: true
|
||||||
|
secretName: checkmate-server-tls
|
||||||
|
```
|
||||||
|
|
||||||
|
### Alternative: Using --set flags
|
||||||
|
|
||||||
|
You can also enable TLS during installation using Helm's `--set` flags:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install checkmate ./charts/helm/checkmate \
|
||||||
|
--set client.protocol=https \
|
||||||
|
--set server.protocol=https \
|
||||||
|
--set client.ingress.annotations."cert-manager\.io/cluster-issuer"="letsencrypt-prod" \
|
||||||
|
--set client.ingress.tls.enabled=true \
|
||||||
|
--set client.ingress.tls.secretName=checkmate-client-tls \
|
||||||
|
--set server.ingress.annotations."cert-manager\.io/cluster-issuer"="letsencrypt-prod" \
|
||||||
|
--set server.ingress.tls.enabled=true \
|
||||||
|
--set server.ingress.tls.secretName=checkmate-server-tls
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verification
|
||||||
|
|
||||||
|
After deployment, cert-manager will automatically create the TLS secrets. You can verify the certificate status:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check certificates
|
||||||
|
kubectl get certificates
|
||||||
|
|
||||||
|
# Check certificate details
|
||||||
|
kubectl describe certificate checkmate-client-tls
|
||||||
|
kubectl describe certificate checkmate-server-tls
|
||||||
|
|
||||||
|
# Verify the secrets were created
|
||||||
|
kubectl get secrets | grep checkmate-tls
|
||||||
|
```
|
||||||
|
|
||||||
|
The ingress will automatically use these secrets to enable HTTPS access to your Checkmate instance.
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
{{- if eq .Values.client.ingress.host "change_me" }}
|
|
||||||
{{- fail "client.ingress.host must be overridden and not set to 'change_me'" }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{- if eq .Values.server.ingress.host "change_me" }}
|
|
||||||
{{- fail "server.ingress.host must be overridden and not set to 'change_me'" }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{- $protocol := .Values.server.protocol }}
|
|
||||||
{{- if not (or (eq $protocol "http") (eq $protocol "https")) }}
|
|
||||||
{{- fail "server.protocol must be either 'http' or 'https'" }}
|
|
||||||
{{- end }}
|
|
||||||
@@ -11,6 +11,12 @@ metadata:
|
|||||||
{{- end }}
|
{{- end }}
|
||||||
spec:
|
spec:
|
||||||
ingressClassName: {{ .Values.client.ingress.className }}
|
ingressClassName: {{ .Values.client.ingress.className }}
|
||||||
|
{{- if .Values.client.ingress.tls.enabled }}
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- {{ .Values.client.ingress.host }}
|
||||||
|
secretName: {{ default (printf "%s-client-tls" .Release.Name) .Values.client.ingress.tls.secretName }}
|
||||||
|
{{- end }}
|
||||||
rules:
|
rules:
|
||||||
- host: {{ .Values.client.ingress.host }}
|
- host: {{ .Values.client.ingress.host }}
|
||||||
http:
|
http:
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ spec:
|
|||||||
- metadata:
|
- metadata:
|
||||||
name: checkmate-mongo-persistent-storage
|
name: checkmate-mongo-persistent-storage
|
||||||
spec:
|
spec:
|
||||||
storageClassName: "gp3"
|
storageClassName: {{ .Values.persistence.mongo.storageClass | quote }}
|
||||||
accessModes: ["ReadWriteOnce"]
|
accessModes: ["ReadWriteOnce"]
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
storage: {{ .Values.persistence.mongodbSize }}
|
storage: {{ .Values.persistence.mongo.size | default "5Gi" | quote }}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
{{- if eq .Values.client.ingress.host "change_me" }}
|
||||||
|
{{- fail "client.ingress.host must be overridden and not set to 'change_me'" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- if eq .Values.server.ingress.host "change_me" }}
|
||||||
|
{{- fail "server.ingress.host must be overridden and not set to 'change_me'" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- $serverProtocol := .Values.server.protocol }}
|
||||||
|
{{- if not (or (eq $serverProtocol "http") (eq $serverProtocol "https")) }}
|
||||||
|
{{- fail "server.protocol must be either 'http' or 'https'" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- $clientProtocol := .Values.client.protocol }}
|
||||||
|
{{- if not (or (eq $clientProtocol "http") (eq $clientProtocol "https")) }}
|
||||||
|
{{- fail "client.protocol must be either 'http' or 'https'" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/* Enforce protocol when TLS is enabled to avoid mixed-content */}}
|
||||||
|
{{- if and .Values.client.ingress.tls.enabled (ne $clientProtocol "https") }}
|
||||||
|
{{- fail "client.ingress.tls.enabled is true but client.protocol is not 'https'. Set client.protocol: https to avoid mixed content." }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- if and .Values.server.ingress.tls.enabled (ne $serverProtocol "https") }}
|
||||||
|
{{- fail "server.ingress.tls.enabled is true but server.protocol is not 'https'. Set server.protocol: https to ensure correct API base URL." }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/* If client runs on https, API must also be https to avoid mixed content */}}
|
||||||
|
{{- if and (eq $clientProtocol "https") (ne $serverProtocol "https") }}
|
||||||
|
{{- fail "client.protocol is 'https' but server.protocol is not. Set server.protocol: https to prevent browser mixed-content issues." }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/* Fail early if TLS enabled without cert-manager annotations (cluster-issuer or issuer) */}}
|
||||||
|
{{- $cAnn := .Values.client.ingress.annotations | default dict }}
|
||||||
|
{{- $sAnn := .Values.server.ingress.annotations | default dict }}
|
||||||
|
|
||||||
|
{{- $clientHasIssuer := or (hasKey $cAnn "cert-manager.io/cluster-issuer") (hasKey $cAnn "cert-manager.io/issuer") }}
|
||||||
|
{{- $serverHasIssuer := or (hasKey $sAnn "cert-manager.io/cluster-issuer") (hasKey $sAnn "cert-manager.io/issuer") }}
|
||||||
|
|
||||||
|
{{- if and .Values.client.ingress.tls.enabled (not $clientHasIssuer) }}
|
||||||
|
{{- fail "client.ingress.tls.enabled is true but no cert-manager issuer annotation found. Add 'cert-manager.io/cluster-issuer' or 'cert-manager.io/issuer'." }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- if and .Values.server.ingress.tls.enabled (not $serverHasIssuer) }}
|
||||||
|
{{- fail "server.ingress.tls.enabled is true but no cert-manager issuer annotation found. Add 'cert-manager.io/cluster-issuer' or 'cert-manager.io/issuer'." }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/* Secret name can be omitted; we default to <release>-client|server-tls in templates */}}
|
||||||
@@ -25,9 +25,9 @@ spec:
|
|||||||
- metadata:
|
- metadata:
|
||||||
name: checkmate-redis-persistent-storage
|
name: checkmate-redis-persistent-storage
|
||||||
spec:
|
spec:
|
||||||
storageClassName: "gp3"
|
storageClassName: {{ .Values.persistence.redis.storageClass | quote }}
|
||||||
accessModes: ["ReadWriteOnce"]
|
accessModes: ["ReadWriteOnce"]
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
storage: {{ .Values.persistence.redisSize }}
|
storage: {{ .Values.persistence.redis.size | default "1Gi" | quote }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
@@ -18,6 +18,12 @@ metadata:
|
|||||||
#nginx.ingress.kubernetes.io/cors-allow-credentials: "true"*/}}
|
#nginx.ingress.kubernetes.io/cors-allow-credentials: "true"*/}}
|
||||||
spec:
|
spec:
|
||||||
ingressClassName: {{ .Values.server.ingress.className }}
|
ingressClassName: {{ .Values.server.ingress.className }}
|
||||||
|
{{- if .Values.server.ingress.tls.enabled }}
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- {{ .Values.server.ingress.host }}
|
||||||
|
secretName: {{ default (printf "%s-server-tls" .Release.Name) .Values.server.ingress.tls.secretName }}
|
||||||
|
{{- end }}
|
||||||
rules:
|
rules:
|
||||||
- host: {{ .Values.server.ingress.host }}
|
- host: {{ .Values.server.ingress.host }}
|
||||||
http:
|
http:
|
||||||
|
|||||||
@@ -7,6 +7,15 @@ client:
|
|||||||
host: change_me
|
host: change_me
|
||||||
className: nginx
|
className: nginx
|
||||||
annotations: {}
|
annotations: {}
|
||||||
|
# Example annotations for cert-manager:
|
||||||
|
# annotations:
|
||||||
|
# cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||||
|
tls:
|
||||||
|
enabled: false
|
||||||
|
# secretName: {{ .Release.Name }}-client-tls # Optional; defaults to <release>-client-tls if omitted
|
||||||
|
# Note: when enabling TLS, also set client.protocol: https and add
|
||||||
|
# a cert-manager issuer annotation (e.g. cert-manager.io/cluster-issuer: "letsencrypt-prod").
|
||||||
|
# The secret will be automatically created by cert-manager when using the cert-manager.io/cluster-issuer annotation
|
||||||
|
|
||||||
server:
|
server:
|
||||||
image: ghcr.io/bluewave-labs/checkmate-backend:v3.2.0
|
image: ghcr.io/bluewave-labs/checkmate-backend:v3.2.0
|
||||||
@@ -17,6 +26,15 @@ server:
|
|||||||
host: change_me
|
host: change_me
|
||||||
className: nginx
|
className: nginx
|
||||||
annotations: {}
|
annotations: {}
|
||||||
|
# Example annotations for cert-manager:
|
||||||
|
# annotations:
|
||||||
|
# cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||||
|
tls:
|
||||||
|
enabled: false
|
||||||
|
# secretName: {{ .Release.Name }}-server-tls # Optional; defaults to <release>-server-tls if omitted
|
||||||
|
# Note: when enabling TLS, also set server.protocol: https and add
|
||||||
|
# a cert-manager issuer annotation (e.g. cert-manager.io/cluster-issuer: "letsencrypt-prod").
|
||||||
|
# The secret will be automatically created by cert-manager when using the cert-manager.io/cluster-issuer annotation
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
enabled: false
|
enabled: false
|
||||||
@@ -44,5 +62,9 @@ secrets:
|
|||||||
# REFRESH_TOKEN_TTL: 99d
|
# REFRESH_TOKEN_TTL: 99d
|
||||||
|
|
||||||
persistence:
|
persistence:
|
||||||
mongodbSize: 5Gi
|
mongo:
|
||||||
redisSize: 1Gi
|
size: 5Gi
|
||||||
|
storageClass: ""
|
||||||
|
redis:
|
||||||
|
size: 1Gi
|
||||||
|
storageClass: ""
|
||||||
+2
-1
@@ -28,4 +28,5 @@ dist-ssr
|
|||||||
|
|
||||||
# Scripts
|
# Scripts
|
||||||
*.sh
|
*.sh
|
||||||
!env.sh
|
!env.sh
|
||||||
|
.env.development
|
||||||
Generated
+651
-64
@@ -12,7 +12,6 @@
|
|||||||
"@emotion/styled": "^11.13.0",
|
"@emotion/styled": "^11.13.0",
|
||||||
"@hello-pangea/dnd": "^18.0.0",
|
"@hello-pangea/dnd": "^18.0.0",
|
||||||
"@hookform/resolvers": "5.2.2",
|
"@hookform/resolvers": "5.2.2",
|
||||||
"@mui/icons-material": "6.4.11",
|
|
||||||
"@mui/lab": "6.0.0-dev.240424162023-9968b4889d",
|
"@mui/lab": "6.0.0-dev.240424162023-9968b4889d",
|
||||||
"@mui/material": "6.4.11",
|
"@mui/material": "6.4.11",
|
||||||
"@mui/x-charts": "^7.5.1",
|
"@mui/x-charts": "^7.5.1",
|
||||||
@@ -25,8 +24,9 @@
|
|||||||
"human-interval": "2.0.1",
|
"human-interval": "2.0.1",
|
||||||
"i18next": "25.4.2",
|
"i18next": "25.4.2",
|
||||||
"joi": "17.13.3",
|
"joi": "17.13.3",
|
||||||
|
"lucide-react": "^0.562.0",
|
||||||
"mui-color-input": "^6.0.0",
|
"mui-color-input": "^6.0.0",
|
||||||
"pretty-ms": "9.3.0",
|
"pretty-ms": "^9.3.0",
|
||||||
"react": "18.3.1",
|
"react": "18.3.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hook-form": "7.63.0",
|
"react-hook-form": "7.63.0",
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
"react-toastify": "^10.0.5",
|
"react-toastify": "^10.0.5",
|
||||||
"recharts": "2.15.2",
|
"recharts": "2.15.2",
|
||||||
"redux-persist": "6.0.0",
|
"redux-persist": "6.0.0",
|
||||||
"swr": "2.3.6",
|
"swr": "^2.3.6",
|
||||||
"vite-plugin-svgr": "^4.2.0",
|
"vite-plugin-svgr": "^4.2.0",
|
||||||
"zod": "4.1.11"
|
"zod": "4.1.11"
|
||||||
},
|
},
|
||||||
@@ -97,6 +97,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz",
|
||||||
"integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==",
|
"integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.2.0",
|
"@ampproject/remapping": "^2.2.0",
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.27.1",
|
||||||
@@ -408,6 +409,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
|
||||||
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
|
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.18.3",
|
"@babel/runtime": "^7.18.3",
|
||||||
"@emotion/babel-plugin": "^11.13.5",
|
"@emotion/babel-plugin": "^11.13.5",
|
||||||
@@ -451,6 +453,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz",
|
||||||
"integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==",
|
"integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.18.3",
|
"@babel/runtime": "^7.18.3",
|
||||||
"@emotion/babel-plugin": "^11.13.5",
|
"@emotion/babel-plugin": "^11.13.5",
|
||||||
@@ -496,6 +499,262 @@
|
|||||||
"integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
|
"integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/@esbuild/linux-x64": {
|
||||||
"version": "0.25.5",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz",
|
||||||
@@ -512,6 +771,134 @@
|
|||||||
"node": ">=18"
|
"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": {
|
"node_modules/@eslint-community/eslint-utils": {
|
||||||
"version": "4.7.0",
|
"version": "4.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
|
||||||
@@ -802,32 +1189,6 @@
|
|||||||
"url": "https://opencollective.com/mui-org"
|
"url": "https://opencollective.com/mui-org"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mui/icons-material": {
|
|
||||||
"version": "6.4.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.11.tgz",
|
|
||||||
"integrity": "sha512-+jjJGIrB1awNbMv4ZVPPdN/p7O1UKFZ+xqRvNIQ8B1KnlID5hPMPBLM6UUbRF4bu3UDCbu79rn9Nye5LGNzmeA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/runtime": "^7.26.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/mui-org"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@mui/material": "^6.4.11",
|
|
||||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
||||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@mui/lab": {
|
"node_modules/@mui/lab": {
|
||||||
"version": "6.0.0-dev.240424162023-9968b4889d",
|
"version": "6.0.0-dev.240424162023-9968b4889d",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/lab/-/lab-6.0.0-dev.240424162023-9968b4889d.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/lab/-/lab-6.0.0-dev.240424162023-9968b4889d.tgz",
|
||||||
@@ -989,6 +1350,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.11.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.11.tgz",
|
||||||
"integrity": "sha512-k2D3FLJS+/qD0qnd6ZlAjGFvaaxe1Dl10NyvpeDzIebMuYdn8VqYe6XBgGueEAtnzSJM4V03VD9kb5Fi24dnTA==",
|
"integrity": "sha512-k2D3FLJS+/qD0qnd6ZlAjGFvaaxe1Dl10NyvpeDzIebMuYdn8VqYe6XBgGueEAtnzSJM4V03VD9kb5Fi24dnTA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.26.0",
|
"@babel/runtime": "^7.26.0",
|
||||||
"@mui/core-downloads-tracker": "^6.4.11",
|
"@mui/core-downloads-tracker": "^6.4.11",
|
||||||
@@ -1153,7 +1515,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.1.0.tgz",
|
||||||
"integrity": "sha512-4Kck4jxhqF6YxNwJdSae1WgDfXVg0lIH6JVJ7gtuFfuKcQCgomJxPvUEOySTFRPz1IZzwz5OAcToskRdffElDA==",
|
"integrity": "sha512-4Kck4jxhqF6YxNwJdSae1WgDfXVg0lIH6JVJ7gtuFfuKcQCgomJxPvUEOySTFRPz1IZzwz5OAcToskRdffElDA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.27.1",
|
"@babel/runtime": "^7.27.1",
|
||||||
"@mui/utils": "^7.1.0",
|
"@mui/utils": "^7.1.0",
|
||||||
@@ -1181,7 +1542,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.1.0.tgz",
|
||||||
"integrity": "sha512-/OM3S8kSHHmWNOP+NH9xEtpYSG10upXeQ0wLZnfDgmgadTAk5F4MQfFLyZ5FCRJENB3eRzltMmaNl6UtDnPovw==",
|
"integrity": "sha512-/OM3S8kSHHmWNOP+NH9xEtpYSG10upXeQ0wLZnfDgmgadTAk5F4MQfFLyZ5FCRJENB3eRzltMmaNl6UtDnPovw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.27.1",
|
"@babel/runtime": "^7.27.1",
|
||||||
"@mui/types": "^7.4.2",
|
"@mui/types": "^7.4.2",
|
||||||
@@ -1212,7 +1572,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.1.0.tgz",
|
||||||
"integrity": "sha512-m0mJ0c6iRC+f9hMeRe0W7zZX1wme3oUX0+XTVHjPG7DJz6OdQ6K/ggEOq7ZdwilcpdsDUwwMfOmvO71qDkYd2w==",
|
"integrity": "sha512-m0mJ0c6iRC+f9hMeRe0W7zZX1wme3oUX0+XTVHjPG7DJz6OdQ6K/ggEOq7ZdwilcpdsDUwwMfOmvO71qDkYd2w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.27.1",
|
"@babel/runtime": "^7.27.1",
|
||||||
"@emotion/cache": "^11.13.5",
|
"@emotion/cache": "^11.13.5",
|
||||||
@@ -1288,7 +1647,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.1.0.tgz",
|
||||||
"integrity": "sha512-/OM3S8kSHHmWNOP+NH9xEtpYSG10upXeQ0wLZnfDgmgadTAk5F4MQfFLyZ5FCRJENB3eRzltMmaNl6UtDnPovw==",
|
"integrity": "sha512-/OM3S8kSHHmWNOP+NH9xEtpYSG10upXeQ0wLZnfDgmgadTAk5F4MQfFLyZ5FCRJENB3eRzltMmaNl6UtDnPovw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.27.1",
|
"@babel/runtime": "^7.27.1",
|
||||||
"@mui/types": "^7.4.2",
|
"@mui/types": "^7.4.2",
|
||||||
@@ -1666,9 +2024,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@remix-run/router": {
|
"node_modules/@remix-run/router": {
|
||||||
"version": "1.23.0",
|
"version": "1.23.2",
|
||||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz",
|
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz",
|
||||||
"integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==",
|
"integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
@@ -1696,6 +2054,123 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
|
"version": "4.41.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.0.tgz",
|
||||||
|
"integrity": "sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-android-arm64": {
|
||||||
|
"version": "4.41.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.0.tgz",
|
||||||
|
"integrity": "sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||||
|
"version": "4.41.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.0.tgz",
|
||||||
|
"integrity": "sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-darwin-x64": {
|
||||||
|
"version": "4.41.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.0.tgz",
|
||||||
|
"integrity": "sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||||
|
"version": "4.41.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.0.tgz",
|
||||||
|
"integrity": "sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||||
|
"version": "4.41.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.0.tgz",
|
||||||
|
"integrity": "sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||||
|
"version": "4.41.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.0.tgz",
|
||||||
|
"integrity": "sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||||
|
"version": "4.41.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.0.tgz",
|
||||||
|
"integrity": "sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||||
|
"version": "4.41.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.0.tgz",
|
||||||
|
"integrity": "sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||||
"version": "4.41.0",
|
"version": "4.41.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.0.tgz",
|
||||||
@@ -1709,6 +2184,71 @@
|
|||||||
"linux"
|
"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": {
|
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||||
"version": "4.41.0",
|
"version": "4.41.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.0.tgz",
|
||||||
@@ -1735,6 +2275,45 @@
|
|||||||
"linux"
|
"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": {
|
"node_modules/@sideway/address": {
|
||||||
"version": "4.1.5",
|
"version": "4.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
|
||||||
@@ -2133,6 +2712,7 @@
|
|||||||
"integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==",
|
"integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.12.0"
|
"undici-types": "~7.12.0"
|
||||||
}
|
}
|
||||||
@@ -2154,6 +2734,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.22.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.22.tgz",
|
||||||
"integrity": "sha512-vUhG0YmQZ7kL/tmKLrD3g5zXbXXreZXB3pmROW8bg3CnLnpjkRVwUlLne7Ufa2r9yJ8+/6B73RzhAek5TBKh2Q==",
|
"integrity": "sha512-vUhG0YmQZ7kL/tmKLrD3g5zXbXXreZXB3pmROW8bg3CnLnpjkRVwUlLne7Ufa2r9yJ8+/6B73RzhAek5TBKh2Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
@@ -2217,6 +2798,7 @@
|
|||||||
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
|
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -2523,6 +3105,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"caniuse-lite": "^1.0.30001716",
|
"caniuse-lite": "^1.0.30001716",
|
||||||
"electron-to-chromium": "^1.5.149",
|
"electron-to-chromium": "^1.5.149",
|
||||||
@@ -2935,7 +3518,8 @@
|
|||||||
"version": "1.11.13",
|
"version": "1.11.13",
|
||||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
|
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
|
||||||
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
|
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.1",
|
"version": "4.4.1",
|
||||||
@@ -3342,6 +3926,7 @@
|
|||||||
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
|
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.2.0",
|
"@eslint-community/eslint-utils": "^4.2.0",
|
||||||
"@eslint-community/regexpp": "^4.6.1",
|
"@eslint-community/regexpp": "^4.6.1",
|
||||||
@@ -4149,6 +4734,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.27.6"
|
"@babel/runtime": "^7.27.6"
|
||||||
},
|
},
|
||||||
@@ -4685,9 +5271,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/js-yaml": {
|
"node_modules/js-yaml": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"argparse": "^2.0.1"
|
"argparse": "^2.0.1"
|
||||||
@@ -4852,6 +5438,15 @@
|
|||||||
"yallist": "^3.0.2"
|
"yallist": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lucide-react": {
|
||||||
|
"version": "0.562.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.562.0.tgz",
|
||||||
|
"integrity": "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==",
|
||||||
|
"license": "ISC",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/math-intrinsics": {
|
"node_modules/math-intrinsics": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
@@ -5416,6 +6011,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.1.0",
|
"loose-envify": "^1.1.0",
|
||||||
"scheduler": "^0.23.2"
|
"scheduler": "^0.23.2"
|
||||||
@@ -5429,6 +6025,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.63.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.63.0.tgz",
|
||||||
"integrity": "sha512-ZwueDMvUeucovM2VjkCf7zIHcs1aAlDimZu2Hvel5C5907gUzMpm4xCrQXtRzCvsBqFjonB4m3x4LzCFI1ZKWA==",
|
"integrity": "sha512-ZwueDMvUeucovM2VjkCf7zIHcs1aAlDimZu2Hvel5C5907gUzMpm4xCrQXtRzCvsBqFjonB4m3x4LzCFI1ZKWA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
},
|
},
|
||||||
@@ -5515,12 +6112,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-router": {
|
"node_modules/react-router": {
|
||||||
"version": "6.30.1",
|
"version": "6.30.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz",
|
||||||
"integrity": "sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==",
|
"integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@remix-run/router": "1.23.0"
|
"@remix-run/router": "1.23.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
@@ -5530,13 +6127,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-router-dom": {
|
"node_modules/react-router-dom": {
|
||||||
"version": "6.30.1",
|
"version": "6.30.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz",
|
||||||
"integrity": "sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==",
|
"integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@remix-run/router": "1.23.0",
|
"@remix-run/router": "1.23.2",
|
||||||
"react-router": "6.30.1"
|
"react-router": "6.30.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
@@ -5632,7 +6229,8 @@
|
|||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||||
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/redux-persist": {
|
"node_modules/redux-persist": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
@@ -5770,6 +6368,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.0.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.0.tgz",
|
||||||
"integrity": "sha512-HqMFpUbWlf/tvcxBFNKnJyzc7Lk+XO3FGc3pbNBLqEbOz0gPLRgcrlS3UF4MfUrVlstOaP/q0kM6GVvi+LrLRg==",
|
"integrity": "sha512-HqMFpUbWlf/tvcxBFNKnJyzc7Lk+XO3FGc3pbNBLqEbOz0gPLRgcrlS3UF4MfUrVlstOaP/q0kM6GVvi+LrLRg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree": "1.0.7"
|
"@types/estree": "1.0.7"
|
||||||
},
|
},
|
||||||
@@ -6405,6 +7004,7 @@
|
|||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -6524,6 +7124,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz",
|
||||||
"integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==",
|
"integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"fdir": "^6.4.4",
|
"fdir": "^6.4.4",
|
||||||
@@ -6744,20 +7345,6 @@
|
|||||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/yaml": {
|
|
||||||
"version": "2.8.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz",
|
|
||||||
"integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==",
|
|
||||||
"license": "ISC",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
|
||||||
"yaml": "bin.mjs"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 14.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/yocto-queue": {
|
"node_modules/yocto-queue": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||||
|
|||||||
+3
-3
@@ -17,7 +17,6 @@
|
|||||||
"@emotion/styled": "^11.13.0",
|
"@emotion/styled": "^11.13.0",
|
||||||
"@hello-pangea/dnd": "^18.0.0",
|
"@hello-pangea/dnd": "^18.0.0",
|
||||||
"@hookform/resolvers": "5.2.2",
|
"@hookform/resolvers": "5.2.2",
|
||||||
"@mui/icons-material": "6.4.11",
|
|
||||||
"@mui/lab": "6.0.0-dev.240424162023-9968b4889d",
|
"@mui/lab": "6.0.0-dev.240424162023-9968b4889d",
|
||||||
"@mui/material": "6.4.11",
|
"@mui/material": "6.4.11",
|
||||||
"@mui/x-charts": "^7.5.1",
|
"@mui/x-charts": "^7.5.1",
|
||||||
@@ -30,8 +29,9 @@
|
|||||||
"human-interval": "2.0.1",
|
"human-interval": "2.0.1",
|
||||||
"i18next": "25.4.2",
|
"i18next": "25.4.2",
|
||||||
"joi": "17.13.3",
|
"joi": "17.13.3",
|
||||||
|
"lucide-react": "^0.562.0",
|
||||||
"mui-color-input": "^6.0.0",
|
"mui-color-input": "^6.0.0",
|
||||||
"pretty-ms": "9.3.0",
|
"pretty-ms": "^9.3.0",
|
||||||
"react": "18.3.1",
|
"react": "18.3.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hook-form": "7.63.0",
|
"react-hook-form": "7.63.0",
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
"react-toastify": "^10.0.5",
|
"react-toastify": "^10.0.5",
|
||||||
"recharts": "2.15.2",
|
"recharts": "2.15.2",
|
||||||
"redux-persist": "6.0.0",
|
"redux-persist": "6.0.0",
|
||||||
"swr": "2.3.6",
|
"swr": "^2.3.6",
|
||||||
"vite-plugin-svgr": "^4.2.0",
|
"vite-plugin-svgr": "^4.2.0",
|
||||||
"zod": "4.1.11"
|
"zod": "4.1.11"
|
||||||
},
|
},
|
||||||
|
|||||||
+18
-2
@@ -22,13 +22,29 @@ function App() {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const theme = mode === "light" ? lightTheme : darkTheme;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={mode === "light" ? lightTheme : darkTheme}>
|
<ThemeProvider theme={theme}>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
|
<GlobalStyles
|
||||||
|
styles={{
|
||||||
|
body: {
|
||||||
|
backgroundColor: theme.palette.background.main,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<AppLayout>
|
<AppLayout>
|
||||||
<Routes />
|
<Routes />
|
||||||
</AppLayout>
|
</AppLayout>
|
||||||
<ToastContainer />
|
<ToastContainer
|
||||||
|
newestOnTop={true}
|
||||||
|
theme={mode}
|
||||||
|
style={{
|
||||||
|
"--toastify-color-progress-light": "#7C8BA1",
|
||||||
|
"--toastify-color-progress-dark": "#7C8BA1",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const langMap = {
|
|||||||
const LanguageSelector = () => {
|
const LanguageSelector = () => {
|
||||||
const { i18n } = useTranslation();
|
const { i18n } = useTranslation();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { language } = useSelector((state) => state.ui);
|
const { language = "en" } = useSelector((state) => state.ui);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const handleChange = (event) => {
|
const handleChange = (event) => {
|
||||||
const newLang = event.target.value;
|
const newLang = event.target.value;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import IconButton from "@mui/material/IconButton";
|
import IconButton from "@mui/material/IconButton";
|
||||||
import Menu from "@mui/material/Menu";
|
import Menu from "@mui/material/Menu";
|
||||||
import MenuItem from "@mui/material/MenuItem";
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
import Settings from "../../../assets/icons/settings-bold.svg?react";
|
import Icon from "../Icon";
|
||||||
import Dialog from "../Dialog/index.jsx";
|
import Dialog from "../Dialog/index.jsx";
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
@@ -12,7 +12,7 @@ import { useNavigate } from "react-router-dom";
|
|||||||
import { createToast } from "../../../Utils/toastUtils.jsx";
|
import { createToast } from "../../../Utils/toastUtils.jsx";
|
||||||
|
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { usePauseMonitor, useDeleteMonitor } from "../../../Hooks/v1/monitorHooks.js";
|
import { usePauseMonitor, useDeleteMonitor } from "../../../Hooks/monitorHooks.js";
|
||||||
|
|
||||||
const ActionsMenu = ({
|
const ActionsMenu = ({
|
||||||
monitor,
|
monitor,
|
||||||
@@ -31,7 +31,7 @@ const ActionsMenu = ({
|
|||||||
const handleRemove = async (event) => {
|
const handleRemove = async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
let monitor = { _id: actions.id };
|
let monitor = { id: actions.id };
|
||||||
await deleteMonitor({ monitor });
|
await deleteMonitor({ monitor });
|
||||||
updateRowCallback();
|
updateRowCallback();
|
||||||
};
|
};
|
||||||
@@ -39,7 +39,7 @@ const ActionsMenu = ({
|
|||||||
const handlePause = async () => {
|
const handlePause = async () => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
await pauseMonitor({ monitorId: monitor._id });
|
await pauseMonitor({ monitorId: monitor.id });
|
||||||
pauseCallback();
|
pauseCallback();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
createToast({ body: "Failed to pause monitor." });
|
createToast({ body: "Failed to pause monitor." });
|
||||||
@@ -72,7 +72,7 @@ const ActionsMenu = ({
|
|||||||
aria-label="monitor actions"
|
aria-label="monitor actions"
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
openMenu(event, monitor._id, monitor.type === "ping" ? null : monitor.url);
|
openMenu(event, monitor.id, monitor.type === "ping" ? null : monitor.url);
|
||||||
}}
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
"&:focus": {
|
"&:focus": {
|
||||||
@@ -83,7 +83,10 @@ const ActionsMenu = ({
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Settings />
|
<Icon
|
||||||
|
name="Settings"
|
||||||
|
size={20}
|
||||||
|
/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
<Menu
|
<Menu
|
||||||
@@ -197,6 +200,7 @@ const ActionsMenu = ({
|
|||||||
confirmationButtonLabel="Delete"
|
confirmationButtonLabel="Delete"
|
||||||
/* Do we need stop propagation? */
|
/* Do we need stop propagation? */
|
||||||
onConfirm={(e) => {
|
onConfirm={(e) => {
|
||||||
|
console.log(e);
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleRemove(e);
|
handleRemove(e);
|
||||||
}}
|
}}
|
||||||
@@ -210,7 +214,7 @@ const ActionsMenu = ({
|
|||||||
|
|
||||||
ActionsMenu.propTypes = {
|
ActionsMenu.propTypes = {
|
||||||
monitor: PropTypes.shape({
|
monitor: PropTypes.shape({
|
||||||
_id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
url: PropTypes.string,
|
url: PropTypes.string,
|
||||||
type: PropTypes.string,
|
type: PropTypes.string,
|
||||||
isActive: PropTypes.bool,
|
isActive: PropTypes.bool,
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import { Box, Button, IconButton, Stack, Typography } from "@mui/material";
|
import { Box, Button, IconButton, Stack, Typography } from "@mui/material";
|
||||||
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
|
import Icon from "../Icon";
|
||||||
import ErrorOutlineOutlinedIcon from "@mui/icons-material/ErrorOutlineOutlined";
|
|
||||||
import WarningAmberOutlinedIcon from "@mui/icons-material/WarningAmberOutlined";
|
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -13,9 +10,24 @@ import "./index.css";
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const icons = {
|
const icons = {
|
||||||
info: <InfoOutlinedIcon />,
|
info: (
|
||||||
error: <ErrorOutlineOutlinedIcon />,
|
<Icon
|
||||||
warning: <WarningAmberOutlinedIcon />,
|
name="Info"
|
||||||
|
size={24}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
error: (
|
||||||
|
<Icon
|
||||||
|
name="AlertCircle"
|
||||||
|
size={24}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
warning: (
|
||||||
|
<Icon
|
||||||
|
name="AlertTriangle"
|
||||||
|
size={24}
|
||||||
|
/>
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -97,10 +109,9 @@ const Alert = ({ variant, title, body, isToast, hasIcon = true, onClick }) => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CloseIcon
|
<Icon
|
||||||
sx={{
|
name="X"
|
||||||
fontSize: "20px",
|
size={20}
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,27 +1,28 @@
|
|||||||
import LeftArrow from "../../../assets/icons/left-arrow.svg?react";
|
import Icon from "../Icon";
|
||||||
import LeftArrowDouble from "../../../assets/icons/left-arrow-double.svg?react";
|
|
||||||
import LeftArrowLong from "../../../assets/icons/left-arrow-long.svg?react";
|
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
const ArrowLeft = ({ type, color = "#667085", ...props }) => {
|
const ArrowLeft = ({ type, color = "#667085", ...props }) => {
|
||||||
if (type === "double") {
|
if (type === "double") {
|
||||||
return (
|
return (
|
||||||
<LeftArrowDouble
|
<Icon
|
||||||
style={{ color }}
|
name="ChevronsLeft"
|
||||||
|
color={color}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (type === "long") {
|
} else if (type === "long") {
|
||||||
return (
|
return (
|
||||||
<LeftArrowLong
|
<Icon
|
||||||
style={{ color }}
|
name="ArrowLeft"
|
||||||
|
color={color}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<LeftArrow
|
<Icon
|
||||||
style={{ color }}
|
name="ChevronLeft"
|
||||||
|
color={color}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
import RightArrow from "../../../assets/icons/right-arrow.svg?react";
|
import Icon from "../Icon";
|
||||||
import RightArrowDouble from "../../../assets/icons/right-arrow-double.svg?react";
|
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
const ArrowRight = ({ type, color = "#667085", ...props }) => {
|
const ArrowRight = ({ type, color = "#667085", ...props }) => {
|
||||||
if (type === "double") {
|
if (type === "double") {
|
||||||
return (
|
return (
|
||||||
<RightArrowDouble
|
<Icon
|
||||||
style={{ color }}
|
name="ChevronsRight"
|
||||||
|
color={color}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<RightArrow
|
<Icon
|
||||||
style={{ color }}
|
name="ChevronRight"
|
||||||
|
color={color}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import { Box, Stack, Tooltip, Typography } from "@mui/material";
|
import { Box, Stack, Tooltip, Typography } from "@mui/material";
|
||||||
import { formatDateWithTz } from "../../../../Utils/timeUtils.js";
|
import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
@@ -103,7 +103,7 @@ const BarChart = ({ checks = [] }) => {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
placement="top"
|
placement="top"
|
||||||
key={`check-${check?._id}`}
|
key={`check-${check?.id}`}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
popper: {
|
popper: {
|
||||||
className: "bar-tooltip",
|
className: "bar-tooltip",
|
||||||
@@ -145,7 +145,7 @@ const BarChart = ({ checks = [] }) => {
|
|||||||
position="relative"
|
position="relative"
|
||||||
width="9px"
|
width="9px"
|
||||||
height="100%"
|
height="100%"
|
||||||
backgroundColor={theme.palette.primary.lowContrast} // CAIO_REVIEW
|
backgroundColor={theme.palette.primary.lowContrast}
|
||||||
sx={{
|
sx={{
|
||||||
borderRadius: theme.spacing(1.5),
|
borderRadius: theme.spacing(1.5),
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Stack, Box, Tooltip, Typography } from "@mui/material";
|
|||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { formatDateWithTz } from "../../../../Utils/timeUtils.js";
|
import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
|
|
||||||
const PlaceholderCheck = ({ daysToShow }) => {
|
const PlaceholderCheck = ({ daysToShow }) => {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { Box, Stack, Typography } from "@mui/material";
|
|||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { formatDateWithTz } from "../../../../Utils/timeUtils.js";
|
import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js";
|
||||||
import {
|
import {
|
||||||
tooltipDateFormatLookup,
|
tooltipDateFormatLookup,
|
||||||
tickDateFormatLookup,
|
tickDateFormatLookup,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import { Box, Stack, Tooltip, Typography } from "@mui/material";
|
import { Box, Stack, Tooltip, Typography } from "@mui/material";
|
||||||
import { formatDateWithTz } from "../../../../Utils/timeUtils.js";
|
import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js";
|
||||||
import { useEffect, useState, forwardRef } from "react";
|
import { useEffect, useState, forwardRef } from "react";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
@@ -148,7 +148,7 @@ const StatusPageBarChart = ({ checks = [] }) => {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
placement="top"
|
placement="top"
|
||||||
key={`check-${check?._id}`}
|
key={`check-${check?.id}`}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
popper: {
|
popper: {
|
||||||
className: "bar-tooltip",
|
className: "bar-tooltip",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import PropTypes from "prop-types";
|
|||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { useTheme } from "@mui/material";
|
import { useTheme } from "@mui/material";
|
||||||
import { Text } from "recharts";
|
import { Text } from "recharts";
|
||||||
import { formatDateWithTz } from "../../../../Utils/timeUtils.js";
|
import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js";
|
||||||
import { Box, Stack, Typography } from "@mui/material";
|
import { Box, Stack, Typography } from "@mui/material";
|
||||||
import { tickDateFormatLookup, tooltipDateFormatLookup } from "./chartUtilFunctions.js";
|
import { tickDateFormatLookup, tooltipDateFormatLookup } from "./chartUtilFunctions.js";
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import "./check.css";
|
import "./check.css";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import CheckGrey from "../../../assets/icons/check.svg?react";
|
import Icon from "../Icon";
|
||||||
import CheckOutlined from "../../../assets/icons/check-outlined.svg?react";
|
|
||||||
import { Box, Stack, Typography } from "@mui/material";
|
import { Box, Stack, Typography } from "@mui/material";
|
||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
|
|
||||||
@@ -35,16 +34,16 @@ const Check = ({ text, noHighlightText, variant = "info", outlined = false }) =>
|
|||||||
alignItems="center"
|
alignItems="center"
|
||||||
>
|
>
|
||||||
{outlined ? (
|
{outlined ? (
|
||||||
<CheckOutlined alt="check" />
|
<Icon
|
||||||
|
name="CheckCircle"
|
||||||
|
size={20}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Box
|
<Icon
|
||||||
lineHeight={0}
|
name="Check"
|
||||||
sx={{
|
size={16}
|
||||||
"& svg > path": { fill: colors[variant] },
|
color={colors[variant]}
|
||||||
}}
|
/>
|
||||||
>
|
|
||||||
<CheckGrey alt="form checks" />
|
|
||||||
</Box>
|
|
||||||
)}
|
)}
|
||||||
<Typography
|
<Typography
|
||||||
component="span"
|
component="span"
|
||||||
@@ -57,7 +56,14 @@ const Check = ({ text, noHighlightText, variant = "info", outlined = false }) =>
|
|||||||
fontWeight: 450,
|
fontWeight: 450,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{noHighlightText && <Typography component="span">{noHighlightText}</Typography>}{" "}
|
{noHighlightText && (
|
||||||
|
<Typography
|
||||||
|
component="span"
|
||||||
|
sx={{ color: "inherit" }}
|
||||||
|
>
|
||||||
|
{noHighlightText}
|
||||||
|
</Typography>
|
||||||
|
)}{" "}
|
||||||
{text}
|
{text}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ import IconButton from "@mui/material/IconButton";
|
|||||||
import Container from "@mui/material/Container";
|
import Container from "@mui/material/Container";
|
||||||
import MenuItem from "@mui/material/MenuItem";
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
import Drawer from "@mui/material/Drawer";
|
import Drawer from "@mui/material/Drawer";
|
||||||
import MenuIcon from "@mui/icons-material/Menu";
|
import Icon from "../Icon";
|
||||||
import CloseRoundedIcon from "@mui/icons-material/CloseRounded";
|
|
||||||
import ThemeSwitch from "../ThemeSwitch/index.jsx";
|
import ThemeSwitch from "../ThemeSwitch/index.jsx";
|
||||||
import { useTheme } from "@mui/material/styles";
|
import { useTheme } from "@mui/material/styles";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
@@ -153,7 +152,11 @@ const AppAppBar = () => {
|
|||||||
aria-label="Menu button"
|
aria-label="Menu button"
|
||||||
onClick={toggleDrawer(true)}
|
onClick={toggleDrawer(true)}
|
||||||
>
|
>
|
||||||
<MenuIcon sx={{ color: theme.palette.text.primary }} />
|
<Icon
|
||||||
|
name="Menu"
|
||||||
|
size={24}
|
||||||
|
color={theme.palette.text.primary}
|
||||||
|
/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Drawer
|
<Drawer
|
||||||
anchor="top"
|
anchor="top"
|
||||||
@@ -176,7 +179,11 @@ const AppAppBar = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconButton onClick={toggleDrawer(false)}>
|
<IconButton onClick={toggleDrawer(false)}>
|
||||||
<CloseRoundedIcon sx={{ color: theme.palette.text.primary }} />
|
<Icon
|
||||||
|
name="X"
|
||||||
|
size={24}
|
||||||
|
color={theme.palette.text.primary}
|
||||||
|
/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Checkbox, FormControl, ListItemText, MenuItem, Select } from "@mui/material";
|
import { Checkbox, FormControl, ListItemText, MenuItem, Select } from "@mui/material";
|
||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import AddCircleOutlineIcon from "@mui/icons-material/AddCircleOutline";
|
import Icon from "../Icon";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A reusable filter header component that displays a dropdown menu with selectable options.
|
* A reusable filter header component that displays a dropdown menu with selectable options.
|
||||||
@@ -29,9 +29,10 @@ const FilterHeader = ({ header, options, value, onChange, multiple = true }) =>
|
|||||||
<Select
|
<Select
|
||||||
multiple={multiple}
|
multiple={multiple}
|
||||||
IconComponent={(props) => (
|
IconComponent={(props) => (
|
||||||
<AddCircleOutlineIcon
|
<Icon
|
||||||
{...props}
|
{...props}
|
||||||
sx={{ fontSize: "medium" }}
|
name="PlusCircle"
|
||||||
|
size={18}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
displayEmpty
|
displayEmpty
|
||||||
|
|||||||
@@ -40,13 +40,13 @@ const GenericFallback = ({ children }) => {
|
|||||||
<Box
|
<Box
|
||||||
component="img"
|
component="img"
|
||||||
src={mode === "light" ? OutputAnimation : DarkmodeOutput}
|
src={mode === "light" ? OutputAnimation : DarkmodeOutput}
|
||||||
Background="transparent"
|
|
||||||
alt="Loading animation"
|
alt="Loading animation"
|
||||||
sx={{
|
sx={{
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
border: "none",
|
border: "none",
|
||||||
borderRadius: theme.spacing(8),
|
borderRadius: theme.spacing(8),
|
||||||
width: "100%",
|
width: "100%",
|
||||||
|
backgroundColor: "transparent",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Box
|
<Box
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Stack, Typography } from "@mui/material";
|
|||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import Dot from "../Dot/index.jsx";
|
import Dot from "../Dot/index.jsx";
|
||||||
|
import { StatusLabel } from "@/Components/v1/Label/index.jsx";
|
||||||
/**
|
/**
|
||||||
* Host component.
|
* Host component.
|
||||||
* This subcomponent receives a params object and displays the host details.
|
* This subcomponent receives a params object and displays the host details.
|
||||||
@@ -14,7 +15,7 @@ import Dot from "../Dot/index.jsx";
|
|||||||
* @param {number} params.percentage - The percentage to display.
|
* @param {number} params.percentage - The percentage to display.
|
||||||
* @returns {React.ElementType} Returns a div element with the host details.
|
* @returns {React.ElementType} Returns a div element with the host details.
|
||||||
*/
|
*/
|
||||||
const Host = ({ url, title, percentageColor, percentage, showURL }) => {
|
const Host = ({ url, title, percentageColor, percentage, showURL, status }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
@@ -24,19 +25,34 @@ const Host = ({ url, title, percentageColor, percentage, showURL }) => {
|
|||||||
alignItems="center"
|
alignItems="center"
|
||||||
gap={theme.spacing(4)}
|
gap={theme.spacing(4)}
|
||||||
>
|
>
|
||||||
{title}
|
<Typography
|
||||||
|
component="span"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 500,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
{percentageColor && percentage && (
|
{percentageColor && percentage && (
|
||||||
<>
|
<>
|
||||||
<Dot />
|
<Dot />
|
||||||
<Typography
|
<Typography
|
||||||
component="span"
|
component="span"
|
||||||
sx={{
|
sx={{
|
||||||
color: percentageColor,
|
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
|
color: percentageColor,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{percentage}%
|
{percentage}%
|
||||||
</Typography>
|
</Typography>
|
||||||
|
{status && (
|
||||||
|
<StatusLabel
|
||||||
|
status={status}
|
||||||
|
text={status}
|
||||||
|
customStyles={{ textTransform: "capitalize" }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -51,6 +67,7 @@ Host.propTypes = {
|
|||||||
percentage: PropTypes.string,
|
percentage: PropTypes.string,
|
||||||
url: PropTypes.string,
|
url: PropTypes.string,
|
||||||
showURL: PropTypes.bool,
|
showURL: PropTypes.bool,
|
||||||
|
status: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Host;
|
export default Host;
|
||||||
@@ -2,7 +2,7 @@ import i18n from "../../../Utils/i18n.js";
|
|||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
const I18nLoader = () => {
|
const I18nLoader = () => {
|
||||||
const language = useSelector((state) => state.ui.language);
|
const language = useSelector((state) => state.ui.language ?? "en");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (language && i18n.language !== language) {
|
if (language && i18n.language !== language) {
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
/**
|
||||||
|
* Maps legacy icon names to Lucide icon names
|
||||||
|
* Use: import { iconMap } from './iconMap'; LucideIcons[iconMap['monitors']]
|
||||||
|
*/
|
||||||
|
export const iconMap = {
|
||||||
|
// Navigation/Sidebar icons
|
||||||
|
monitors: "Globe",
|
||||||
|
incidents: "AlertTriangle",
|
||||||
|
integrations: "Link",
|
||||||
|
"page-speed": "Gauge",
|
||||||
|
settings: "Settings",
|
||||||
|
notifications: "Bell",
|
||||||
|
maintenance: "Wrench",
|
||||||
|
"status-pages": "Wifi",
|
||||||
|
docs: "FileText",
|
||||||
|
discussions: "MessageCircle",
|
||||||
|
support: "HelpCircle",
|
||||||
|
changeLog: "Code",
|
||||||
|
logs: "Database",
|
||||||
|
|
||||||
|
// User icons
|
||||||
|
user: "User",
|
||||||
|
"user-two": "Users",
|
||||||
|
"user-edit": "UserPen",
|
||||||
|
groups: "Users",
|
||||||
|
lock: "Lock",
|
||||||
|
key: "Key",
|
||||||
|
logout: "LogOut",
|
||||||
|
|
||||||
|
// Action icons
|
||||||
|
edit: "Pencil",
|
||||||
|
"trash-bin": "Trash2",
|
||||||
|
search: "Search",
|
||||||
|
"close-icon": "X",
|
||||||
|
check: "Check",
|
||||||
|
"check-icon": "CheckCircle",
|
||||||
|
"check-outlined": "CheckCircle",
|
||||||
|
|
||||||
|
// Arrow icons
|
||||||
|
"left-arrow": "ChevronLeft",
|
||||||
|
"right-arrow": "ChevronRight",
|
||||||
|
"up-arrow": "ChevronUp",
|
||||||
|
"down-arrow": "ChevronDown",
|
||||||
|
"left-arrow-double": "ChevronsLeft",
|
||||||
|
"right-arrow-double": "ChevronsRight",
|
||||||
|
"left-arrow-long": "ArrowLeft",
|
||||||
|
"top-right-arrow": "ArrowUpRight",
|
||||||
|
"open-in-new-page": "ExternalLink",
|
||||||
|
"selector-vertical": "ChevronsUpDown",
|
||||||
|
|
||||||
|
// Status/Alert icons
|
||||||
|
"alert-icon": "Bell",
|
||||||
|
"warning-icon": "AlertCircle",
|
||||||
|
"history-icon": "History",
|
||||||
|
|
||||||
|
// Data/Chart icons
|
||||||
|
dashboard: "LayoutGrid",
|
||||||
|
"monitor-graph-line": "TrendingUp",
|
||||||
|
"response-time-icon": "TrendingUp",
|
||||||
|
"uptime-icon": "Activity",
|
||||||
|
"speedometer-icon": "Gauge",
|
||||||
|
"spedometer-icon": "Gauge",
|
||||||
|
"ruler-icon": "Ruler",
|
||||||
|
"performance-report": "Layers",
|
||||||
|
"average-response-icon": "BarChart3",
|
||||||
|
"distributed-uptime": "Network",
|
||||||
|
|
||||||
|
// Misc icons
|
||||||
|
calendar: "Calendar",
|
||||||
|
"calendar-check": "CalendarCheck",
|
||||||
|
"clock-snooze": "Clock",
|
||||||
|
mail: "Mail",
|
||||||
|
email: "Mail",
|
||||||
|
folder: "Folder",
|
||||||
|
"dots-vertical": "MoreVertical",
|
||||||
|
"pause-icon": "PauseCircle",
|
||||||
|
"resume-icon": "PlayCircle",
|
||||||
|
"checkbox-filled": "CheckSquare",
|
||||||
|
"checkbox-outline": "Square",
|
||||||
|
"radio-checked": "Circle",
|
||||||
|
"cpu-chip": "Cpu",
|
||||||
|
certificate: "Award",
|
||||||
|
"interval-check": "RefreshCw",
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MUI icon to Lucide mapping
|
||||||
|
*/
|
||||||
|
export const muiToLucideMap = {
|
||||||
|
AddCircleOutline: "PlusCircle",
|
||||||
|
ArrowDownwardRounded: "ArrowDown",
|
||||||
|
ArrowUpwardRounded: "ArrowUp",
|
||||||
|
ArrowDropDown: "ChevronDown",
|
||||||
|
ArrowOutward: "ExternalLink",
|
||||||
|
CheckCircle: "CheckCircle2",
|
||||||
|
Clear: "X",
|
||||||
|
Close: "X",
|
||||||
|
CloseRounded: "X",
|
||||||
|
CloudUpload: "Upload",
|
||||||
|
Delete: "Trash2",
|
||||||
|
DeleteOutlineRounded: "Trash2",
|
||||||
|
Email: "Mail",
|
||||||
|
ErrorOutline: "AlertTriangle",
|
||||||
|
ErrorOutlineOutlined: "AlertTriangle",
|
||||||
|
Image: "ImageIcon",
|
||||||
|
InfoOutlined: "Info",
|
||||||
|
KeyboardArrowDown: "ChevronDown",
|
||||||
|
Menu: "Menu",
|
||||||
|
PauseCircleOutline: "PauseCircle",
|
||||||
|
PauseOutlined: "Pause",
|
||||||
|
PlayArrowOutlined: "Play",
|
||||||
|
PlayCircleOutlineRounded: "PlayCircle",
|
||||||
|
RadioButtonChecked: "Circle",
|
||||||
|
ReorderRounded: "GripVertical",
|
||||||
|
SettingsOutlined: "Settings",
|
||||||
|
Share: "Share2",
|
||||||
|
Visibility: "Eye",
|
||||||
|
VisibilityOff: "EyeOff",
|
||||||
|
WarningAmberOutlined: "AlertTriangle",
|
||||||
|
};
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
import { useTheme } from "@mui/material";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import {
|
||||||
|
Activity,
|
||||||
|
AlertCircle,
|
||||||
|
AlertTriangle,
|
||||||
|
ArrowDown,
|
||||||
|
ArrowLeft,
|
||||||
|
ArrowUp,
|
||||||
|
ArrowUpRight,
|
||||||
|
Bell,
|
||||||
|
Calendar,
|
||||||
|
Check,
|
||||||
|
CheckCircle,
|
||||||
|
CheckCircle2,
|
||||||
|
CheckSquare,
|
||||||
|
ChevronDown,
|
||||||
|
ChevronLeft,
|
||||||
|
ChevronRight,
|
||||||
|
ChevronsLeft,
|
||||||
|
ChevronsRight,
|
||||||
|
ChevronsUpDown,
|
||||||
|
Circle,
|
||||||
|
CircleDot,
|
||||||
|
Clock,
|
||||||
|
Code,
|
||||||
|
Cpu,
|
||||||
|
Database,
|
||||||
|
ExternalLink,
|
||||||
|
Eye,
|
||||||
|
EyeOff,
|
||||||
|
FileText,
|
||||||
|
Gauge,
|
||||||
|
Globe,
|
||||||
|
GripVertical,
|
||||||
|
HelpCircle,
|
||||||
|
History,
|
||||||
|
Image as ImageIcon,
|
||||||
|
Info,
|
||||||
|
Key,
|
||||||
|
Layers,
|
||||||
|
Link,
|
||||||
|
Lock,
|
||||||
|
LogOut,
|
||||||
|
Mail,
|
||||||
|
Menu,
|
||||||
|
MessageCircle,
|
||||||
|
MoreVertical,
|
||||||
|
Pause,
|
||||||
|
PauseCircle,
|
||||||
|
Play,
|
||||||
|
PlayCircle,
|
||||||
|
PlusCircle,
|
||||||
|
RefreshCw,
|
||||||
|
Ruler,
|
||||||
|
Search,
|
||||||
|
Settings,
|
||||||
|
Square,
|
||||||
|
Trash2,
|
||||||
|
TrendingUp,
|
||||||
|
Upload,
|
||||||
|
User,
|
||||||
|
Users,
|
||||||
|
Wifi,
|
||||||
|
Wrench,
|
||||||
|
X,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map of icon names to Lucide icon components.
|
||||||
|
* Only icons explicitly imported here will be included in the bundle (tree-shaking).
|
||||||
|
* To add a new icon: import it above and add it to this object.
|
||||||
|
*/
|
||||||
|
const iconComponents = {
|
||||||
|
Activity,
|
||||||
|
AlertCircle,
|
||||||
|
AlertTriangle,
|
||||||
|
ArrowDown,
|
||||||
|
ArrowLeft,
|
||||||
|
ArrowUp,
|
||||||
|
ArrowUpRight,
|
||||||
|
Bell,
|
||||||
|
Calendar,
|
||||||
|
Check,
|
||||||
|
CheckCircle,
|
||||||
|
CheckCircle2,
|
||||||
|
CheckSquare,
|
||||||
|
ChevronDown,
|
||||||
|
ChevronLeft,
|
||||||
|
ChevronRight,
|
||||||
|
ChevronsLeft,
|
||||||
|
ChevronsRight,
|
||||||
|
ChevronsUpDown,
|
||||||
|
Circle,
|
||||||
|
CircleDot,
|
||||||
|
Clock,
|
||||||
|
Code,
|
||||||
|
Cpu,
|
||||||
|
Database,
|
||||||
|
ExternalLink,
|
||||||
|
Eye,
|
||||||
|
EyeOff,
|
||||||
|
FileText,
|
||||||
|
Gauge,
|
||||||
|
Globe,
|
||||||
|
GripVertical,
|
||||||
|
HelpCircle,
|
||||||
|
History,
|
||||||
|
Image: ImageIcon,
|
||||||
|
Info,
|
||||||
|
Key,
|
||||||
|
Layers,
|
||||||
|
Link,
|
||||||
|
Lock,
|
||||||
|
LogOut,
|
||||||
|
Mail,
|
||||||
|
Menu,
|
||||||
|
MessageCircle,
|
||||||
|
MoreVertical,
|
||||||
|
Pause,
|
||||||
|
PauseCircle,
|
||||||
|
Play,
|
||||||
|
PlayCircle,
|
||||||
|
PlusCircle,
|
||||||
|
RefreshCw,
|
||||||
|
Ruler,
|
||||||
|
Search,
|
||||||
|
Settings,
|
||||||
|
Square,
|
||||||
|
Trash2,
|
||||||
|
TrendingUp,
|
||||||
|
Upload,
|
||||||
|
User,
|
||||||
|
Users,
|
||||||
|
Wifi,
|
||||||
|
Wrench,
|
||||||
|
X,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Theme-aware icon component wrapping Lucide icons
|
||||||
|
*
|
||||||
|
* @param {string} name - Lucide icon name (e.g., "Bell", "Check", "ArrowLeft")
|
||||||
|
* @param {number} size - Icon size in pixels (default: 20)
|
||||||
|
* @param {string} color - Direct color value OR theme path like "primary.contrastText"
|
||||||
|
* @param {number} strokeWidth - Stroke width (default: 1.5)
|
||||||
|
* @param {string} fill - Fill color (default: "none")
|
||||||
|
*/
|
||||||
|
const Icon = ({ name, size = 20, color, strokeWidth = 1.5, fill = "none", ...props }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
// Resolve color from theme path or use direct value
|
||||||
|
const resolveColor = (colorValue) => {
|
||||||
|
if (!colorValue) {
|
||||||
|
return theme.palette.primary.contrastTextTertiary; // Default icon color
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's a theme path like "primary.contrastText"
|
||||||
|
if (typeof colorValue === "string" && colorValue.includes(".")) {
|
||||||
|
const parts = colorValue.split(".");
|
||||||
|
let resolved = theme.palette;
|
||||||
|
for (const part of parts) {
|
||||||
|
resolved = resolved?.[part];
|
||||||
|
}
|
||||||
|
return resolved || colorValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Direct color value
|
||||||
|
return colorValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
const LucideIcon = iconComponents[name];
|
||||||
|
|
||||||
|
if (!LucideIcon) {
|
||||||
|
console.warn(`Icon "${name}" not found in Icon component`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LucideIcon
|
||||||
|
size={size}
|
||||||
|
color={resolveColor(color)}
|
||||||
|
strokeWidth={strokeWidth}
|
||||||
|
fill={fill}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Icon.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
size: PropTypes.number,
|
||||||
|
color: PropTypes.string,
|
||||||
|
strokeWidth: PropTypes.number,
|
||||||
|
fill: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Icon;
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { FormControlLabel, Checkbox as MuiCheckbox } from "@mui/material";
|
import { FormControlLabel, Checkbox as MuiCheckbox } from "@mui/material";
|
||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import CheckboxOutline from "../../../../assets/icons/checkbox-outline.svg?react";
|
import Icon from "../../Icon";
|
||||||
import CheckboxFilled from "../../../../assets/icons/checkbox-filled.svg?react";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checkbox Component
|
* Checkbox Component
|
||||||
@@ -65,8 +64,8 @@ const Checkbox = ({
|
|||||||
name={name}
|
name={name}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
icon={<CheckboxOutline />}
|
icon={<Icon name="Square" />}
|
||||||
checkedIcon={<CheckboxFilled />}
|
checkedIcon={<Icon name="CheckSquare" />}
|
||||||
inputProps={{
|
inputProps={{
|
||||||
"aria-label": "controlled checkbox",
|
"aria-label": "controlled checkbox",
|
||||||
id: id,
|
id: id,
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
// Components
|
// Components
|
||||||
import { Box, Stack, Typography } from "@mui/material";
|
import { Box, Stack, Typography } from "@mui/material";
|
||||||
import CloudUploadIcon from "@mui/icons-material/CloudUpload";
|
import Icon from "../../Icon";
|
||||||
import Image from "../../Image/index.jsx";
|
import Image from "../../Image/index.jsx";
|
||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
import IconButton from "@mui/material/IconButton";
|
import IconButton from "@mui/material/IconButton";
|
||||||
import ProgressUpload from "../../ProgressBars/index.jsx";
|
import ProgressUpload from "../../ProgressBars/index.jsx";
|
||||||
import ImageIcon from "@mui/icons-material/Image";
|
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
@@ -176,7 +175,10 @@ const ImageUpload = ({
|
|||||||
boxShadow: theme.shape.boxShadow,
|
boxShadow: theme.shape.boxShadow,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CloudUploadIcon />
|
<Icon
|
||||||
|
name="Upload"
|
||||||
|
size={24}
|
||||||
|
/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Typography
|
<Typography
|
||||||
component="h2"
|
component="h2"
|
||||||
@@ -203,7 +205,12 @@ const ImageUpload = ({
|
|||||||
</Box>
|
</Box>
|
||||||
{(localError || progress.isLoading || progress.value !== 0) && (
|
{(localError || progress.isLoading || progress.value !== 0) && (
|
||||||
<ProgressUpload
|
<ProgressUpload
|
||||||
icon={<ImageIcon />}
|
icon={
|
||||||
|
<Icon
|
||||||
|
name="Image"
|
||||||
|
size={20}
|
||||||
|
/>
|
||||||
|
}
|
||||||
label={file?.name || "Upload failed"}
|
label={file?.name || "Upload failed"}
|
||||||
size={file?.size}
|
size={file?.size}
|
||||||
progress={progress.value}
|
progress={progress.value}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { FormControlLabel, Radio as MUIRadio, Typography } from "@mui/material";
|
import { FormControlLabel, Radio as MUIRadio, Typography } from "@mui/material";
|
||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import RadioChecked from "../../../../assets/icons/radio-checked.svg?react";
|
import Icon from "../../Icon";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,7 +47,12 @@ const Radio = ({
|
|||||||
<MUIRadio
|
<MUIRadio
|
||||||
id={id}
|
id={id}
|
||||||
size={size}
|
size={size}
|
||||||
checkedIcon={<RadioChecked />}
|
checkedIcon={
|
||||||
|
<Icon
|
||||||
|
name="CircleDot"
|
||||||
|
size={16}
|
||||||
|
/>
|
||||||
|
}
|
||||||
sx={{
|
sx={{
|
||||||
color: "transparent",
|
color: "transparent",
|
||||||
width: 16,
|
width: 16,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
Checkbox,
|
Checkbox,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import SearchIcon from "../../../../assets/icons/search.svg?react";
|
import Icon from "../../Icon";
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import FieldWrapper from "../FieldWrapper/index.jsx";
|
import FieldWrapper from "../FieldWrapper/index.jsx";
|
||||||
@@ -31,20 +31,13 @@ import FieldWrapper from "../FieldWrapper/index.jsx";
|
|||||||
const SearchAdornment = () => {
|
const SearchAdornment = () => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box height={16}>
|
||||||
height={16}
|
<Icon
|
||||||
sx={{
|
name="Search"
|
||||||
"& svg": {
|
size={16}
|
||||||
width: 16,
|
color="primary.contrastTextTertiary"
|
||||||
height: 16,
|
strokeWidth={1.2}
|
||||||
"& path": {
|
/>
|
||||||
stroke: theme.palette.primary.contrastTextTertiary,
|
|
||||||
strokeWidth: 1.2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SearchIcon />
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -85,7 +78,12 @@ const Search = ({
|
|||||||
const enhancedOptions = React.useMemo(() => {
|
const enhancedOptions = React.useMemo(() => {
|
||||||
return multiple && isAdorned
|
return multiple && isAdorned
|
||||||
? [
|
? [
|
||||||
{ [filteredBy]: t("selectAll"), isSelectAll: true, _id: "select_all" },
|
{
|
||||||
|
[filteredBy]: t("selectAll"),
|
||||||
|
isSelectAll: true,
|
||||||
|
_id: "select_all",
|
||||||
|
id: "select_all",
|
||||||
|
},
|
||||||
...options,
|
...options,
|
||||||
]
|
]
|
||||||
: options;
|
: options;
|
||||||
@@ -93,7 +91,7 @@ const Search = ({
|
|||||||
const isOptionSelected = (option) => {
|
const isOptionSelected = (option) => {
|
||||||
if (!multiple && !isAdorned) return false;
|
if (!multiple && !isAdorned) return false;
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
return value.some((item) => item._id === option._id);
|
return value.some((item) => (item._id ?? item.id) === (option._id ?? option.id));
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
@@ -145,7 +143,9 @@ const Search = ({
|
|||||||
disableClearable
|
disableClearable
|
||||||
options={enhancedOptions}
|
options={enhancedOptions}
|
||||||
getOptionLabel={(option) => option[filteredBy]}
|
getOptionLabel={(option) => option[filteredBy]}
|
||||||
isOptionEqualToValue={(option, value) => option._id === value._id} // Compare by unique identifier
|
isOptionEqualToValue={(option, value) =>
|
||||||
|
(option._id ?? option.id) === (value?._id ?? value?.id)
|
||||||
|
} // Compare by unique identifier
|
||||||
renderInput={(params) => (
|
renderInput={(params) => (
|
||||||
<FieldWrapper
|
<FieldWrapper
|
||||||
label={label}
|
label={label}
|
||||||
@@ -206,7 +206,7 @@ const Search = ({
|
|||||||
return filtered;
|
return filtered;
|
||||||
}}
|
}}
|
||||||
getOptionKey={(option) => {
|
getOptionKey={(option) => {
|
||||||
return option._id;
|
return option._id ?? option.id;
|
||||||
}}
|
}}
|
||||||
renderOption={(props, option) => {
|
renderOption={(props, option) => {
|
||||||
const { key, ...optionProps } = props;
|
const { key, ...optionProps } = props;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import { MenuItem, Select as MuiSelect, Stack, Typography } from "@mui/material";
|
import { MenuItem, Select as MuiSelect, Stack, Typography } from "@mui/material";
|
||||||
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
|
import Icon from "../../Icon";
|
||||||
import FieldWrapper from "../FieldWrapper/index.jsx";
|
import FieldWrapper from "../FieldWrapper/index.jsx";
|
||||||
|
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
@@ -62,6 +62,7 @@ const Select = ({
|
|||||||
fieldWrapperSx = {},
|
fieldWrapperSx = {},
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const getItemValue = (item) => item?._id ?? item?.id;
|
||||||
const itemStyles = {
|
const itemStyles = {
|
||||||
fontSize: "var(--env-var-font-size-medium)",
|
fontSize: "var(--env-var-font-size-medium)",
|
||||||
color: theme.palette.primary.contrastTextTertiary,
|
color: theme.palette.primary.contrastTextTertiary,
|
||||||
@@ -97,7 +98,13 @@ const Select = ({
|
|||||||
error={error}
|
error={error}
|
||||||
name={name}
|
name={name}
|
||||||
inputProps={{ id: id }}
|
inputProps={{ id: id }}
|
||||||
IconComponent={KeyboardArrowDownIcon}
|
IconComponent={(props) => (
|
||||||
|
<Icon
|
||||||
|
name="ChevronDown"
|
||||||
|
size={20}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
MenuProps={{ disableScrollLock: true }}
|
MenuProps={{ disableScrollLock: true }}
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
@@ -123,7 +130,7 @@ const Select = ({
|
|||||||
...sx,
|
...sx,
|
||||||
}}
|
}}
|
||||||
renderValue={(selected) => {
|
renderValue={(selected) => {
|
||||||
const selectedItem = items.find((item) => item._id === selected);
|
const selectedItem = items.find((item) => getItemValue(item) === selected);
|
||||||
const displayName = selectedItem ? selectedItem.name : placeholder;
|
const displayName = selectedItem ? selectedItem.name : placeholder;
|
||||||
return (
|
return (
|
||||||
<Typography
|
<Typography
|
||||||
@@ -152,17 +159,25 @@ const Select = ({
|
|||||||
{placeholder}
|
{placeholder}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{items.map((item) => (
|
{items
|
||||||
<MenuItem
|
.map((item) => {
|
||||||
value={item._id}
|
const itemValue = getItemValue(item);
|
||||||
key={`${id}-${item._id}`}
|
if (itemValue === undefined || itemValue === null) {
|
||||||
sx={{
|
return null;
|
||||||
...itemStyles,
|
}
|
||||||
}}
|
return (
|
||||||
>
|
<MenuItem
|
||||||
{item.name}
|
value={itemValue}
|
||||||
</MenuItem>
|
key={`${id}-${itemValue}`}
|
||||||
))}
|
sx={{
|
||||||
|
...itemStyles,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.filter(Boolean)}
|
||||||
</MuiSelect>
|
</MuiSelect>
|
||||||
</FieldWrapper>
|
</FieldWrapper>
|
||||||
);
|
);
|
||||||
@@ -179,8 +194,8 @@ Select.propTypes = {
|
|||||||
.isRequired,
|
.isRequired,
|
||||||
items: PropTypes.arrayOf(
|
items: PropTypes.arrayOf(
|
||||||
PropTypes.shape({
|
PropTypes.shape({
|
||||||
_id: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool])
|
_id: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]),
|
||||||
.isRequired,
|
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]),
|
||||||
|
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import { Stack, Typography, InputAdornment, IconButton } from "@mui/material";
|
import { Stack, Typography, InputAdornment, IconButton } from "@mui/material";
|
||||||
import { useTheme } from "@mui/material/styles";
|
import { useTheme } from "@mui/material/styles";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import VisibilityOff from "@mui/icons-material/VisibilityOff";
|
import Icon from "../../../Icon";
|
||||||
import Visibility from "@mui/icons-material/Visibility";
|
|
||||||
import ReorderRoundedIcon from "@mui/icons-material/ReorderRounded";
|
|
||||||
import DeleteIcon from "../../../../../assets/icons/trash-bin.svg?react";
|
|
||||||
|
|
||||||
export const HttpAdornment = ({ https }) => {
|
export const HttpAdornment = ({ https }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@@ -56,7 +53,17 @@ export const PasswordEndAdornment = ({ fieldType, setFieldType }) => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{fieldType === "password" ? <VisibilityOff /> : <Visibility />}
|
{fieldType === "password" ? (
|
||||||
|
<Icon
|
||||||
|
name="EyeOff"
|
||||||
|
size={20}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Icon
|
||||||
|
name="Eye"
|
||||||
|
size={20}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -147,11 +147,9 @@ const StatusLabel = ({ status, text, customStyles }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
width={7}
|
|
||||||
height={7}
|
|
||||||
bgcolor={theme.palette[themeColor].lowContrast}
|
bgcolor={theme.palette[themeColor].lowContrast}
|
||||||
borderRadius="50%"
|
borderRadius="0%"
|
||||||
marginRight="5px"
|
marginRight="1px"
|
||||||
/>
|
/>
|
||||||
</BaseLabel>
|
</BaseLabel>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
overflow-x: hidden;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO go for this approach for responsiveness. The aside needs to be taken care of */
|
/* TODO go for this approach for responsiveness. The aside needs to be taken care of */
|
||||||
@@ -12,15 +14,7 @@
|
|||||||
}
|
}
|
||||||
} */
|
} */
|
||||||
|
|
||||||
/* .home-layout aside {
|
.home-layout > .home-content-wrapper {
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
height: 100vh;
|
|
||||||
max-width: var(--env-var-side-bar-width);
|
|
||||||
} */
|
|
||||||
|
|
||||||
.home-layout > div {
|
|
||||||
min-height: calc(100vh - var(--env-var-spacing-2) * 2);
|
min-height: calc(100vh - var(--env-var-spacing-2) * 2);
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
@@ -30,4 +24,6 @@
|
|||||||
max-width: 1400px;
|
max-width: 1400px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,27 @@
|
|||||||
import Sidebar from "../../Sidebar/index.jsx";
|
import Sidebar from "../../Sidebar/index.jsx";
|
||||||
import { Outlet } from "react-router";
|
import { Outlet } from "react-router";
|
||||||
import { Stack } from "@mui/material";
|
import { Box, Stack } from "@mui/material";
|
||||||
|
import { useSidebar } from "@/Hooks/useSidebar.js";
|
||||||
|
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
const HomeLayout = () => {
|
const HomeLayout = () => {
|
||||||
|
const { width, transition } = useSidebar();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
className="home-layout"
|
className="home-layout"
|
||||||
flexDirection="row"
|
flexDirection="row"
|
||||||
gap={14}
|
|
||||||
>
|
>
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
|
{/* Spacer for fixed sidebar */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width,
|
||||||
|
flexShrink: 0,
|
||||||
|
transition,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Stack className="home-content-wrapper">
|
<Stack className="home-content-wrapper">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import ButtonGroup from "@mui/material/ButtonGroup";
|
import ButtonGroup from "@mui/material/ButtonGroup";
|
||||||
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
|
import Icon from "../Icon";
|
||||||
import ClickAwayListener from "@mui/material/ClickAwayListener";
|
import ClickAwayListener from "@mui/material/ClickAwayListener";
|
||||||
import Grow from "@mui/material/Grow";
|
import Grow from "@mui/material/Grow";
|
||||||
import Paper from "@mui/material/Paper";
|
import Paper from "@mui/material/Paper";
|
||||||
@@ -11,7 +11,7 @@ import MenuList from "@mui/material/MenuList";
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { createToast } from "../../../Utils/toastUtils.jsx";
|
import { createToast } from "../../../Utils/toastUtils.jsx";
|
||||||
import { useExportMonitors } from "../../../Hooks/v1/monitorHooks.js";
|
import { useExportMonitors } from "../../../Hooks/monitorHooks.js";
|
||||||
|
|
||||||
const MonitorActions = ({ isLoading }) => {
|
const MonitorActions = ({ isLoading }) => {
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
@@ -70,7 +70,10 @@ const MonitorActions = ({ isLoading }) => {
|
|||||||
aria-haspopup="menu"
|
aria-haspopup="menu"
|
||||||
onClick={handleToggle}
|
onClick={handleToggle}
|
||||||
>
|
>
|
||||||
<ArrowDropDownIcon />
|
<Icon
|
||||||
|
name="ChevronDown"
|
||||||
|
size={20}
|
||||||
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
<Popper
|
<Popper
|
||||||
|
|||||||
@@ -3,19 +3,16 @@ import Status from "./status.jsx";
|
|||||||
import Skeleton from "./skeleton.jsx";
|
import Skeleton from "./skeleton.jsx";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import { Tooltip } from "@mui/material";
|
import { Tooltip } from "@mui/material";
|
||||||
import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined";
|
import Icon from "../Icon";
|
||||||
import PauseOutlinedIcon from "@mui/icons-material/PauseOutlined";
|
|
||||||
import PlayArrowOutlinedIcon from "@mui/icons-material/PlayArrowOutlined";
|
|
||||||
import EmailIcon from "@mui/icons-material/Email";
|
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useTheme } from "@mui/material/styles";
|
import { useTheme } from "@mui/material/styles";
|
||||||
import { usePauseMonitor } from "../../../Hooks/v1/monitorHooks.js";
|
import { usePauseMonitor } from "../../../Hooks/monitorHooks.js";
|
||||||
import { useSendTestEmail } from "../../../Hooks/v1/useSendTestEmail.js";
|
import { useSendTestEmail } from "../../../Hooks/useSendTestEmail.js";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useTestAllNotifications } from "../../../Hooks/v1/useNotifications.js";
|
import { useTestAllNotifications } from "../../../Hooks/useNotifications.js";
|
||||||
/**
|
/**
|
||||||
* MonitorDetailsControlHeader component displays the control header for monitor details.
|
* MonitorDetailsControlHeader component displays the control header for monitor details.
|
||||||
* It includes status display, pause/resume button, and a configure button for admins.
|
* It includes status display, pause/resume button, and a configure button for admins.
|
||||||
@@ -66,7 +63,7 @@ const MonitorDetailsControlHeader = ({
|
|||||||
gap={theme.spacing(2)}
|
gap={theme.spacing(2)}
|
||||||
>
|
>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
key={monitor?._id}
|
key={monitor?.id}
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
title={tooltipTitle}
|
title={tooltipTitle}
|
||||||
>
|
>
|
||||||
@@ -75,10 +72,15 @@ const MonitorDetailsControlHeader = ({
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
loading={isSending}
|
loading={isSending}
|
||||||
startIcon={<EmailIcon />}
|
startIcon={
|
||||||
|
<Icon
|
||||||
|
name="Mail"
|
||||||
|
size={18}
|
||||||
|
/>
|
||||||
|
}
|
||||||
disabled={isTestNotificationsDisabled}
|
disabled={isTestNotificationsDisabled}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
testAllNotifications({ monitorId: monitor?._id });
|
testAllNotifications({ monitorId: monitor?.id });
|
||||||
}}
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
whiteSpace: "nowrap",
|
whiteSpace: "nowrap",
|
||||||
@@ -92,7 +94,7 @@ const MonitorDetailsControlHeader = ({
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
navigate(`/incidents/${monitor?._id}`);
|
navigate(`/incidents/${monitor?.id}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("menu.incidents")}
|
{t("menu.incidents")}
|
||||||
@@ -103,24 +105,39 @@ const MonitorDetailsControlHeader = ({
|
|||||||
color="secondary"
|
color="secondary"
|
||||||
loading={isPausing}
|
loading={isPausing}
|
||||||
startIcon={
|
startIcon={
|
||||||
monitor?.isActive ? <PauseOutlinedIcon /> : <PlayArrowOutlinedIcon />
|
monitor?.isActive ? (
|
||||||
|
<Icon
|
||||||
|
name="Pause"
|
||||||
|
size={18}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Icon
|
||||||
|
name="Play"
|
||||||
|
size={18}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
pauseMonitor({
|
pauseMonitor({
|
||||||
monitorId: monitor?._id,
|
monitorId: monitor?.id,
|
||||||
triggerUpdate,
|
triggerUpdate,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{monitor?.isActive ? "Pause" : "Resume"}
|
{monitor?.isActive ? t("pause") : t("resume")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
startIcon={<SettingsOutlinedIcon />}
|
startIcon={
|
||||||
onClick={() => navigate(`/${path}/configure/${monitor._id}`)}
|
<Icon
|
||||||
|
name="Settings"
|
||||||
|
size={18}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClick={() => navigate(`/${path}/configure/${monitor.id}`)}
|
||||||
>
|
>
|
||||||
Configure
|
Configure
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import PulseDot from "../Animated/PulseDot.jsx";
|
|||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import Dot from "../Dot/index.jsx";
|
import Dot from "../Dot/index.jsx";
|
||||||
// Utils
|
// Utils
|
||||||
import { formatDurationRounded } from "../../../Utils/timeUtils.js";
|
import { formatDurationRounded } from "../../../Utils/timeUtilsLegacy.js";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import { useMonitorUtils } from "../../../Hooks/v1/useMonitorUtils.js";
|
import { useMonitorUtils } from "../../../Hooks/useMonitorUtils.js";
|
||||||
import { formatMonitorUrl } from "../../../Utils/utils.js";
|
import { formatMonitorUrl } from "../../../Utils/utils.js";
|
||||||
/**
|
/**
|
||||||
* Status component displays the status information of a monitor.
|
* Status component displays the status information of a monitor.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import Stack from "@mui/material/Stack";
|
import Stack from "@mui/material/Stack";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import Divider from "@mui/material/Divider";
|
import Divider from "@mui/material/Divider";
|
||||||
import DeleteOutlineRoundedIcon from "@mui/icons-material/DeleteOutlineRounded";
|
import Icon from "../Icon";
|
||||||
import Search from "../Inputs/Search/index.jsx";
|
import Search from "../Inputs/Search/index.jsx";
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
@@ -31,7 +31,7 @@ const NotificationConfig = ({
|
|||||||
setMonitor((prev) => {
|
setMonitor((prev) => {
|
||||||
return {
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
notifications: value.map((notification) => notification._id),
|
notifications: value.map((notification) => notification.id),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -39,14 +39,14 @@ const NotificationConfig = ({
|
|||||||
// Handlers
|
// Handlers
|
||||||
const handleDelete = (id) => {
|
const handleDelete = (id) => {
|
||||||
const updatedNotifications = selectedNotifications.filter(
|
const updatedNotifications = selectedNotifications.filter(
|
||||||
(notification) => notification._id !== id
|
(notification) => notification.id !== id
|
||||||
);
|
);
|
||||||
|
|
||||||
setSelectedNotifications(updatedNotifications);
|
setSelectedNotifications(updatedNotifications);
|
||||||
setMonitor((prev) => {
|
setMonitor((prev) => {
|
||||||
return {
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
notifications: updatedNotifications.map((notification) => notification._id),
|
notifications: updatedNotifications.map((notification) => notification.id),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -57,7 +57,7 @@ const NotificationConfig = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (setNotifications) {
|
if (setNotifications) {
|
||||||
const toSet = setNotifications.map((notification) => {
|
const toSet = setNotifications.map((notification) => {
|
||||||
return notifications.find((n) => n._id === notification);
|
return notifications.find((n) => n.id === notification);
|
||||||
});
|
});
|
||||||
setSelectedNotifications(toSet);
|
setSelectedNotifications(toSet);
|
||||||
}
|
}
|
||||||
@@ -94,7 +94,7 @@ const NotificationConfig = ({
|
|||||||
<Stack
|
<Stack
|
||||||
direction="row"
|
direction="row"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
key={notification._id}
|
key={notification.id}
|
||||||
width="100%"
|
width="100%"
|
||||||
>
|
>
|
||||||
<Typography
|
<Typography
|
||||||
@@ -102,11 +102,13 @@ const NotificationConfig = ({
|
|||||||
>
|
>
|
||||||
{notification.notificationName}
|
{notification.notificationName}
|
||||||
</Typography>
|
</Typography>
|
||||||
<DeleteOutlineRoundedIcon
|
<Icon
|
||||||
|
name="Trash2"
|
||||||
|
size={20}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleDelete(notification._id);
|
handleDelete(notification.id);
|
||||||
}}
|
}}
|
||||||
sx={{ cursor: "pointer" }}
|
style={{ cursor: "pointer" }}
|
||||||
/>
|
/>
|
||||||
{index < selectedNotifications.length - 1 && <Divider />}
|
{index < selectedNotifications.length - 1 && <Divider />}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import Fallback from "../Fallback/index.jsx";
|
|||||||
import { Typography } from "@mui/material";
|
import { Typography } from "@mui/material";
|
||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useIsAdmin } from "../../../Hooks/v1/useIsAdmin.js";
|
import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
const PageStateWrapper = ({
|
const PageStateWrapper = ({
|
||||||
networkError,
|
networkError,
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { Box, IconButton, LinearProgress, Stack, Typography } from "@mui/material";
|
import { Box, IconButton, LinearProgress, Stack, Typography } from "@mui/material";
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
import Icon from "../Icon";
|
||||||
import ErrorOutlineOutlinedIcon from "@mui/icons-material/ErrorOutlineOutlined";
|
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -51,7 +50,10 @@ const ProgressUpload = ({ icon, label, size, progress = 0, onClick, error }) =>
|
|||||||
alignItems={error ? "center" : "flex-start"}
|
alignItems={error ? "center" : "flex-start"}
|
||||||
>
|
>
|
||||||
{error ? (
|
{error ? (
|
||||||
<ErrorOutlineOutlinedIcon />
|
<Icon
|
||||||
|
name="AlertTriangle"
|
||||||
|
size={20}
|
||||||
|
/>
|
||||||
) : icon ? (
|
) : icon ? (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@@ -127,10 +129,9 @@ const ProgressUpload = ({ icon, label, size, progress = 0, onClick, error }) =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<CloseIcon
|
<Icon
|
||||||
sx={{
|
name="X"
|
||||||
fontSize: "20px",
|
size={20}
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -1,15 +1,22 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Step, Stepper, StepLabel, Typography } from "@mui/material";
|
import { Step, Stepper, StepLabel, Typography } from "@mui/material";
|
||||||
import RadioButtonCheckedIcon from "@mui/icons-material/RadioButtonChecked";
|
import Icon from "../Icon";
|
||||||
import CheckCircle from "@mui/icons-material/CheckCircle";
|
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
const CustomStepIcon = (props) => {
|
const CustomStepIcon = (props) => {
|
||||||
const { completed, active } = props;
|
const { completed, active } = props;
|
||||||
return completed ? (
|
return completed ? (
|
||||||
<CheckCircle color="accent" />
|
<Icon
|
||||||
|
name="CheckCircle2"
|
||||||
|
size={24}
|
||||||
|
color="accent.main"
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<RadioButtonCheckedIcon color={active ? "accent" : "disabled"} />
|
<Icon
|
||||||
|
name="Circle"
|
||||||
|
size={24}
|
||||||
|
color={active ? "accent.main" : "text.disabled"}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import html2canvas from "html2canvas";
|
import html2canvas from "html2canvas";
|
||||||
import ShareIcon from "@mui/icons-material/Share";
|
import Icon from "../Icon";
|
||||||
import { Button } from "@mui/material";
|
import { Button } from "@mui/material";
|
||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
|
|
||||||
@@ -55,7 +55,13 @@ const ShareComponent = ({ elementToCapture, fileName = "screenshot" }) => {
|
|||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
startIcon={<ShareIcon sx={{ color: theme.palette.success.main }} />}
|
startIcon={
|
||||||
|
<Icon
|
||||||
|
name="Share2"
|
||||||
|
size={18}
|
||||||
|
color="success.main"
|
||||||
|
/>
|
||||||
|
}
|
||||||
color="success"
|
color="success"
|
||||||
onClick={captureAndShare}
|
onClick={captureAndShare}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ import Avatar from "../../Avatar/index.jsx";
|
|||||||
import Menu from "@mui/material/Menu";
|
import Menu from "@mui/material/Menu";
|
||||||
import MenuItem from "@mui/material/MenuItem";
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
import Divider from "@mui/material/Divider";
|
import Divider from "@mui/material/Divider";
|
||||||
import DotsVertical from "../../../../assets/icons/dots-vertical.svg?react";
|
import Icon from "../../Icon";
|
||||||
import LogoutSvg from "../../../../assets/icons/logout.svg?react";
|
|
||||||
|
|
||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
@@ -81,6 +80,10 @@ const AuthFooter = ({ collapsed, accountMenuItems }) => {
|
|||||||
gap: theme.spacing(2),
|
gap: theme.spacing(2),
|
||||||
borderRadius: theme.shape.borderRadius,
|
borderRadius: theme.shape.borderRadius,
|
||||||
pl: theme.spacing(4),
|
pl: theme.spacing(4),
|
||||||
|
color: theme.palette.primary.contrastTextTertiary,
|
||||||
|
"& svg": {
|
||||||
|
stroke: theme.palette.primary.contrastTextTertiary,
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{item.icon}
|
{item.icon}
|
||||||
@@ -160,19 +163,14 @@ const AuthFooter = ({ collapsed, accountMenuItems }) => {
|
|||||||
ml: "50px",
|
ml: "50px",
|
||||||
"&:focus": { outline: "none" },
|
"&:focus": { outline: "none" },
|
||||||
alignSelf: "center",
|
alignSelf: "center",
|
||||||
|
|
||||||
"& svg": {
|
|
||||||
width: "22px",
|
|
||||||
height: "22px",
|
|
||||||
},
|
|
||||||
"& svg path": {
|
|
||||||
/* Vertical three dots */
|
|
||||||
stroke: theme.palette.primary.contrastTextTertiary,
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
onClick={(event) => openPopup(event)}
|
onClick={(event) => openPopup(event)}
|
||||||
>
|
>
|
||||||
<DotsVertical />
|
<Icon
|
||||||
|
name="MoreVertical"
|
||||||
|
size={22}
|
||||||
|
color="primary.contrastTextTertiary"
|
||||||
|
/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -256,12 +254,13 @@ const AuthFooter = ({ collapsed, accountMenuItems }) => {
|
|||||||
gap: theme.spacing(4),
|
gap: theme.spacing(4),
|
||||||
borderRadius: theme.shape.borderRadius,
|
borderRadius: theme.shape.borderRadius,
|
||||||
pl: theme.spacing(4),
|
pl: theme.spacing(4),
|
||||||
"& svg path": {
|
|
||||||
stroke: theme.palette.primary.contrastTextTertiary,
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<LogoutSvg />
|
<Icon
|
||||||
|
name="LogOut"
|
||||||
|
size={20}
|
||||||
|
color="primary.contrastTextTertiary"
|
||||||
|
/>
|
||||||
{t("menu.logOut", "Log out")}
|
{t("menu.logOut", "Log out")}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|||||||
@@ -52,12 +52,11 @@ const NavItem = ({ item, collapsed, selected, onClick }) => {
|
|||||||
<ListItemIcon
|
<ListItemIcon
|
||||||
sx={{
|
sx={{
|
||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
|
color: iconStroke,
|
||||||
"& svg": {
|
"& svg": {
|
||||||
height: 20,
|
height: 20,
|
||||||
width: 20,
|
width: 20,
|
||||||
opacity: 0.81,
|
opacity: 0.81,
|
||||||
},
|
|
||||||
"& svg path": {
|
|
||||||
stroke: iconStroke,
|
stroke: iconStroke,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -8,28 +8,13 @@ import NavItem from "./components/navItem.jsx";
|
|||||||
import AuthFooter from "./components/authFooter.jsx";
|
import AuthFooter from "./components/authFooter.jsx";
|
||||||
|
|
||||||
import StarPrompt from "../StarPrompt/index.jsx";
|
import StarPrompt from "../StarPrompt/index.jsx";
|
||||||
import LockSvg from "../../../assets/icons/lock.svg?react";
|
import Icon from "../Icon";
|
||||||
import UserSvg from "../../../assets/icons/user.svg?react";
|
|
||||||
import TeamSvg from "../../../assets/icons/user-two.svg?react";
|
|
||||||
import Support from "../../../assets/icons/support.svg?react";
|
|
||||||
import Maintenance from "../../../assets/icons/maintenance.svg?react";
|
|
||||||
import Monitors from "../../../assets/icons/monitors.svg?react";
|
|
||||||
import Incidents from "../../../assets/icons/incidents.svg?react";
|
|
||||||
import Integrations from "../../../assets/icons/integrations.svg?react";
|
|
||||||
import PageSpeed from "../../../assets/icons/page-speed.svg?react";
|
|
||||||
import Settings from "../../../assets/icons/settings.svg?react";
|
|
||||||
import ChangeLog from "../../../assets/icons/changeLog.svg?react";
|
|
||||||
import Docs from "../../../assets/icons/docs.svg?react";
|
|
||||||
import StatusPages from "../../../assets/icons/status-pages.svg?react";
|
|
||||||
import Discussions from "../../../assets/icons/discussions.svg?react";
|
|
||||||
import Notifications from "../../../assets/icons/notifications.svg?react";
|
|
||||||
import Logs from "../../../assets/icons/logs.svg?react";
|
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
import { useTheme } from "@mui/material/styles";
|
import { useTheme } from "@mui/material/styles";
|
||||||
import { useSelector } from "react-redux";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
|
import { useSidebar } from "@/Hooks/useSidebar.js";
|
||||||
|
|
||||||
const URL_MAP = {
|
const URL_MAP = {
|
||||||
support: "https://discord.com/invite/NAb6H3UTjK",
|
support: "https://discord.com/invite/NAb6H3UTjK",
|
||||||
@@ -39,51 +24,48 @@ const URL_MAP = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getMenu = (t) => [
|
const getMenu = (t) => [
|
||||||
{ name: t("menu.uptime"), path: "uptime", icon: <Monitors /> },
|
{ name: t("menu.uptime"), path: "uptime", icon: <Icon name="Globe" /> },
|
||||||
{ name: t("menu.pagespeed"), path: "pagespeed", icon: <PageSpeed /> },
|
{ name: t("menu.pagespeed"), path: "pagespeed", icon: <Icon name="Gauge" /> },
|
||||||
|
{ name: t("menu.infrastructure"), path: "infrastructure", icon: <Icon name="Link" /> },
|
||||||
{ name: t("menu.infrastructure"), path: "infrastructure", icon: <Integrations /> },
|
|
||||||
{
|
{
|
||||||
name: t("menu.notifications"),
|
name: t("menu.notifications"),
|
||||||
path: "notifications",
|
path: "notifications",
|
||||||
icon: <Notifications />,
|
icon: <Icon name="Bell" />,
|
||||||
},
|
},
|
||||||
{ name: t("menu.incidents"), path: "incidents", icon: <Incidents /> },
|
{ name: t("menu.checks"), path: "checks", icon: <Icon name="FileText" /> },
|
||||||
|
{ name: t("menu.incidents"), path: "incidents", icon: <Icon name="AlertTriangle" /> },
|
||||||
{ name: t("menu.statusPages"), path: "status", icon: <StatusPages /> },
|
{ name: t("menu.statusPages"), path: "status", icon: <Icon name="Wifi" /> },
|
||||||
{ name: t("menu.maintenance"), path: "maintenance", icon: <Maintenance /> },
|
{ name: t("menu.maintenance"), path: "maintenance", icon: <Icon name="Wrench" /> },
|
||||||
{ name: t("menu.logs"), path: "logs", icon: <Logs /> },
|
{ name: t("menu.logs"), path: "logs", icon: <Icon name="Database" /> },
|
||||||
|
|
||||||
{
|
{
|
||||||
name: t("menu.settings"),
|
name: t("menu.settings"),
|
||||||
icon: <Settings />,
|
icon: <Icon name="Settings" />,
|
||||||
path: "settings",
|
path: "settings",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const getOtherMenuItems = (t) => [
|
const getOtherMenuItems = (t) => [
|
||||||
{ name: t("menu.support"), path: "support", icon: <Support /> },
|
{ name: t("menu.support"), path: "support", icon: <Icon name="HelpCircle" /> },
|
||||||
{
|
{
|
||||||
name: t("menu.discussions"),
|
name: t("menu.discussions"),
|
||||||
path: "discussions",
|
path: "discussions",
|
||||||
icon: <Discussions />,
|
icon: <Icon name="MessageCircle" />,
|
||||||
},
|
},
|
||||||
{ name: t("menu.docs"), path: "docs", icon: <Docs /> },
|
{ name: t("menu.docs"), path: "docs", icon: <Icon name="FileText" /> },
|
||||||
{ name: t("menu.changelog"), path: "changelog", icon: <ChangeLog /> },
|
{ name: t("menu.changelog"), path: "changelog", icon: <Icon name="Code" /> },
|
||||||
];
|
];
|
||||||
|
|
||||||
const getAccountMenuItems = (t) => [
|
const getAccountMenuItems = (t) => [
|
||||||
{ name: t("menu.profile"), path: "account/profile", icon: <UserSvg /> },
|
{ name: t("menu.profile"), path: "account/profile", icon: <Icon name="User" /> },
|
||||||
{ name: t("menu.password"), path: "account/password", icon: <LockSvg /> },
|
{ name: t("menu.password"), path: "account/password", icon: <Icon name="Lock" /> },
|
||||||
{ name: t("menu.team"), path: "account/team", icon: <TeamSvg /> },
|
{ name: t("menu.team"), path: "account/team", icon: <Icon name="Users" /> },
|
||||||
];
|
];
|
||||||
|
|
||||||
const Sidebar = () => {
|
const Sidebar = () => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
// Redux state
|
const { collapsed, width, transition } = useSidebar();
|
||||||
const collapsed = useSelector((state) => state.ui.sidebar.collapsed);
|
|
||||||
|
|
||||||
const menu = getMenu(t);
|
const menu = getMenu(t);
|
||||||
const otherMenuItems = getOtherMenuItems(t);
|
const otherMenuItems = getOtherMenuItems(t);
|
||||||
@@ -92,20 +74,19 @@ const Sidebar = () => {
|
|||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
height="100vh"
|
height="100vh"
|
||||||
width={
|
width={width}
|
||||||
collapsed
|
|
||||||
? "var(--env-var-side-bar-collapsed-width)"
|
|
||||||
: "var(--env-var-side-bar-width)"
|
|
||||||
}
|
|
||||||
component="aside"
|
component="aside"
|
||||||
position="sticky"
|
position="fixed"
|
||||||
top={0}
|
top={0}
|
||||||
borderRight={`1px solid ${theme.palette.primary.lowContrast}`}
|
left={0}
|
||||||
paddingTop={theme.spacing(6)}
|
paddingTop={theme.spacing(6)}
|
||||||
paddingBottom={theme.spacing(6)}
|
paddingBottom={theme.spacing(6)}
|
||||||
gap={theme.spacing(6)}
|
gap={theme.spacing(6)}
|
||||||
sx={{
|
sx={{
|
||||||
transition: "width 650ms cubic-bezier(0.36, -0.01, 0, 0.77)",
|
transition,
|
||||||
|
backgroundColor: theme.palette.background.main,
|
||||||
|
borderRight: `1px solid ${theme.palette.primary.lowContrast}`,
|
||||||
|
zIndex: 1000,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CollapseButton collapsed={collapsed} />
|
<CollapseButton collapsed={collapsed} />
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Typography, IconButton, Stack, Box } from "@mui/material";
|
import { Typography, IconButton, Stack, Box } from "@mui/material";
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
import Icon from "../Icon";
|
||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import { useSelector, useDispatch } from "react-redux";
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -67,7 +67,10 @@ const StarPrompt = ({ repoUrl = "https://github.com/bluewave-labs/checkmate" })
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CloseIcon sx={{ fontSize: "1.25rem" }} />
|
<Icon
|
||||||
|
name="X"
|
||||||
|
size={20}
|
||||||
|
/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Stack, Typography } from "@mui/material";
|
|||||||
import Image from "../Image/index.jsx";
|
import Image from "../Image/index.jsx";
|
||||||
import { useTheme } from "@mui/material/styles";
|
import { useTheme } from "@mui/material/styles";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { useMonitorUtils } from "../../../Hooks/v1/useMonitorUtils.js";
|
import { useMonitorUtils } from "../../../Hooks/useMonitorUtils.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* StatBox Component
|
* StatBox Component
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import PropTypes from "prop-types";
|
|||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import { Stack, TablePagination, Typography } from "@mui/material";
|
import { Stack, TablePagination, Typography } from "@mui/material";
|
||||||
import { TablePaginationActions } from "./Actions/index.jsx";
|
import { TablePaginationActions } from "./Actions/index.jsx";
|
||||||
import SelectorVertical from "../../../../assets/icons/selector-vertical.svg?react";
|
import Icon from "../../Icon";
|
||||||
|
|
||||||
Pagination.propTypes = {
|
Pagination.propTypes = {
|
||||||
paginationLabel: PropTypes.string, // Label for the pagination.
|
paginationLabel: PropTypes.string, // Label for the pagination.
|
||||||
@@ -100,7 +100,12 @@ function Pagination({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
inputProps: { id: "pagination-dropdown" },
|
inputProps: { id: "pagination-dropdown" },
|
||||||
IconComponent: SelectorVertical,
|
IconComponent: () => (
|
||||||
|
<Icon
|
||||||
|
name="ChevronsUpDown"
|
||||||
|
size={16}
|
||||||
|
/>
|
||||||
|
),
|
||||||
sx: {
|
sx: {
|
||||||
ml: theme.spacing(4),
|
ml: theme.spacing(4),
|
||||||
mr: theme.spacing(12),
|
mr: theme.spacing(12),
|
||||||
|
|||||||
@@ -3,19 +3,31 @@ import IconButton from "@mui/material/IconButton";
|
|||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import ToastBody from "./body.jsx";
|
import ToastBody from "./body.jsx";
|
||||||
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
|
import Icon from "../Icon";
|
||||||
import ErrorOutlineOutlinedIcon from "@mui/icons-material/ErrorOutlineOutlined";
|
|
||||||
import WarningAmberOutlinedIcon from "@mui/icons-material/WarningAmberOutlined";
|
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
const icons = {
|
const icons = {
|
||||||
info: <InfoOutlinedIcon />,
|
info: (
|
||||||
error: <ErrorOutlineOutlinedIcon />,
|
<Icon
|
||||||
warning: <WarningAmberOutlinedIcon />,
|
name="Info"
|
||||||
|
size={24}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
error: (
|
||||||
|
<Icon
|
||||||
|
name="AlertCircle"
|
||||||
|
size={24}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
warning: (
|
||||||
|
<Icon
|
||||||
|
name="AlertTriangle"
|
||||||
|
size={24}
|
||||||
|
/>
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
const Toast = ({ variant, title, body, onClick, hasDismiss, hasIcon }) => {
|
const Toast = ({ variant, title, body, onClick, hasDismiss, hasIcon }) => {
|
||||||
@@ -50,7 +62,10 @@ const Toast = ({ variant, title, body, onClick, hasDismiss, hasIcon }) => {
|
|||||||
)}
|
)}
|
||||||
{title && (
|
{title && (
|
||||||
<IconButton onClick={onClick}>
|
<IconButton onClick={onClick}>
|
||||||
<CloseIcon />
|
<Icon
|
||||||
|
name="X"
|
||||||
|
size={20}
|
||||||
|
/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -63,7 +78,10 @@ const Toast = ({ variant, title, body, onClick, hasDismiss, hasIcon }) => {
|
|||||||
<ToastBody body={body} />
|
<ToastBody body={body} />
|
||||||
{!title && (
|
{!title && (
|
||||||
<IconButton onClick={onClick}>
|
<IconButton onClick={onClick}>
|
||||||
<CloseIcon />
|
<Icon
|
||||||
|
name="X"
|
||||||
|
size={20}
|
||||||
|
/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
import LeftArrow from "@/assets/icons/left-arrow.svg?react";
|
|
||||||
import LeftArrowDouble from "@/assets/icons/left-arrow-double.svg?react";
|
|
||||||
import LeftArrowLong from "@/assets/icons/left-arrow-long.svg?react";
|
|
||||||
|
|
||||||
export const ArrowLeft = ({
|
|
||||||
type,
|
|
||||||
color = "#667085",
|
|
||||||
...props
|
|
||||||
}: {
|
|
||||||
type?: string;
|
|
||||||
color?: string | undefined;
|
|
||||||
[key: string]: any;
|
|
||||||
}) => {
|
|
||||||
if (type === "double") {
|
|
||||||
return (
|
|
||||||
<LeftArrowDouble
|
|
||||||
style={{ color }}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else if (type === "long") {
|
|
||||||
return (
|
|
||||||
<LeftArrowLong
|
|
||||||
style={{ color }}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<LeftArrow
|
|
||||||
style={{ color }}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import RightArrow from "@/assets/icons/right-arrow.svg?react";
|
|
||||||
import RightArrowDouble from "@/assets/icons/right-arrow-double.svg?react";
|
|
||||||
|
|
||||||
export const ArrowRight = ({
|
|
||||||
type,
|
|
||||||
color = "#667085",
|
|
||||||
...props
|
|
||||||
}: {
|
|
||||||
type?: string;
|
|
||||||
color?: string | undefined;
|
|
||||||
[key: string]: any;
|
|
||||||
}) => {
|
|
||||||
if (type === "double") {
|
|
||||||
return (
|
|
||||||
<RightArrowDouble
|
|
||||||
style={{ color }}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<RightArrow
|
|
||||||
style={{ color }}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import Stack from "@mui/material/Stack";
|
|
||||||
import Box from "@mui/material/Box";
|
|
||||||
import { HeaderAuth } from "@/Components/v2/Auth";
|
|
||||||
import Logo from "@/assets/icons/checkmate-icon.svg?react";
|
|
||||||
|
|
||||||
import type { StackProps } from "@mui/material/Stack";
|
|
||||||
import { useTheme } from "@mui/material/styles";
|
|
||||||
import { Typography } from "@mui/material";
|
|
||||||
|
|
||||||
interface AuthBasePageProps extends StackProps {
|
|
||||||
title?: string;
|
|
||||||
subtitle?: string;
|
|
||||||
children: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AuthBasePage: React.FC<AuthBasePageProps> = ({
|
|
||||||
children,
|
|
||||||
title,
|
|
||||||
subtitle,
|
|
||||||
...props
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<Stack
|
|
||||||
gap={theme.spacing(10)}
|
|
||||||
minHeight="100vh"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<HeaderAuth />
|
|
||||||
<Stack
|
|
||||||
alignItems="center"
|
|
||||||
margin="auto"
|
|
||||||
width="100%"
|
|
||||||
gap={theme.spacing(4)}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
width={{ xs: 60, sm: 70, md: 80 }}
|
|
||||||
mb={theme.spacing(10)}
|
|
||||||
>
|
|
||||||
<Logo style={{ width: "100%", height: "100%" }} />
|
|
||||||
</Box>
|
|
||||||
<Typography variant="h1">{title}</Typography>
|
|
||||||
<Typography variant="h1">{subtitle}</Typography>
|
|
||||||
{children}
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import Stack from "@mui/material/Stack";
|
|
||||||
import Typography from "@mui/material/Typography";
|
|
||||||
import Logo from "@/assets/icons/checkmate-icon.svg?react";
|
|
||||||
|
|
||||||
import { useTheme } from "@mui/material/styles";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { LanguageSelector, ThemeSwitch } from "@/Components/v2/Inputs";
|
|
||||||
|
|
||||||
export const HeaderAuth = () => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
return (
|
|
||||||
<Stack
|
|
||||||
width={"100%"}
|
|
||||||
direction="row"
|
|
||||||
alignItems="center"
|
|
||||||
justifyContent="flex-end"
|
|
||||||
py={theme.spacing(4)}
|
|
||||||
px={theme.spacing(12)}
|
|
||||||
gap={theme.spacing(4)}
|
|
||||||
>
|
|
||||||
<LanguageSelector />
|
|
||||||
<ThemeSwitch color="red" />
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export { HeaderAuth } from "./HeaderAuth";
|
|
||||||
export { AuthBasePage } from "./AuthBasePage";
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import Box from "@mui/material/Box";
|
|
||||||
import { useTheme } from "@mui/material/styles";
|
|
||||||
import type { SxProps } from "@mui/material/styles";
|
|
||||||
|
|
||||||
type BaseBoxProps = React.PropsWithChildren<{ sx?: SxProps }>;
|
|
||||||
|
|
||||||
export const BaseBox: React.FC<BaseBoxProps> = ({ children, sx }) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
border: 1,
|
|
||||||
borderStyle: "solid",
|
|
||||||
borderColor: theme.palette.primary.lowContrast,
|
|
||||||
borderRadius: theme.shape.borderRadius,
|
|
||||||
...sx,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
import Stack from "@mui/material/Stack";
|
|
||||||
import { ErrorFallback, EmptyFallback } from "./Fallback";
|
|
||||||
|
|
||||||
import type { StackProps } from "@mui/material/Stack";
|
|
||||||
import { useTheme } from "@mui/material/styles";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
interface BasePageProps extends StackProps {
|
|
||||||
children: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const BasePage: React.FC<BasePageProps> = ({
|
|
||||||
children,
|
|
||||||
...props
|
|
||||||
}: {
|
|
||||||
children: React.ReactNode;
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<Stack
|
|
||||||
spacing={theme.spacing(10)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface BasePageWithStatesProps extends StackProps {
|
|
||||||
loading: boolean;
|
|
||||||
error: any;
|
|
||||||
items: any[];
|
|
||||||
page: string;
|
|
||||||
actionLink?: string;
|
|
||||||
children: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isEmpty = (items: any[]) => {
|
|
||||||
if (!items) return true;
|
|
||||||
if (Array.isArray(items) && items.length === 0) return true;
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const BasePageWithStates: React.FC<BasePageWithStatesProps> = ({
|
|
||||||
loading,
|
|
||||||
error,
|
|
||||||
items,
|
|
||||||
page,
|
|
||||||
actionLink,
|
|
||||||
children,
|
|
||||||
...props
|
|
||||||
}: BasePageWithStatesProps) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return (
|
|
||||||
<ErrorFallback
|
|
||||||
title="Something went wrong..."
|
|
||||||
subtitle="Please try again later"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isEmpty(items)) {
|
|
||||||
return (
|
|
||||||
<EmptyFallback
|
|
||||||
page={page}
|
|
||||||
title={t(`${page}Monitor.fallback.title`)}
|
|
||||||
bullets={t(`${page}Monitor.fallback.checks`, { returnObjects: true })}
|
|
||||||
actionButtonText={t(`${page}Monitor.fallback.actionButton`)}
|
|
||||||
actionLink={actionLink || ""}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <BasePage {...props}>{children}</BasePage>;
|
|
||||||
};
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
import Box from "@mui/material/Box";
|
|
||||||
import Stack from "@mui/material/Stack";
|
|
||||||
import OutputAnimation from "@/assets/Animations/output.gif";
|
|
||||||
import DarkmodeOutput from "@/assets/Animations/darkmodeOutput.gif";
|
|
||||||
import Typography from "@mui/material/Typography";
|
|
||||||
import { BulletPointCheck } from "@/Components/v2/DesignElements";
|
|
||||||
import { Button } from "@/Components/v2/Inputs";
|
|
||||||
|
|
||||||
import { useNavigate } from "react-router";
|
|
||||||
import { useMediaQuery } from "@mui/material";
|
|
||||||
import { useTheme } from "@mui/material/styles";
|
|
||||||
import { useSelector } from "react-redux";
|
|
||||||
|
|
||||||
import type { BoxProps } from "@mui/material";
|
|
||||||
|
|
||||||
interface BaseFallbackProps extends BoxProps {
|
|
||||||
children: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const BaseFallback: React.FC<BaseFallbackProps> = ({ children, ...props }) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const mode = useSelector((state: any) => state.ui.mode);
|
|
||||||
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
margin={isSmall ? "inherit" : "auto"}
|
|
||||||
marginTop={isSmall ? "33%" : "auto"}
|
|
||||||
width={{
|
|
||||||
sm: "90%",
|
|
||||||
md: "70%",
|
|
||||||
lg: "50%",
|
|
||||||
xl: "40%",
|
|
||||||
}}
|
|
||||||
padding={theme.spacing(16)}
|
|
||||||
bgcolor={theme.palette.primary.main}
|
|
||||||
position="relative"
|
|
||||||
border={1}
|
|
||||||
borderColor={theme.palette.primary.lowContrast}
|
|
||||||
borderRadius={theme.shape.borderRadius}
|
|
||||||
overflow="hidden"
|
|
||||||
sx={{
|
|
||||||
borderStyle: "dashed",
|
|
||||||
}}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<Stack
|
|
||||||
alignItems="center"
|
|
||||||
gap={theme.spacing(20)}
|
|
||||||
sx={{
|
|
||||||
width: "fit-content",
|
|
||||||
margin: "auto",
|
|
||||||
marginTop: "100px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
component="img"
|
|
||||||
src={mode === "light" ? OutputAnimation : DarkmodeOutput}
|
|
||||||
bgcolor="transparent"
|
|
||||||
alt="Loading animation"
|
|
||||||
width="100%"
|
|
||||||
sx={{
|
|
||||||
zIndex: 1,
|
|
||||||
border: "none",
|
|
||||||
borderRadius: theme.spacing(8),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Stack
|
|
||||||
gap={theme.spacing(4)}
|
|
||||||
alignItems="center"
|
|
||||||
maxWidth={"300px"}
|
|
||||||
zIndex={1}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ErrorFallback = ({
|
|
||||||
title,
|
|
||||||
subtitle,
|
|
||||||
}: {
|
|
||||||
title: string;
|
|
||||||
subtitle: string;
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<BaseFallback>
|
|
||||||
<Typography
|
|
||||||
variant="h1"
|
|
||||||
marginY={theme.spacing(4)}
|
|
||||||
color={theme.palette.primary.contrastTextTertiary}
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</Typography>
|
|
||||||
<Typography>{subtitle}</Typography>
|
|
||||||
</BaseFallback>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const EmptyFallback = ({
|
|
||||||
page,
|
|
||||||
title,
|
|
||||||
bullets,
|
|
||||||
actionButtonText,
|
|
||||||
actionLink,
|
|
||||||
}: {
|
|
||||||
page: string;
|
|
||||||
title: string;
|
|
||||||
bullets: any;
|
|
||||||
actionButtonText: string;
|
|
||||||
actionLink: string;
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
return (
|
|
||||||
<BaseFallback>
|
|
||||||
<Stack
|
|
||||||
gap={theme.spacing(10)}
|
|
||||||
zIndex={1}
|
|
||||||
alignItems="center"
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
component="h1"
|
|
||||||
color={theme.palette.primary.contrastText}
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</Typography>
|
|
||||||
<Stack
|
|
||||||
sx={{
|
|
||||||
flexWrap: "wrap",
|
|
||||||
gap: theme.spacing(2),
|
|
||||||
maxWidth: { xs: "90%", md: "80%", lg: "75%" },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{bullets?.map((bullet: string, index: number) => (
|
|
||||||
<BulletPointCheck
|
|
||||||
text={bullet}
|
|
||||||
key={`${(page + "Monitors").trim().split(" ")[0]}-${index}`}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Stack>
|
|
||||||
<Stack>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
color="accent"
|
|
||||||
onClick={() => navigate(actionLink)}
|
|
||||||
>
|
|
||||||
{actionButtonText}
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
</BaseFallback>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
import { Fragment } from "react";
|
|
||||||
import Stack from "@mui/material/Stack";
|
|
||||||
import Box from "@mui/material/Box";
|
|
||||||
import Typography from "@mui/material/Typography";
|
|
||||||
import { useTheme } from "@mui/material/styles";
|
|
||||||
import { useMediaQuery } from "@mui/material";
|
|
||||||
export const SplitBox = ({
|
|
||||||
left,
|
|
||||||
right,
|
|
||||||
}: {
|
|
||||||
left: React.ReactNode;
|
|
||||||
right: React.ReactNode;
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
|
|
||||||
return (
|
|
||||||
<Stack
|
|
||||||
direction={isSmall ? "column" : "row"}
|
|
||||||
bgcolor={theme.palette.primary.main}
|
|
||||||
border={1}
|
|
||||||
borderColor={theme.palette.primary.lowContrast}
|
|
||||||
borderRadius={theme.spacing(2)}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
padding={theme.spacing(15)}
|
|
||||||
borderRight={isSmall ? 0 : 1}
|
|
||||||
borderBottom={isSmall ? 1 : 0}
|
|
||||||
borderColor={theme.palette.primary.lowContrast}
|
|
||||||
flex={0.7}
|
|
||||||
>
|
|
||||||
{left}
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
flex={1}
|
|
||||||
padding={theme.spacing(15)}
|
|
||||||
>
|
|
||||||
{right}
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ConfigBox = ({
|
|
||||||
title,
|
|
||||||
subtitle,
|
|
||||||
rightContent,
|
|
||||||
}: {
|
|
||||||
title: string;
|
|
||||||
subtitle: string;
|
|
||||||
rightContent: React.ReactNode;
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<SplitBox
|
|
||||||
left={
|
|
||||||
<Fragment>
|
|
||||||
<Typography
|
|
||||||
component="h2"
|
|
||||||
variant="h2"
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</Typography>
|
|
||||||
<Typography component="p">{subtitle}</Typography>
|
|
||||||
</Fragment>
|
|
||||||
}
|
|
||||||
right={rightContent}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
import Stack from "@mui/material/Stack";
|
|
||||||
import Typography from "@mui/material/Typography";
|
|
||||||
import { useTheme } from "@mui/material/styles";
|
|
||||||
import { useMediaQuery } from "@mui/material";
|
|
||||||
import type { PaletteKey } from "@/Utils/Theme/v2/theme";
|
|
||||||
import { BaseBox } from "@/Components/v2/DesignElements";
|
|
||||||
|
|
||||||
type GradientBox = React.PropsWithChildren<{ palette?: PaletteKey }>;
|
|
||||||
|
|
||||||
export const GradientBox: React.FC<GradientBox> = ({ children, palette }) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
|
|
||||||
const bg = palette
|
|
||||||
? `linear-gradient(to bottom right, ${theme.palette[palette].main} 30%, ${theme.palette[palette].lowContrast} 70%)`
|
|
||||||
: `linear-gradient(340deg, ${theme.palette.tertiary.main} 10%, ${theme.palette.primary.main} 45%)`;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BaseBox
|
|
||||||
sx={{
|
|
||||||
padding: `${theme.spacing(4)} ${theme.spacing(8)}`,
|
|
||||||
width: isSmall
|
|
||||||
? `calc(50% - (1 * ${theme.spacing(8)} / 2))`
|
|
||||||
: `calc(25% - (3 * ${theme.spacing(8)} / 4))`,
|
|
||||||
|
|
||||||
background: bg,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</BaseBox>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
type StatBoxProps = React.PropsWithChildren<{
|
|
||||||
title: string;
|
|
||||||
subtitle: string;
|
|
||||||
palette?: PaletteKey;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export const StatBox: React.FC<StatBoxProps> = ({
|
|
||||||
title,
|
|
||||||
subtitle,
|
|
||||||
palette,
|
|
||||||
children,
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const textColor = palette ? theme.palette[palette].contrastText : "inherit";
|
|
||||||
|
|
||||||
return (
|
|
||||||
<GradientBox palette={palette}>
|
|
||||||
<Stack>
|
|
||||||
<Typography color={textColor}>{title}</Typography>
|
|
||||||
<Typography color={textColor}>{subtitle}</Typography>
|
|
||||||
{children}
|
|
||||||
</Stack>
|
|
||||||
</GradientBox>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import Box from "@mui/material/Box";
|
|
||||||
import { BaseBox } from "@/Components/v2/DesignElements";
|
|
||||||
import type { MonitorStatus } from "@/Types/Monitor";
|
|
||||||
|
|
||||||
import { getStatusPalette } from "@/Utils/MonitorUtils";
|
|
||||||
import { useTheme } from "@mui/material/styles";
|
|
||||||
|
|
||||||
export const StatusLabel = ({
|
|
||||||
status,
|
|
||||||
isActive,
|
|
||||||
}: {
|
|
||||||
status: MonitorStatus;
|
|
||||||
isActive?: boolean;
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const palette = getStatusPalette(status);
|
|
||||||
const transformedText = status.charAt(0).toUpperCase() + status.slice(1).toLowerCase();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BaseBox
|
|
||||||
sx={{
|
|
||||||
display: "inline-flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
padding: theme.spacing(3, 5),
|
|
||||||
color: theme.palette[palette].main,
|
|
||||||
borderColor: theme.palette[palette].lowContrast,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
width={7}
|
|
||||||
height={7}
|
|
||||||
bgcolor={theme.palette[palette].lowContrast}
|
|
||||||
borderRadius="50%"
|
|
||||||
marginRight="5px"
|
|
||||||
/>
|
|
||||||
{isActive === false ? "Paused" : transformedText}
|
|
||||||
</BaseBox>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,215 +0,0 @@
|
|||||||
import Paper from "@mui/material/Paper";
|
|
||||||
import Table from "@mui/material/Table";
|
|
||||||
import TableBody from "@mui/material/TableBody";
|
|
||||||
import TableCell from "@mui/material/TableCell";
|
|
||||||
import TableContainer from "@mui/material/TableContainer";
|
|
||||||
import TableHead from "@mui/material/TableHead";
|
|
||||||
import TableRow from "@mui/material/TableRow";
|
|
||||||
|
|
||||||
import IconButton from "@mui/material/IconButton";
|
|
||||||
import LastPageIcon from "@mui/icons-material/LastPage";
|
|
||||||
import FirstPageIcon from "@mui/icons-material/FirstPage";
|
|
||||||
import KeyboardArrowLeft from "@mui/icons-material/KeyboardArrowLeft";
|
|
||||||
import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight";
|
|
||||||
|
|
||||||
import Box from "@mui/material/Box";
|
|
||||||
import TablePagination from "@mui/material/TablePagination";
|
|
||||||
import type { TablePaginationProps } from "@mui/material/TablePagination";
|
|
||||||
|
|
||||||
import { useTheme } from "@mui/material/styles";
|
|
||||||
import { useMediaQuery } from "@mui/material";
|
|
||||||
export type Header<T> = {
|
|
||||||
id: number | string;
|
|
||||||
content: React.ReactNode;
|
|
||||||
onClick?: (event: React.MouseEvent<HTMLTableCellElement | null>, row: T) => void;
|
|
||||||
render: (row: T) => React.ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
type DataTableProps<T extends { id?: string | number; _id?: string | number }> = {
|
|
||||||
headers: Header<T>[];
|
|
||||||
data: T[];
|
|
||||||
onRowClick?: (row: T) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function DataTable<
|
|
||||||
T extends {
|
|
||||||
id?: string | number;
|
|
||||||
_id?: string | number;
|
|
||||||
onRowClick?: (row: T) => void;
|
|
||||||
},
|
|
||||||
>({ headers, data, onRowClick }: DataTableProps<T>) {
|
|
||||||
const theme = useTheme();
|
|
||||||
if (data.length === 0 || headers.length === 0) return <div>No data</div>;
|
|
||||||
return (
|
|
||||||
<TableContainer component={Paper}>
|
|
||||||
<Table
|
|
||||||
stickyHeader
|
|
||||||
sx={{
|
|
||||||
"&.MuiTable-root :is(.MuiTableHead-root, .MuiTableBody-root) :is(th, td)": {
|
|
||||||
paddingLeft: theme.spacing(8),
|
|
||||||
},
|
|
||||||
"& :is(th)": {
|
|
||||||
backgroundColor: theme.palette.secondary.main,
|
|
||||||
color: theme.palette.secondary.contrastText,
|
|
||||||
fontWeight: 600,
|
|
||||||
},
|
|
||||||
"& :is(td)": {
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
color: theme.palette.primary.contrastTextSecondary,
|
|
||||||
},
|
|
||||||
"& .MuiTableBody-root .MuiTableRow-root:last-child .MuiTableCell-root": {
|
|
||||||
borderBottom: "none",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
{headers.map((header, idx) => {
|
|
||||||
return (
|
|
||||||
<TableCell
|
|
||||||
align={idx === 0 ? "left" : "center"}
|
|
||||||
key={header.id}
|
|
||||||
>
|
|
||||||
{header.content}
|
|
||||||
</TableCell>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
|
||||||
{data.map((row) => {
|
|
||||||
const key = row.id || row._id || Math.random();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TableRow
|
|
||||||
key={key}
|
|
||||||
sx={{ cursor: onRowClick ? "pointer" : "default" }}
|
|
||||||
onClick={() => (onRowClick ? 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) : undefined
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{header.render(row)}
|
|
||||||
</TableCell>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</TableRow>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TablePaginationActionsProps {
|
|
||||||
count: number;
|
|
||||||
page: number;
|
|
||||||
rowsPerPage: number;
|
|
||||||
onPageChange: (event: React.MouseEvent<HTMLButtonElement>, newPage: number) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function TablePaginationActions(props: TablePaginationActionsProps) {
|
|
||||||
const theme = useTheme();
|
|
||||||
const { count, page, rowsPerPage, onPageChange } = props;
|
|
||||||
|
|
||||||
const handleFirstPageButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
||||||
onPageChange(event, 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleBackButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
||||||
onPageChange(event, page - 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleNextButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
||||||
onPageChange(event, page + 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLastPageButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
||||||
onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{ flexShrink: 0, ml: 2.5 }}
|
|
||||||
className="table-pagination-actions"
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
onClick={handleFirstPageButtonClick}
|
|
||||||
disabled={page === 0}
|
|
||||||
aria-label="first page"
|
|
||||||
>
|
|
||||||
{theme.direction === "rtl" ? <LastPageIcon /> : <FirstPageIcon />}
|
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
|
||||||
onClick={handleBackButtonClick}
|
|
||||||
disabled={page === 0}
|
|
||||||
aria-label="previous page"
|
|
||||||
>
|
|
||||||
{theme.direction === "rtl" ? <KeyboardArrowRight /> : <KeyboardArrowLeft />}
|
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
|
||||||
onClick={handleNextButtonClick}
|
|
||||||
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
|
|
||||||
aria-label="next page"
|
|
||||||
>
|
|
||||||
{theme.direction === "rtl" ? <KeyboardArrowLeft /> : <KeyboardArrowRight />}
|
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
|
||||||
onClick={handleLastPageButtonClick}
|
|
||||||
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
|
|
||||||
aria-label="last page"
|
|
||||||
>
|
|
||||||
{theme.direction === "rtl" ? <FirstPageIcon /> : <LastPageIcon />}
|
|
||||||
</IconButton>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Pagination: React.FC<TablePaginationProps> = ({ ...props }) => {
|
|
||||||
const isSmall = useMediaQuery((theme: any) => theme.breakpoints.down("sm"));
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<TablePagination
|
|
||||||
ActionsComponent={TablePaginationActions}
|
|
||||||
rowsPerPageOptions={[5, 10, 25]}
|
|
||||||
{...props}
|
|
||||||
sx={{
|
|
||||||
"& .MuiTablePagination-toolbar": {
|
|
||||||
display: isSmall ? "grid" : "flex",
|
|
||||||
},
|
|
||||||
"& .MuiTablePagination-selectLabel": {
|
|
||||||
gridColumn: "1",
|
|
||||||
gridRow: "1",
|
|
||||||
justifySelf: "center",
|
|
||||||
},
|
|
||||||
"& .MuiTablePagination-select": {
|
|
||||||
gridColumn: "2",
|
|
||||||
gridRow: "1",
|
|
||||||
justifySelf: "center",
|
|
||||||
},
|
|
||||||
"& .MuiTablePagination-displayedRows": {
|
|
||||||
gridColumn: "2",
|
|
||||||
gridRow: "2",
|
|
||||||
justifySelf: "center ",
|
|
||||||
},
|
|
||||||
"& .table-pagination-actions": {
|
|
||||||
gridColumn: "1",
|
|
||||||
gridRow: "2",
|
|
||||||
justifySelf: "center",
|
|
||||||
},
|
|
||||||
"& .MuiSelect-select": {
|
|
||||||
border: 1,
|
|
||||||
borderColor: theme.palette.primary.lowContrast,
|
|
||||||
borderRadius: theme.shape.borderRadius,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
export { SplitBox as HorizontalSplitBox, ConfigBox } from "./SplitBox";
|
|
||||||
export { BasePage, BasePageWithStates } from "./BasePage";
|
|
||||||
export { BGBox, UpStatusBox, DownStatusBox, PausedStatusBox } from "./StatusBox";
|
|
||||||
export { DataTable as Table, Pagination } from "./Table";
|
|
||||||
export { GradientBox, StatBox } from "./StatBox";
|
|
||||||
export { BaseBox } from "./BaseBox";
|
|
||||||
export { StatusLabel } from "./StatusLabel";
|
|
||||||
export { BaseFallback, ErrorFallback, EmptyFallback } from "./Fallback";
|
|
||||||
export { BulletPointCheck } from "./BulletPointCheck";
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import Autocomplete from "@mui/material/Autocomplete";
|
|
||||||
import type { AutocompleteProps } from "@mui/material/Autocomplete";
|
|
||||||
import { TextInput } from "@/Components/v2/Inputs/TextInput";
|
|
||||||
import { CheckboxInput } from "@/Components/v2/Inputs/Checkbox";
|
|
||||||
import ListItem from "@mui/material/ListItem";
|
|
||||||
import { useTheme } from "@mui/material/styles";
|
|
||||||
|
|
||||||
type AutoCompleteInputProps = Omit<
|
|
||||||
AutocompleteProps<any, boolean, boolean, boolean>,
|
|
||||||
"renderInput"
|
|
||||||
> & {
|
|
||||||
renderInput?: AutocompleteProps<any, boolean, boolean, boolean>["renderInput"];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AutoCompleteInput: React.FC<AutoCompleteInputProps> = ({ ...props }) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<Autocomplete
|
|
||||||
{...props}
|
|
||||||
disableCloseOnSelect
|
|
||||||
renderInput={(params) => (
|
|
||||||
<TextInput
|
|
||||||
{...params}
|
|
||||||
placeholder="Type to search"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
getOptionKey={(option) => option._id}
|
|
||||||
renderTags={() => null}
|
|
||||||
renderOption={(props, option, { selected }) => {
|
|
||||||
const { key, ...optionProps } = props;
|
|
||||||
return (
|
|
||||||
<ListItem
|
|
||||||
key={key}
|
|
||||||
{...optionProps}
|
|
||||||
>
|
|
||||||
<CheckboxInput checked={selected} />
|
|
||||||
{option.name}
|
|
||||||
</ListItem>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
sx={{
|
|
||||||
"&.MuiAutocomplete-root .MuiAutocomplete-input": {
|
|
||||||
padding: `0 ${theme.spacing(5)}`,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import Button from "@mui/material/Button";
|
|
||||||
import type { ButtonProps } from "@mui/material/Button";
|
|
||||||
|
|
||||||
export const ButtonInput: React.FC<ButtonProps> = ({ sx, ...props }) => {
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
{...props}
|
|
||||||
sx={{ textTransform: "none", height: 34, fontWeight: 400, borderRadius: 2, ...sx }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import ButtonGroup from "@mui/material/ButtonGroup";
|
|
||||||
import type { ButtonGroupProps } from "@mui/material/ButtonGroup";
|
|
||||||
export const ButtonGroupInput: React.FC<ButtonGroupProps> = ({
|
|
||||||
orientation,
|
|
||||||
...props
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<ButtonGroup
|
|
||||||
orientation={orientation}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import Checkbox from "@mui/material/Checkbox";
|
|
||||||
import type { CheckboxProps } from "@mui/material/Checkbox";
|
|
||||||
import CheckboxOutline from "@/assets/icons/checkbox-outline.svg?react";
|
|
||||||
import CheckboxFilled from "@/assets/icons/checkbox-filled.svg?react";
|
|
||||||
import { useTheme } from "@mui/material/styles";
|
|
||||||
type CheckboxInputProps = CheckboxProps & {
|
|
||||||
label?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CheckboxInput: React.FC<CheckboxInputProps> = ({ label, ...props }) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<Checkbox
|
|
||||||
{...props}
|
|
||||||
icon={<CheckboxOutline />}
|
|
||||||
checkedIcon={<CheckboxFilled />}
|
|
||||||
sx={{
|
|
||||||
"&:hover": { backgroundColor: "transparent" },
|
|
||||||
"& svg": { width: theme.spacing(8), height: theme.spacing(8) },
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
import "flag-icons/css/flag-icons.min.css";
|
|
||||||
import { Select } from "@/Components/v2/Inputs";
|
|
||||||
import MenuItem from "@mui/material/MenuItem";
|
|
||||||
import Stack from "@mui/material/Stack";
|
|
||||||
import Typography from "@mui/material/Typography";
|
|
||||||
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { useTheme } from "@mui/material/styles";
|
|
||||||
import { useDispatch } from "react-redux";
|
|
||||||
import { useSelector } from "react-redux";
|
|
||||||
import { setLanguage } from "@/Features/UI/uiSlice";
|
|
||||||
import type { SelectChangeEvent } from "@mui/material/Select";
|
|
||||||
|
|
||||||
export const LanguageSelector = () => {
|
|
||||||
const { i18n } = useTranslation();
|
|
||||||
const theme = useTheme();
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const language = useSelector((state: any) => state.ui.language);
|
|
||||||
const languages = Object.keys(i18n.options.resources || {});
|
|
||||||
const languageMap: Record<string, string> = {
|
|
||||||
cs: "cz",
|
|
||||||
ja: "jp",
|
|
||||||
uk: "ua",
|
|
||||||
vi: "vn",
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChange = (event: SelectChangeEvent<unknown>) => {
|
|
||||||
const newLang = event.target.value;
|
|
||||||
dispatch(setLanguage(newLang));
|
|
||||||
};
|
|
||||||
|
|
||||||
const languagesForDisplay = languages.map((l) => {
|
|
||||||
let formattedLanguage = l === "en" ? "gb" : l;
|
|
||||||
formattedLanguage = formattedLanguage.includes("-")
|
|
||||||
? formattedLanguage.split("-")[1].toLowerCase()
|
|
||||||
: formattedLanguage;
|
|
||||||
formattedLanguage = languageMap[formattedLanguage] || formattedLanguage;
|
|
||||||
const flag = formattedLanguage ? `fi fi-${formattedLanguage}` : null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<MenuItem
|
|
||||||
key={l}
|
|
||||||
value={l}
|
|
||||||
>
|
|
||||||
<Stack
|
|
||||||
direction="row"
|
|
||||||
gap={theme.spacing(4)}
|
|
||||||
>
|
|
||||||
{flag && <span className={flag} />}
|
|
||||||
<Typography textTransform={"uppercase"}>{l}</Typography>
|
|
||||||
</Stack>
|
|
||||||
</MenuItem>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Select
|
|
||||||
value={language}
|
|
||||||
onChange={handleChange}
|
|
||||||
>
|
|
||||||
{languagesForDisplay}
|
|
||||||
</Select>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
import Radio from "@mui/material/Radio";
|
|
||||||
import type { RadioProps } from "@mui/material/Radio";
|
|
||||||
import { useTheme } from "@mui/material/styles";
|
|
||||||
import RadioChecked from "@/assets/icons/radio-checked.svg?react";
|
|
||||||
import FormControlLabel from "@mui/material/FormControlLabel";
|
|
||||||
import Typography from "@mui/material/Typography";
|
|
||||||
|
|
||||||
interface RadioInputProps extends RadioProps {}
|
|
||||||
|
|
||||||
export const RadioInput: React.FC<RadioInputProps> = ({ ...props }) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<Radio
|
|
||||||
{...props}
|
|
||||||
checkedIcon={<RadioChecked />}
|
|
||||||
sx={{
|
|
||||||
color: "transparent",
|
|
||||||
boxShadow: `inset 0 0 0 1px ${theme.palette.secondary.main}`,
|
|
||||||
"&:not(.Mui-checked)": {
|
|
||||||
boxShadow: `inset 0 0 0 1px ${theme.palette.primary.contrastText}70`, // Use theme text color for the outline
|
|
||||||
},
|
|
||||||
mt: theme.spacing(0.5),
|
|
||||||
padding: 0,
|
|
||||||
"& .MuiSvgIcon-root": {
|
|
||||||
fontSize: 16,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const RadioWithDescription: React.FC<
|
|
||||||
RadioInputProps & { label: string; description: string }
|
|
||||||
> = ({ label, description, ...props }) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<FormControlLabel
|
|
||||||
control={<RadioInput {...props} />}
|
|
||||||
label={
|
|
||||||
<>
|
|
||||||
<Typography component="p">{label}</Typography>
|
|
||||||
<Typography
|
|
||||||
component="h6"
|
|
||||||
color={theme.palette.primary.contrastTextSecondary}
|
|
||||||
>
|
|
||||||
{description}
|
|
||||||
</Typography>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
sx={{
|
|
||||||
alignItems: "flex-start",
|
|
||||||
p: theme.spacing(2.5),
|
|
||||||
m: theme.spacing(-2.5),
|
|
||||||
borderRadius: theme.shape.borderRadius,
|
|
||||||
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: theme.palette.tertiary.main,
|
|
||||||
},
|
|
||||||
"& .MuiButtonBase-root": {
|
|
||||||
p: 0,
|
|
||||||
mr: theme.spacing(6),
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import Select from "@mui/material/Select";
|
|
||||||
import type { SelectProps } from "@mui/material/Select";
|
|
||||||
import { useTheme } from "@mui/material/styles";
|
|
||||||
|
|
||||||
export const SelectInput: React.FC<SelectProps> = ({ ...props }) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<Select
|
|
||||||
{...props}
|
|
||||||
sx={{
|
|
||||||
height: "34px",
|
|
||||||
"& .MuiOutlinedInput-notchedOutline": {
|
|
||||||
borderRadius: theme.shape.borderRadius,
|
|
||||||
borderColor: theme.palette.primary.lowContrast,
|
|
||||||
},
|
|
||||||
"&:hover .MuiOutlinedInput-notchedOutline": {
|
|
||||||
borderColor: theme.palette.primary.lowContrast,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { forwardRef } from "react";
|
|
||||||
import TextField from "@mui/material/TextField";
|
|
||||||
import type { TextFieldProps } from "@mui/material";
|
|
||||||
import { typographyLevels } from "@/Utils/Theme/v2/palette";
|
|
||||||
|
|
||||||
export const TextInput = forwardRef<HTMLInputElement, TextFieldProps>(
|
|
||||||
function TextInput(props, ref) {
|
|
||||||
return (
|
|
||||||
<TextField
|
|
||||||
{...props}
|
|
||||||
inputRef={ref}
|
|
||||||
sx={{
|
|
||||||
"& .MuiOutlinedInput-root": {
|
|
||||||
height: 34,
|
|
||||||
fontSize: typographyLevels.base,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
TextInput.displayName = "TextInput";
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import Typography from "@mui/material/Typography";
|
|
||||||
import Stack from "@mui/material/Stack";
|
|
||||||
import Link from "@mui/material/Link";
|
|
||||||
import { Link as RouterLink } from "react-router-dom";
|
|
||||||
|
|
||||||
import { useTheme } from "@mui/material/styles";
|
|
||||||
|
|
||||||
export const TextLink = ({
|
|
||||||
text,
|
|
||||||
linkText,
|
|
||||||
href,
|
|
||||||
target = "_self",
|
|
||||||
}: {
|
|
||||||
text: string;
|
|
||||||
linkText: string;
|
|
||||||
href: string;
|
|
||||||
target?: string;
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack
|
|
||||||
direction="row"
|
|
||||||
gap={theme.spacing(4)}
|
|
||||||
>
|
|
||||||
<Typography>{text}</Typography>
|
|
||||||
<Link
|
|
||||||
color="accent"
|
|
||||||
to={href}
|
|
||||||
component={RouterLink}
|
|
||||||
target={target}
|
|
||||||
>
|
|
||||||
{linkText}
|
|
||||||
</Link>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
import { useTheme } from "@mui/material/styles";
|
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
|
||||||
import { setMode } from "@/Features/UI/uiSlice.js";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import IconButton from "@mui/material/IconButton";
|
|
||||||
|
|
||||||
const SunAndMoonIcon = () => {
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<svg
|
|
||||||
className="sun-and-moon"
|
|
||||||
aria-hidden="true"
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<mask
|
|
||||||
className="moon"
|
|
||||||
id="moon-mask"
|
|
||||||
>
|
|
||||||
<rect
|
|
||||||
x="0"
|
|
||||||
y="0"
|
|
||||||
width="100%"
|
|
||||||
height="100%"
|
|
||||||
fill="#fff"
|
|
||||||
/>
|
|
||||||
<circle
|
|
||||||
cx="24"
|
|
||||||
cy="10"
|
|
||||||
r="6"
|
|
||||||
fill="#000"
|
|
||||||
/>
|
|
||||||
</mask>
|
|
||||||
<circle
|
|
||||||
className="sun"
|
|
||||||
cx="12"
|
|
||||||
cy="12"
|
|
||||||
r="6"
|
|
||||||
fill={theme.palette.primary.contrastTextSecondary}
|
|
||||||
mask="url(#moon-mask)"
|
|
||||||
/>
|
|
||||||
<g
|
|
||||||
className="sun-beams"
|
|
||||||
stroke={theme.palette.primary.contrastTextSecondary}
|
|
||||||
>
|
|
||||||
<line
|
|
||||||
x1="12"
|
|
||||||
y1="1"
|
|
||||||
x2="12"
|
|
||||||
y2="3"
|
|
||||||
/>
|
|
||||||
<line
|
|
||||||
x1="12"
|
|
||||||
y1="21"
|
|
||||||
x2="12"
|
|
||||||
y2="23"
|
|
||||||
/>
|
|
||||||
<line
|
|
||||||
x1="4.22"
|
|
||||||
y1="4.22"
|
|
||||||
x2="5.64"
|
|
||||||
y2="5.64"
|
|
||||||
/>
|
|
||||||
<line
|
|
||||||
x1="18.36"
|
|
||||||
y1="18.36"
|
|
||||||
x2="19.78"
|
|
||||||
y2="19.78"
|
|
||||||
/>
|
|
||||||
<line
|
|
||||||
x1="1"
|
|
||||||
y1="12"
|
|
||||||
x2="3"
|
|
||||||
y2="12"
|
|
||||||
/>
|
|
||||||
<line
|
|
||||||
x1="21"
|
|
||||||
y1="12"
|
|
||||||
x2="23"
|
|
||||||
y2="12"
|
|
||||||
/>
|
|
||||||
<line
|
|
||||||
x1="4.22"
|
|
||||||
y1="19.78"
|
|
||||||
x2="5.64"
|
|
||||||
y2="18.36"
|
|
||||||
/>
|
|
||||||
<line
|
|
||||||
x1="18.36"
|
|
||||||
y1="5.64"
|
|
||||||
x2="19.78"
|
|
||||||
y2="4.22"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ThemeSwitch = ({
|
|
||||||
width = 48,
|
|
||||||
height = 48,
|
|
||||||
}: {
|
|
||||||
width?: number;
|
|
||||||
height?: number;
|
|
||||||
}) => {
|
|
||||||
const mode = useSelector((state: any) => state.ui.mode);
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const handleChange = () => {
|
|
||||||
dispatch(setMode(mode === "light" ? "dark" : "light"));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<IconButton
|
|
||||||
id="theme-toggle"
|
|
||||||
title={t("common.buttons.toggleTheme")}
|
|
||||||
className={`theme-${mode}`}
|
|
||||||
aria-label="auto"
|
|
||||||
aria-live="polite"
|
|
||||||
onClick={handleChange}
|
|
||||||
sx={{
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SunAndMoonIcon />
|
|
||||||
</IconButton>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
export { ButtonInput as Button } from "./Button";
|
|
||||||
export { ButtonGroupInput as ButtonGroup } from "./ButtonGroup";
|
|
||||||
export { TextInput } from "./TextInput";
|
|
||||||
export { SelectInput as Select } from "./Select";
|
|
||||||
export { LanguageSelector } from "./LanguageSelector";
|
|
||||||
export { ThemeSwitch } from "./ThemeSwitch";
|
|
||||||
export { TextLink } from "./TextLink";
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { Outlet } from "react-router";
|
|
||||||
import Stack from "@mui/material/Stack";
|
|
||||||
import { SideBar } from "@/Components/v2/Layouts/Sidebar";
|
|
||||||
import { useTheme } from "@mui/material/styles";
|
|
||||||
const RootLayout = () => {
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<Stack
|
|
||||||
direction="row"
|
|
||||||
minHeight="100vh"
|
|
||||||
>
|
|
||||||
<SideBar />
|
|
||||||
<Stack
|
|
||||||
flex={1}
|
|
||||||
padding={theme.spacing(12)}
|
|
||||||
>
|
|
||||||
<Outlet />
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RootLayout;
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { useTheme } from "@mui/material/styles";
|
|
||||||
|
|
||||||
import Stack from "@mui/material/Stack";
|
|
||||||
|
|
||||||
export const BottomControls = ({}) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<Stack
|
|
||||||
direction="row"
|
|
||||||
height={50}
|
|
||||||
py={theme.spacing(4)}
|
|
||||||
px={theme.spacing(8)}
|
|
||||||
gap={theme.spacing(2)}
|
|
||||||
></Stack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
import IconButton from "@mui/material/IconButton";
|
|
||||||
import { ArrowRight } from "@/Components/v2/Arrows/ArrowRight";
|
|
||||||
import { ArrowLeft } from "@/Components/v2/Arrows/ArrowLeft";
|
|
||||||
import { useTheme } from "@mui/material/styles";
|
|
||||||
import { useDispatch } from "react-redux";
|
|
||||||
import { toggleSidebar } from "../../../../Features/UI/uiSlice.js";
|
|
||||||
|
|
||||||
export const CollapseButton = ({ collapsed }: { collapsed: boolean }) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const arrowIcon = collapsed ? (
|
|
||||||
<ArrowRight
|
|
||||||
height={theme.spacing(8)}
|
|
||||||
width={theme.spacing(8)}
|
|
||||||
color={theme.palette.primary.contrastTextSecondary}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<ArrowLeft
|
|
||||||
height={theme.spacing(8)}
|
|
||||||
width={theme.spacing(8)}
|
|
||||||
color={theme.palette.primary.contrastTextSecondary}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<IconButton
|
|
||||||
sx={{
|
|
||||||
position: "absolute",
|
|
||||||
/* TODO 60 is a magic number. if logo chnges size this might break */
|
|
||||||
top: 60,
|
|
||||||
right: 0,
|
|
||||||
transform: `translate(50%, 0)`,
|
|
||||||
backgroundColor: theme.palette.tertiary.main,
|
|
||||||
border: `1px solid ${theme.palette.primary.lowContrast}`,
|
|
||||||
p: theme.spacing(2.5),
|
|
||||||
|
|
||||||
"&:focus": { outline: "none" },
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: theme.palette.primary.lowContrast,
|
|
||||||
borderColor: theme.palette.primary.lowContrast,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
|
||||||
dispatch(toggleSidebar());
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{arrowIcon}
|
|
||||||
</IconButton>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
import Stack from "@mui/material/Stack";
|
|
||||||
import Box from "@mui/material/Box";
|
|
||||||
import Typography from "@mui/material/Typography";
|
|
||||||
import { useTheme } from "@mui/material/styles";
|
|
||||||
import { useNavigate } from "react-router";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
export const Logo = ({ collapsed }: { collapsed: boolean }) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const theme = useTheme();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack
|
|
||||||
pt={theme.spacing(6)}
|
|
||||||
pb={theme.spacing(12)}
|
|
||||||
pl={theme.spacing(8)}
|
|
||||||
direction="row"
|
|
||||||
alignItems="center"
|
|
||||||
gap={theme.spacing(4)}
|
|
||||||
onClick={() => navigate("/")}
|
|
||||||
sx={{ cursor: "pointer" }}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
pl={theme.spacing("1px")}
|
|
||||||
minWidth={theme.spacing(16)}
|
|
||||||
minHeight={theme.spacing(16)}
|
|
||||||
display={"flex"}
|
|
||||||
justifyContent={"center"}
|
|
||||||
alignItems={"center"}
|
|
||||||
bgcolor={theme.palette.accent.main}
|
|
||||||
borderRadius={theme.shape.borderRadius}
|
|
||||||
color={theme.palette.accent.contrastText}
|
|
||||||
fontSize={18}
|
|
||||||
>
|
|
||||||
C
|
|
||||||
</Typography>
|
|
||||||
<Box
|
|
||||||
overflow={"hidden"}
|
|
||||||
sx={{
|
|
||||||
transition: "opacity 900ms ease, width 900ms ease",
|
|
||||||
opacity: collapsed ? 0 : 1,
|
|
||||||
whiteSpace: "nowrap",
|
|
||||||
width: collapsed ? 0 : "100%",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{" "}
|
|
||||||
<Typography
|
|
||||||
lineHeight={1}
|
|
||||||
mt={theme.spacing(2)}
|
|
||||||
color={theme.palette.primary.contrastText}
|
|
||||||
variant="h2"
|
|
||||||
>
|
|
||||||
{t("common.appName")}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
// import Notifications from "@/assets/icons/notifications.svg?react";
|
|
||||||
import Monitors from "@/assets/icons/monitors.svg?react";
|
|
||||||
// import PageSpeed from "@/assets/icons/page-speed.svg?react";
|
|
||||||
// import Integrations from "@/assets/icons/integrations.svg?react";
|
|
||||||
// import Incidents from "@/assets/icons/incidents.svg?react";
|
|
||||||
// import StatusPages from "@/assets/icons/status-pages.svg?react";
|
|
||||||
// import Maintenance from "@/assets/icons/maintenance.svg?react";
|
|
||||||
// import Logs from "@/assets/icons/logs.svg?react";
|
|
||||||
// import Settings from "@/assets/icons/settings.svg?react";
|
|
||||||
import Support from "@/assets/icons/support.svg?react";
|
|
||||||
import Discussions from "@/assets/icons/discussions.svg?react";
|
|
||||||
import Docs from "@/assets/icons/docs.svg?react";
|
|
||||||
import ChangeLog from "@/assets/icons/changeLog.svg?react";
|
|
||||||
|
|
||||||
export const getMenu = (t: Function) => [
|
|
||||||
{ name: t("menu.uptime"), path: "v2/uptime", icon: <Monitors /> },
|
|
||||||
// { name: t("menu.pagespeed"), path: "pagespeed", icon: <PageSpeed /> },
|
|
||||||
|
|
||||||
// { name: t("menu.infrastructure"), path: "infrastructure", icon: <Integrations /> },
|
|
||||||
// {
|
|
||||||
// name: t("menu.notifications"),
|
|
||||||
// path: "notifications",
|
|
||||||
// icon: <Notifications />,
|
|
||||||
// },
|
|
||||||
// { name: t("menu.incidents"), path: "incidents", icon: <Incidents /> },
|
|
||||||
|
|
||||||
// { name: t("menu.statusPages"), path: "status", icon: <StatusPages /> },
|
|
||||||
// { name: t("menu.maintenance"), path: "maintenance", icon: <Maintenance /> },
|
|
||||||
// { name: t("menu.logs"), path: "logs", icon: <Logs /> },
|
|
||||||
|
|
||||||
// {
|
|
||||||
// name: t("menu.settings"),
|
|
||||||
// icon: <Settings />,
|
|
||||||
// path: "settings",
|
|
||||||
// },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const getBottomMenu = (t: Function) => [
|
|
||||||
{ name: t("menu.support"), path: "support", icon: <Support />, url: "invite" },
|
|
||||||
{
|
|
||||||
name: t("menu.discussions"),
|
|
||||||
path: "discussions",
|
|
||||||
icon: <Discussions />,
|
|
||||||
url: "https://github.com/bluewave-labs/checkmate/discussions",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: t("menu.docs"),
|
|
||||||
path: "docs",
|
|
||||||
icon: <Docs />,
|
|
||||||
url: "https://bluewavelabs.gitbook.io/checkmate",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: t("menu.changelog"),
|
|
||||||
path: "changelog",
|
|
||||||
icon: <ChangeLog />,
|
|
||||||
url: "https://github.com/bluewave-labs/checkmate/releases",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
import Tooltip from "@mui/material/Tooltip";
|
|
||||||
import ListItemButton from "@mui/material/ListItemButton";
|
|
||||||
import ListItemIcon from "@mui/material/ListItemIcon";
|
|
||||||
import Box from "@mui/material/Box";
|
|
||||||
import Typography from "@mui/material/Typography";
|
|
||||||
import { useTheme } from "@mui/material/styles";
|
|
||||||
|
|
||||||
export interface NavData {
|
|
||||||
name: string;
|
|
||||||
icon: JSX.Element;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const NavItem = ({
|
|
||||||
item,
|
|
||||||
collapsed,
|
|
||||||
selected,
|
|
||||||
onClick,
|
|
||||||
}: {
|
|
||||||
item: NavData;
|
|
||||||
collapsed: boolean;
|
|
||||||
selected: boolean;
|
|
||||||
onClick: (event: React.MouseEvent) => void;
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const iconStroke = selected
|
|
||||||
? theme.palette.primary.contrastText
|
|
||||||
: theme.palette.primary.contrastTextTertiary;
|
|
||||||
|
|
||||||
const buttonBgColor = selected ? theme.palette.secondary.main : "transparent";
|
|
||||||
const buttonBgHoverColor = selected
|
|
||||||
? theme.palette.secondary.main
|
|
||||||
: theme.palette.tertiary.main;
|
|
||||||
const fontWeight = selected ? 600 : 400;
|
|
||||||
return (
|
|
||||||
<Tooltip
|
|
||||||
placement="right"
|
|
||||||
title={collapsed ? item.name : ""}
|
|
||||||
slotProps={{
|
|
||||||
popper: {
|
|
||||||
modifiers: [
|
|
||||||
{
|
|
||||||
name: "offset",
|
|
||||||
options: {
|
|
||||||
offset: [0, -16],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
disableInteractive
|
|
||||||
>
|
|
||||||
<ListItemButton
|
|
||||||
sx={{
|
|
||||||
backgroundColor: buttonBgColor,
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: buttonBgHoverColor,
|
|
||||||
},
|
|
||||||
height: 37,
|
|
||||||
gap: theme.spacing(4),
|
|
||||||
borderRadius: theme.shape.borderRadius,
|
|
||||||
px: theme.spacing(4),
|
|
||||||
pl: theme.spacing(5),
|
|
||||||
}}
|
|
||||||
onClick={onClick}
|
|
||||||
>
|
|
||||||
<ListItemIcon
|
|
||||||
sx={{
|
|
||||||
minWidth: 0,
|
|
||||||
"& svg": {
|
|
||||||
height: 20,
|
|
||||||
width: 20,
|
|
||||||
opacity: 0.81,
|
|
||||||
},
|
|
||||||
"& svg path": {
|
|
||||||
stroke: iconStroke,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{item.icon}
|
|
||||||
</ListItemIcon>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
overflow: "hidden",
|
|
||||||
transition: "opacity 900ms ease",
|
|
||||||
opacity: collapsed ? 0 : 1,
|
|
||||||
whiteSpace: "nowrap",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
variant="body1"
|
|
||||||
color={theme.palette.primary.contrastText}
|
|
||||||
sx={{
|
|
||||||
fontWeight: fontWeight,
|
|
||||||
opacity: 0.9,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{item.name}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</ListItemButton>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
import { useEffect } from "react";
|
|
||||||
import { useSelector, useDispatch } from "react-redux";
|
|
||||||
import { setCollapsed } from "@/Features/UI/uiSlice";
|
|
||||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
|
||||||
import { useTheme } from "@mui/material/styles";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { useNavigate, useLocation } from "react-router-dom";
|
|
||||||
|
|
||||||
import { CollapseButton } from "@/Components/v2/Layouts/Sidebar/CollapseButton";
|
|
||||||
import Stack from "@mui/material/Stack";
|
|
||||||
import List from "@mui/material/List";
|
|
||||||
import Divider from "@mui/material/Divider";
|
|
||||||
import { Logo } from "@/Components/v2/Layouts/Sidebar/Logo";
|
|
||||||
import { getMenu, getBottomMenu } from "@/Components/v2/Layouts/Sidebar/Menu";
|
|
||||||
import { NavItem } from "@/Components/v2/Layouts/Sidebar/NavItem";
|
|
||||||
import { BottomControls } from "@/Components/v2/Layouts/Sidebar/BottomControls";
|
|
||||||
|
|
||||||
export const COLLAPSED_WIDTH = 64;
|
|
||||||
export const EXPANDED_WIDTH = 250;
|
|
||||||
|
|
||||||
export const SideBar = () => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const collapsed = useSelector((state: any) => state.ui.sidebar.collapsed);
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const location = useLocation();
|
|
||||||
const menu = getMenu(t);
|
|
||||||
const bottomMenu = getBottomMenu(t);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
dispatch(setCollapsed({ collapsed: isSmall }));
|
|
||||||
}, [isSmall]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack
|
|
||||||
component="aside"
|
|
||||||
position="sticky"
|
|
||||||
top={0}
|
|
||||||
minHeight={"100vh"}
|
|
||||||
maxHeight={"100vh"}
|
|
||||||
paddingTop={theme.spacing(6)}
|
|
||||||
paddingBottom={theme.spacing(6)}
|
|
||||||
gap={theme.spacing(6)}
|
|
||||||
borderRight={`1px solid ${theme.palette.primary.lowContrast}`}
|
|
||||||
width={collapsed ? COLLAPSED_WIDTH : EXPANDED_WIDTH}
|
|
||||||
sx={{
|
|
||||||
transition: "width 650ms cubic-bezier(0.36, -0.01, 0, 0.77)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CollapseButton collapsed={collapsed} />
|
|
||||||
<Logo collapsed={collapsed} />
|
|
||||||
<List
|
|
||||||
component="nav"
|
|
||||||
disablePadding
|
|
||||||
sx={{
|
|
||||||
px: theme.spacing(6),
|
|
||||||
height: "100%",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{menu.map((item) => {
|
|
||||||
const selected = location.pathname.startsWith(`/${item.path}`);
|
|
||||||
return (
|
|
||||||
<NavItem
|
|
||||||
key={item.path}
|
|
||||||
item={item}
|
|
||||||
collapsed={collapsed}
|
|
||||||
selected={selected}
|
|
||||||
onClick={() => navigate(`/${item.path}`)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</List>
|
|
||||||
<List
|
|
||||||
component="nav"
|
|
||||||
disablePadding
|
|
||||||
sx={{
|
|
||||||
px: theme.spacing(6),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{bottomMenu.map((item) => {
|
|
||||||
const selected = location.pathname.startsWith(`/${item.path}`);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<NavItem
|
|
||||||
key={item.path}
|
|
||||||
item={item}
|
|
||||||
collapsed={collapsed}
|
|
||||||
selected={selected}
|
|
||||||
onClick={() => {
|
|
||||||
if (item.url) {
|
|
||||||
window.open(item.url, "_blank", "noreferrer");
|
|
||||||
} else {
|
|
||||||
navigate(`/${item.path}`);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</List>
|
|
||||||
<Divider sx={{ mt: "auto", borderColor: theme.palette.primary.lowContrast }} />
|
|
||||||
<BottomControls />
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
import Stack from "@mui/material/Stack";
|
|
||||||
import { MonitorStatus } from "@/Components/v2/Monitors/MonitorStatus";
|
|
||||||
import { ButtonGroup, Button } from "@/Components/v2/Inputs";
|
|
||||||
import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined";
|
|
||||||
import PauseOutlinedIcon from "@mui/icons-material/PauseOutlined";
|
|
||||||
import PlayArrowOutlinedIcon from "@mui/icons-material/PlayArrowOutlined";
|
|
||||||
import EmailIcon from "@mui/icons-material/Email";
|
|
||||||
import BugReportOutlinedIcon from "@mui/icons-material/BugReportOutlined";
|
|
||||||
|
|
||||||
import { useMediaQuery } from "@mui/material";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { useTheme } from "@mui/material/styles";
|
|
||||||
|
|
||||||
import type { IMonitor } from "@/Types/Monitor";
|
|
||||||
|
|
||||||
export const HeaderControls = ({
|
|
||||||
monitor,
|
|
||||||
patch,
|
|
||||||
isPatching,
|
|
||||||
refetch,
|
|
||||||
}: {
|
|
||||||
monitor: IMonitor;
|
|
||||||
patch: Function;
|
|
||||||
isPatching: boolean;
|
|
||||||
refetch: Function;
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const theme = useTheme();
|
|
||||||
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack
|
|
||||||
direction={isSmall ? "column" : "row"}
|
|
||||||
spacing={isSmall ? theme.spacing(4) : 0}
|
|
||||||
justifyContent={"space-between"}
|
|
||||||
>
|
|
||||||
<MonitorStatus monitor={monitor} />
|
|
||||||
<Stack
|
|
||||||
direction={"row"}
|
|
||||||
spacing={theme.spacing(2)}
|
|
||||||
>
|
|
||||||
<ButtonGroup
|
|
||||||
orientation={isSmall ? "vertical" : "horizontal"}
|
|
||||||
fullWidth={isSmall}
|
|
||||||
variant="contained"
|
|
||||||
color="secondary"
|
|
||||||
>
|
|
||||||
<Button startIcon={<EmailIcon />}>{t("sendTestNotifications")}</Button>
|
|
||||||
<Button startIcon={<BugReportOutlinedIcon />}>{t("menu.incidents")}</Button>
|
|
||||||
<Button
|
|
||||||
loading={isPatching}
|
|
||||||
onClick={async () => {
|
|
||||||
await patch(`/monitors/${monitor._id}/active`);
|
|
||||||
refetch();
|
|
||||||
}}
|
|
||||||
startIcon={
|
|
||||||
monitor?.isActive ? <PauseOutlinedIcon /> : <PlayArrowOutlinedIcon />
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{monitor?.isActive ? t("pause") : t("resume")}
|
|
||||||
</Button>
|
|
||||||
<Button startIcon={<SettingsOutlinedIcon />}>{t("configure")}</Button>
|
|
||||||
</ButtonGroup>
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import Stack from "@mui/material/Stack";
|
|
||||||
import { Button } from "@/Components/v2/Inputs";
|
|
||||||
import { useTheme } from "@mui/material/styles";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { useNavigate } from "react-router";
|
|
||||||
export const HeaderCreate = ({
|
|
||||||
label,
|
|
||||||
isLoading,
|
|
||||||
path,
|
|
||||||
}: {
|
|
||||||
label?: string;
|
|
||||||
isLoading: boolean;
|
|
||||||
path: string;
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
return (
|
|
||||||
<Stack
|
|
||||||
direction="row"
|
|
||||||
justifyContent="end"
|
|
||||||
alignItems="center"
|
|
||||||
gap={theme.spacing(6)}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
loading={isLoading}
|
|
||||||
variant="contained"
|
|
||||||
color="accent"
|
|
||||||
onClick={() => navigate(path)}
|
|
||||||
>
|
|
||||||
{label || t("createNew")}
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
import Stack from "@mui/material/Stack";
|
|
||||||
import { ButtonGroup, Button } from "@/Components/v2/Inputs";
|
|
||||||
import { useTheme } from "@mui/material/styles";
|
|
||||||
import Typography from "@mui/material/Typography";
|
|
||||||
import { useMediaQuery } from "@mui/material";
|
|
||||||
export const HeaderRange = ({
|
|
||||||
range,
|
|
||||||
setRange,
|
|
||||||
loading,
|
|
||||||
}: {
|
|
||||||
range: string;
|
|
||||||
setRange: Function;
|
|
||||||
loading: boolean;
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
|
|
||||||
return (
|
|
||||||
<Stack
|
|
||||||
gap={theme.spacing(9)}
|
|
||||||
direction={isSmall ? "column" : "row"}
|
|
||||||
alignItems={"center"}
|
|
||||||
justifyContent="flex-end"
|
|
||||||
>
|
|
||||||
<Typography variant="body2">{`Showing statistics for past ${range}`}</Typography>
|
|
||||||
<ButtonGroup
|
|
||||||
orientation={isSmall ? "vertical" : "horizontal"}
|
|
||||||
fullWidth={isSmall}
|
|
||||||
variant="contained"
|
|
||||||
color={"primary"}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
color={range === "2h" ? "secondary" : "inherit"}
|
|
||||||
onClick={() => setRange("2h")}
|
|
||||||
loading={loading}
|
|
||||||
>
|
|
||||||
Recent
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
color={range === "24h" ? "secondary" : "inherit"}
|
|
||||||
onClick={() => setRange("24h")}
|
|
||||||
loading={loading}
|
|
||||||
>
|
|
||||||
Day
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
color={range === "7d" ? "secondary" : "inherit"}
|
|
||||||
onClick={() => setRange("7d")}
|
|
||||||
loading={loading}
|
|
||||||
>
|
|
||||||
7 days
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
color={range === "30d" ? "secondary" : "inherit"}
|
|
||||||
onClick={() => setRange("30d")}
|
|
||||||
loading={loading}
|
|
||||||
>
|
|
||||||
30 days
|
|
||||||
</Button>
|
|
||||||
</ButtonGroup>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
import Stack from "@mui/material/Stack";
|
|
||||||
import Box from "@mui/material/Box";
|
|
||||||
import { useTheme } from "@mui/material/styles";
|
|
||||||
import type { Check } from "@/Types/Check";
|
|
||||||
import { HistogramResponseTimeTooltip } from "@/Components/v2/Monitors/HistogramResponseTimeTooltip";
|
|
||||||
import { normalizeResponseTimes } from "@/Utils/DataUtils";
|
|
||||||
|
|
||||||
export const HistogramResponseTime = ({ checks }: { checks: Check[] }) => {
|
|
||||||
const normalChecks = normalizeResponseTimes(checks, "responseTime");
|
|
||||||
let data = Array<any>();
|
|
||||||
|
|
||||||
if (!normalChecks || normalChecks.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (normalChecks.length !== 25) {
|
|
||||||
const placeholders = Array(25 - normalChecks.length).fill("placeholder");
|
|
||||||
data = [...normalChecks, ...placeholders];
|
|
||||||
} else {
|
|
||||||
data = normalChecks;
|
|
||||||
}
|
|
||||||
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack
|
|
||||||
direction="row"
|
|
||||||
flexWrap="nowrap"
|
|
||||||
gap={theme.spacing(1.5)}
|
|
||||||
height="50px"
|
|
||||||
width="fit-content"
|
|
||||||
onClick={(event) => event.stopPropagation()}
|
|
||||||
sx={{
|
|
||||||
cursor: "default",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{data.map((check, index) => {
|
|
||||||
if (check === "placeholder") {
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
key={`${check}-${index}`}
|
|
||||||
position="relative"
|
|
||||||
width={theme.spacing(4.5)}
|
|
||||||
height="100%"
|
|
||||||
bgcolor={theme.palette.primary.lowContrast}
|
|
||||||
sx={{
|
|
||||||
borderRadius: theme.spacing(1.5),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<HistogramResponseTimeTooltip
|
|
||||||
key={`${check}-${index}`}
|
|
||||||
check={check}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
position="relative"
|
|
||||||
width="9px"
|
|
||||||
height="100%"
|
|
||||||
bgcolor={theme.palette.primary.lowContrast}
|
|
||||||
sx={{
|
|
||||||
borderRadius: theme.spacing(1.5),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
position="absolute"
|
|
||||||
bottom={0}
|
|
||||||
width="100%"
|
|
||||||
height={`${check.normalResponseTime}%`}
|
|
||||||
bgcolor={
|
|
||||||
check.status
|
|
||||||
? theme.palette.success.lowContrast
|
|
||||||
: theme.palette.error.lowContrast
|
|
||||||
}
|
|
||||||
sx={{
|
|
||||||
borderRadius: theme.spacing(1.5),
|
|
||||||
transition: "height 600ms cubic-bezier(0.4, 0, 0.2, 1)",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</HistogramResponseTimeTooltip>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import Stack from "@mui/material/Stack";
|
|
||||||
import Tooltip from "@mui/material/Tooltip";
|
|
||||||
import Typography from "@mui/material/Typography";
|
|
||||||
import { formatDateWithTz } from "@/Utils/TimeUtils";
|
|
||||||
import { useSelector } from "react-redux";
|
|
||||||
import type { LatestCheck } from "@/Types/Check";
|
|
||||||
|
|
||||||
export const HistogramResponseTimeTooltip: React.FC<{
|
|
||||||
children: React.ReactElement;
|
|
||||||
check: LatestCheck;
|
|
||||||
}> = ({ children, check }) => {
|
|
||||||
const uiTimezone = useSelector((state: any) => state.ui.timezone);
|
|
||||||
return (
|
|
||||||
<Tooltip
|
|
||||||
title={
|
|
||||||
<Stack>
|
|
||||||
<Typography>
|
|
||||||
{formatDateWithTz(check?.checkedAt, "ddd, MMMM D, YYYY, HH:mm A", uiTimezone)}
|
|
||||||
</Typography>
|
|
||||||
{check?.responseTime && (
|
|
||||||
<Typography>Response Time: {check.responseTime} ms</Typography>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,218 +0,0 @@
|
|||||||
import Stack from "@mui/material/Stack";
|
|
||||||
import Box from "@mui/material/Box";
|
|
||||||
import Typography from "@mui/material/Typography";
|
|
||||||
import { BaseBox } from "@/Components/v2/DesignElements";
|
|
||||||
import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts";
|
|
||||||
import UptimeIcon from "@/assets/icons/uptime-icon.svg?react";
|
|
||||||
import IncidentsIcon from "@/assets/icons/incidents.svg?react";
|
|
||||||
|
|
||||||
import type { GroupedCheck } from "@/Types/Check";
|
|
||||||
import type { MonitorStatus } from "@/Types/Monitor";
|
|
||||||
|
|
||||||
import { normalizeResponseTimes } from "@/Utils/DataUtils";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { formatDateWithTz } from "@/Utils/TimeUtils";
|
|
||||||
import { useSelector } from "react-redux";
|
|
||||||
import { useTheme } from "@mui/material/styles";
|
|
||||||
import { getResponseTimeColor } from "@/Utils/MonitorUtils";
|
|
||||||
|
|
||||||
const XLabel = ({
|
|
||||||
p1,
|
|
||||||
p2,
|
|
||||||
range,
|
|
||||||
}: {
|
|
||||||
p1: GroupedCheck;
|
|
||||||
p2: GroupedCheck;
|
|
||||||
range: string;
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const uiTimezone = useSelector((state: any) => state.ui.timezone);
|
|
||||||
const dateFormat = range === "day" ? "MMM D, h:mm A" : "MMM D";
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<text
|
|
||||||
x={0}
|
|
||||||
y="100%"
|
|
||||||
dy={-3}
|
|
||||||
textAnchor="start"
|
|
||||||
fontSize={11}
|
|
||||||
fill={theme.palette.primary.contrastTextTertiary}
|
|
||||||
>
|
|
||||||
{formatDateWithTz(p1._id, dateFormat, uiTimezone)}
|
|
||||||
</text>
|
|
||||||
<text
|
|
||||||
x="100%"
|
|
||||||
y="100%"
|
|
||||||
dy={-3}
|
|
||||||
textAnchor="end"
|
|
||||||
fontSize={11}
|
|
||||||
fill={theme.palette.primary.contrastTextTertiary}
|
|
||||||
>
|
|
||||||
{formatDateWithTz(p2._id, dateFormat, uiTimezone)}
|
|
||||||
</text>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
type BaseChartProps = React.PropsWithChildren<{
|
|
||||||
icon: React.ReactNode;
|
|
||||||
title: string;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export const BaseChart: React.FC<BaseChartProps> = ({ children, icon, title }) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BaseBox
|
|
||||||
sx={{
|
|
||||||
padding: theme.spacing(8),
|
|
||||||
display: "flex",
|
|
||||||
flex: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Stack
|
|
||||||
gap={theme.spacing(8)}
|
|
||||||
flex={1}
|
|
||||||
>
|
|
||||||
<Stack
|
|
||||||
direction="row"
|
|
||||||
alignItems={"center"}
|
|
||||||
gap={theme.spacing(4)}
|
|
||||||
>
|
|
||||||
<BaseBox
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
width: 34,
|
|
||||||
height: 34,
|
|
||||||
backgroundColor: theme.palette.tertiary.main,
|
|
||||||
"& svg": {
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
"& path": {
|
|
||||||
stroke: theme.palette.primary.contrastTextTertiary,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{icon}
|
|
||||||
</BaseBox>
|
|
||||||
<Typography variant="h2">{title}</Typography>
|
|
||||||
</Stack>
|
|
||||||
<Box flex={1}>{children}</Box>
|
|
||||||
</Stack>
|
|
||||||
</BaseBox>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const HistogramStatus = ({
|
|
||||||
checks,
|
|
||||||
status,
|
|
||||||
range,
|
|
||||||
title,
|
|
||||||
}: {
|
|
||||||
checks: GroupedCheck[];
|
|
||||||
status: MonitorStatus;
|
|
||||||
range: string;
|
|
||||||
title: string;
|
|
||||||
}) => {
|
|
||||||
const uiTimezone = useSelector((state: any) => state.ui.timezone);
|
|
||||||
|
|
||||||
const icon = status === "up" ? <UptimeIcon /> : <IncidentsIcon />;
|
|
||||||
const theme = useTheme();
|
|
||||||
const [idx, setIdx] = useState<number | null>(null);
|
|
||||||
const dateFormat = range === "1d" || range === "2h" ? "MMM D, h A" : "MMM D";
|
|
||||||
const normalChecks = normalizeResponseTimes(checks, "avgResponseTime");
|
|
||||||
|
|
||||||
if (normalChecks.length === 0) {
|
|
||||||
return (
|
|
||||||
<BaseChart
|
|
||||||
icon={icon}
|
|
||||||
title={title}
|
|
||||||
>
|
|
||||||
<Stack
|
|
||||||
height={"100%"}
|
|
||||||
alignItems={"center"}
|
|
||||||
justifyContent={"center"}
|
|
||||||
>
|
|
||||||
<Typography variant="h2">
|
|
||||||
{status === "up" ? "No checks yet" : "Great, no downtime yet!"}
|
|
||||||
</Typography>
|
|
||||||
</Stack>
|
|
||||||
</BaseChart>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalChecks = normalChecks.reduce((count, check) => {
|
|
||||||
return count + check.count;
|
|
||||||
}, 0);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BaseChart
|
|
||||||
icon={icon}
|
|
||||||
title={title}
|
|
||||||
>
|
|
||||||
<Stack gap={theme.spacing(8)}>
|
|
||||||
<Stack
|
|
||||||
position="relative"
|
|
||||||
direction="row"
|
|
||||||
justifyContent="space-between"
|
|
||||||
>
|
|
||||||
<Stack>
|
|
||||||
<Typography>Total checks</Typography>
|
|
||||||
{idx ? (
|
|
||||||
<Stack>
|
|
||||||
<Typography variant="h2">{normalChecks[idx].count}</Typography>
|
|
||||||
<Typography
|
|
||||||
position={"absolute"}
|
|
||||||
top={"100%"}
|
|
||||||
>
|
|
||||||
{formatDateWithTz(normalChecks[idx]._id, dateFormat, uiTimezone)}
|
|
||||||
</Typography>
|
|
||||||
</Stack>
|
|
||||||
) : (
|
|
||||||
<Typography variant="h2">{totalChecks}</Typography>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
<ResponsiveContainer
|
|
||||||
width="100%"
|
|
||||||
height={155}
|
|
||||||
>
|
|
||||||
<BarChart data={normalChecks}>
|
|
||||||
<XAxis
|
|
||||||
stroke={theme.palette.primary.lowContrast}
|
|
||||||
height={15}
|
|
||||||
tick={false}
|
|
||||||
label={
|
|
||||||
<XLabel
|
|
||||||
p1={normalChecks[0]}
|
|
||||||
p2={normalChecks[normalChecks.length - 1]}
|
|
||||||
range={range}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Bar
|
|
||||||
dataKey="normalResponseTime"
|
|
||||||
maxBarSize={7}
|
|
||||||
background={{ fill: "transparent" }}
|
|
||||||
>
|
|
||||||
{normalChecks?.map((groupedCheck, idx) => {
|
|
||||||
const fillColor = getResponseTimeColor(groupedCheck.normalResponseTime);
|
|
||||||
return (
|
|
||||||
<Cell
|
|
||||||
onMouseEnter={() => setIdx(idx)}
|
|
||||||
onMouseLeave={() => setIdx(null)}
|
|
||||||
key={groupedCheck._id}
|
|
||||||
fill={theme.palette[fillColor].main}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Bar>
|
|
||||||
</BarChart>
|
|
||||||
</ResponsiveContainer>
|
|
||||||
</Stack>
|
|
||||||
</BaseChart>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { HeaderCreate } from "./HeaderCreate";
|
|
||||||
+5
-2
@@ -2,7 +2,7 @@ import React, { useState } from "react";
|
|||||||
import Menu from "@mui/material/Menu";
|
import Menu from "@mui/material/Menu";
|
||||||
import MenuItem from "@mui/material/MenuItem";
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
import IconButton from "@mui/material/IconButton";
|
import IconButton from "@mui/material/IconButton";
|
||||||
import Settings from "@/assets/icons/settings-bold.svg?react";
|
import { Settings } from "lucide-react";
|
||||||
|
|
||||||
export type ActionMenuItem = {
|
export type ActionMenuItem = {
|
||||||
id: number | string;
|
id: number | string;
|
||||||
@@ -28,7 +28,10 @@ export const ActionsMenu = ({ items }: { items: ActionMenuItem[] }) => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<IconButton onClick={handleClick}>
|
<IconButton onClick={handleClick}>
|
||||||
<Settings />
|
<Settings
|
||||||
|
size={20}
|
||||||
|
strokeWidth={1.5}
|
||||||
|
/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Menu
|
<Menu
|
||||||
anchorEl={anchorEl}
|
anchorEl={anchorEl}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
import Box from "@mui/material/Box";
|
||||||
|
import { useTheme } from "@mui/material/styles";
|
||||||
|
import type { CheckSnapshot } from "@/Types/Check";
|
||||||
|
import { getResponseColor } from "@/Utils/DataUtils";
|
||||||
|
import { HeatmapResponseTimeTooltip } from "@/Components/v2/common/charts/HeatmapResponseTimeTooltip";
|
||||||
|
import type { SxProps } from "@mui/material/styles";
|
||||||
|
import type { ResponsiveStyleValue } from "@mui/system";
|
||||||
|
|
||||||
|
interface PlaceholderCheck {
|
||||||
|
status: "placeholder";
|
||||||
|
responseTime: 0;
|
||||||
|
createdAt: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HeatmapResponseTimeProps {
|
||||||
|
checks: CheckSnapshot[];
|
||||||
|
gap?: ResponsiveStyleValue<number | string>;
|
||||||
|
availabilityCellSx?: SxProps;
|
||||||
|
responseCellSx?: SxProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HeatmapResponseTime = ({
|
||||||
|
checks,
|
||||||
|
gap,
|
||||||
|
availabilityCellSx,
|
||||||
|
responseCellSx,
|
||||||
|
}: HeatmapResponseTimeProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
if (!gap) {
|
||||||
|
gap = theme.spacing(0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!checks || checks.length === 0) return null;
|
||||||
|
|
||||||
|
const latestChecks = checks.slice(-25).reverse();
|
||||||
|
|
||||||
|
let data: Array<CheckSnapshot | PlaceholderCheck>;
|
||||||
|
if (latestChecks.length !== 25) {
|
||||||
|
const placeholders = Array(25 - latestChecks.length).fill({
|
||||||
|
status: "placeholder" as const,
|
||||||
|
});
|
||||||
|
data = [...latestChecks, ...placeholders];
|
||||||
|
} else {
|
||||||
|
data = latestChecks;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Box sx={{ width: "100%" }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateColumns: "repeat(25, 1fr)",
|
||||||
|
gap: gap,
|
||||||
|
alignItems: "stretch",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{data.map((check, index) => {
|
||||||
|
const statusBg =
|
||||||
|
check.status === "placeholder"
|
||||||
|
? theme.palette.action.hover
|
||||||
|
: check.status === true
|
||||||
|
? theme.palette.success.light
|
||||||
|
: theme.palette.error.light;
|
||||||
|
const respBg =
|
||||||
|
check.status === "placeholder"
|
||||||
|
? theme.palette.action.hover
|
||||||
|
: getResponseColor(check.responseTime || 0, {
|
||||||
|
start: theme.palette.success.main,
|
||||||
|
mid: theme.palette.warning.main,
|
||||||
|
end: theme.palette.error.main,
|
||||||
|
});
|
||||||
|
const statusBorder =
|
||||||
|
check.status === "placeholder"
|
||||||
|
? theme.palette.divider
|
||||||
|
: check.status === true
|
||||||
|
? theme.palette.success.main
|
||||||
|
: theme.palette.error.main;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HeatmapResponseTimeTooltip
|
||||||
|
key={`${check}-${index}`}
|
||||||
|
check={check}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateRows: "auto auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: theme.spacing(2),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: "relative",
|
||||||
|
width: "100%",
|
||||||
|
aspectRatio: "10",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
inset: 0,
|
||||||
|
bgcolor: statusBg,
|
||||||
|
borderRadius: theme.spacing(0.5),
|
||||||
|
...availabilityCellSx,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
inset: 0,
|
||||||
|
pointerEvents: "none",
|
||||||
|
borderRadius: theme.spacing(0.5),
|
||||||
|
boxShadow: `inset 0 0 0 2px ${statusBorder}`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
aspectRatio: "1 / 1",
|
||||||
|
bgcolor: respBg,
|
||||||
|
borderRadius: theme.spacing(0.5),
|
||||||
|
...responseCellSx,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</HeatmapResponseTimeTooltip>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
Loaded 100 of 738 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user