Compare commits

...
Author SHA1 Message Date
Alexander Holliday e3ecfe6ad8 Merge pull request #3323 from bluewave-labs/develop
develop -> master
2026-02-20 12:38:33 -08:00
Alexander Holliday 288b677781 Merge pull request #3322 from bluewave-labs/fix/ts-error
ts error
2026-02-20 12:37:57 -08:00
Alex Holliday efcf2567b8 ts error 2026-02-20 20:37:29 +00:00
Alexander Holliday f4664eec16 Merge pull request #3321 from bluewave-labs/develop
develop->master
2026-02-20 12:20:49 -08:00
Alexander Holliday 3763b668ed Merge pull request #3320 from bluewave-labs/fix/no-cache
no cache
2026-02-20 12:20:12 -08:00
Alex Holliday 39c6d05196 no cache 2026-02-20 20:19:42 +00:00
Alexander Holliday 56f601e34b Merge pull request #3319 from bluewave-labs/develop
develop -> master
2026-02-20 12:07:29 -08:00
Alexander Holliday 7bf83c12af Merge pull request #3310 from bluewave-labs/fix/be/status-page-access
Fix: Authentication Middleware in StatusPage
2026-02-20 10:45:11 -08:00
Br0wnHammer e25669ac6a Fix: Client Redirect 2026-02-21 00:12:17 +05:30
Br0wnHammer f9653888b1 Fix: One Route 2026-02-21 00:10:39 +05:30
Alexander Holliday 921a1c5145 Merge pull request #3316 from bluewave-labs/fix/settings-refactor
fix/settings refactor
2026-02-20 10:29:46 -08:00
Alex Holliday d45e6ec97c remove v2 dir 2026-02-20 18:13:39 +00:00
Alex Holliday ef774b44f2 remove v1 theme and components 2026-02-20 18:10:43 +00:00
Alex Holliday 8d104ee30a remove v1 theme provider 2026-02-20 17:59:08 +00:00
Alex Holliday fc6300d057 remove old, add about and export 2026-02-20 17:36:04 +00:00
Alex Holliday 73f313e4d5 remove old settings strings 2026-02-20 17:22:43 +00:00
Alex Holliday 42fe6ade3e target blank 2026-02-20 17:22:20 +00:00
Alex Holliday b78b584844 add link text 2026-02-20 17:20:43 +00:00
Alex Holliday b64cf3f6b6 email 2026-02-19 19:23:40 +00:00
Br0wnHammer c27bb0e7f1 fix: build error 2026-02-20 00:52:36 +05:30
Br0wnHammer 1a52531faf feat: new middleware 2026-02-20 00:34:43 +05:30
Br0wnHammer 0974c84b8d feat: fe integration of new endpoint 2026-02-20 00:34:23 +05:30
Br0wnHammer 600c937062 feat: API Middleware 2026-02-20 00:31:42 +05:30
Br0wnHammer b87fd5438e feat: verifyStatusPage middleware 2026-02-20 00:30:06 +05:30
Alex Holliday 6a04ce5eee demo monitors, remove monitors 2026-02-19 18:26:40 +00:00
Br0wnHammer dae2709d31 Fix: Authentication Middleware in StatusPage 2026-02-19 23:55:43 +05:30
Alex Holliday 889bb07946 min values 2026-02-19 18:04:34 +00:00
Alex Holliday 9283890b5f 0 handling 2026-02-19 18:01:15 +00:00
Alex Holliday f2779160a1 port 2026-02-19 17:55:55 +00:00
Alex Holliday 216c7753b4 add global thresholds configbox 2026-02-19 17:33:28 +00:00
Alex Holliday a3a25b2add clear-stats 2026-02-19 17:28:00 +00:00
Alex Holliday b5f2a7b26c Fix pagespeed key issue 2026-02-18 21:06:29 +00:00
Alex Holliday d4e72ee8a6 url settigns 2026-02-18 21:02:08 +00:00
Alex Holliday a59f7fd830 pagespeed 2026-02-18 20:35:46 +00:00
Alex Holliday d8e7f9fc0f add pagespeed config 2026-02-18 20:20:45 +00:00
Alex Holliday cdbf5fd0c0 simplify form 2026-02-18 20:14:35 +00:00
Alex Holliday bbb2da3935 fix state type 2026-02-18 20:04:53 +00:00
Alex Holliday 248482a406 initial commit 2026-02-18 19:59:03 +00:00
Alexander Holliday 1c1474cd6f Merge pull request #3302 from bluewave-labs/fix/settings-form
fix: settings form
2026-02-18 11:25:34 -08:00
Alex Holliday db800e2259 simplify form 2026-02-18 19:23:03 +00:00
Alex Holliday b6765830a5 fix client/server validation 2026-02-18 19:13:52 +00:00
Alex Holliday 8cbde4a705 initial commit 2026-02-18 18:47:58 +00:00
Alexander Holliday 6fcaabbdb6 Merge pull request #3301 from bluewave-labs/fix/settings-form
fix logical errors in settings form
2026-02-18 10:12:44 -08:00
Alex Holliday 7cfbc85d9b fix logical errors in settings form 2026-02-18 18:06:00 +00:00
Alexander Holliday ab834d400e Merge pull request #3300 from bluewave-labs/fix/remove-constraint
fix: remove unique constraint
2026-02-18 09:45:15 -08:00
Alex Holliday 1fcf06a415 remove unique constraint 2026-02-18 17:43:32 +00:00
Alexander Holliday dab465b8c5 Merge pull request #3288 from bluewave-labs/settings-zod-schema
Settings zod schema
2026-02-18 09:32:28 -08:00
Alex Holliday 59d245b424 remove pre tag 2026-02-18 17:27:59 +00:00
karenvicent e921b20fe3 fix zod patterns 2026-02-17 22:21:56 -05:00
Gorkem Cetin 0e6680366d Remove Owaise from the contributing section 2026-02-17 19:53:24 -05:00
Alexander Holliday 7f7a9f40df Merge pull request #3298 from bluewave-labs/fix/monitor-stats-duplication
fix monitor stats bug
2026-02-17 16:08:44 -08:00
Alex Holliday 31698bd002 fix monitor stats bug 2026-02-18 00:06:02 +00:00
Alexander Holliday c7ccdafec2 Merge pull request #3297 from bluewave-labs/fix/avg-response-time-calc
fix: increment before calculate
2026-02-17 15:42:22 -08:00
Alex Holliday c920191984 increment before calculate 2026-02-17 23:40:14 +00:00
Alexander Holliday 374b6aa6ae Merge pull request #3296 from bluewave-labs/feat/max-response-time-rolling-stats
feat: max response time rolling stats
2026-02-17 15:34:35 -08:00
Alex Holliday 3d5c81fe2f Add maxResponseTime to client-side MonitorStats type 2026-02-17 22:50:44 +00:00
Alex Holliday 3bd6ec5d28 max response time 2026-02-17 22:47:37 +00:00
Alexander Holliday 7bb47ef432 Merge pull request #3295 from bluewave-labs/feat/cleanup-job
feat: cleanup job
2026-02-17 14:34:42 -08:00
Alex Holliday 7856317e8f remvoe space 2026-02-17 22:29:30 +00:00
Alex Holliday edbd8f2abb fix check bug 2026-02-17 22:27:52 +00:00
Alex Holliday cddf8b32e6 cleanup incidents 2026-02-17 19:39:24 +00:00
Alex Holliday 0032765bd1 remove orphaned checks 2026-02-17 19:20:08 +00:00
Alex Holliday 6bc9194e8f initial commit 2026-02-17 19:03:17 +00:00
Alexander Holliday ef15e5d6e0 Merge pull request #3294 from bluewave-labs/fix/remove-incidents-on-delete-monitor
fix: remove incidents on delete
2026-02-17 10:32:16 -08:00
Alex Holliday 97750da889 remove incidents on delete 2026-02-17 18:30:22 +00:00
Alexander Holliday af8083bdaa Merge pull request #3293 from bluewave-labs/fix/active-incidents
fix: add missing pluralization strings
2026-02-17 10:16:45 -08:00
Alexander Holliday 54b33ec835 Merge pull request #3292 from bluewave-labs/fix/incident-reason
fix/incident reason
2026-02-17 10:15:41 -08:00
Alex Holliday 847904a274 add missing pluralization strings 2026-02-17 18:15:30 +00:00
Alex Holliday 7bc03c976b remove comment 2026-02-17 17:53:00 +00:00
Alex Holliday 73f05ab78e refactor to remove duplication 2026-02-17 17:49:56 +00:00
Alex Holliday 584e4633d3 incident details 2026-02-17 17:42:11 +00:00
Alexander Holliday 433c57b42e Merge pull request #3291 from bluewave-labs/feat/notification-refactor
feat: notification refactor
2026-02-17 09:31:47 -08:00
Alexander Holliday a2cf2afc09 Merge pull request #3279 from bluewave-labs/enhancement/uptime-graph-avg-max-response
enhancement: add average and max response time to histogram
2026-02-17 09:31:25 -08:00
Alex Holliday 232be7a405 cleanup 2026-02-17 17:28:43 +00:00
mannilakash 7d66bb5668 fix: unnecessary custom styling and originalResponseTime usage 2026-02-17 10:27:22 -05:00
mannilakash 566fded55c Merge branch 'develop' of github.com:bluewave-labs/Checkmate into enhancement/uptime-graph-avg-max-response 2026-02-17 10:20:37 -05:00
Alex Holliday fbb0e940c6 emails 2026-02-17 01:47:57 +00:00
Alex Holliday f983863c72 new template 2026-02-17 01:25:30 +00:00
Alex Holliday e0a75c78db refactor discord 2026-02-17 01:16:00 +00:00
Alex Holliday 2cd2a6d917 pagerduty 2026-02-16 21:58:58 +00:00
Alex Holliday 6f55e4934f matrix 2026-02-16 21:49:42 +00:00
Alex Holliday 0e1dc3e3fe slack 2026-02-16 21:48:03 +00:00
Alex Holliday e18681606c add webhook 2026-02-16 20:55:00 +00:00
Alex Holliday d2d75cd035 initial commit 2026-02-16 20:18:25 +00:00
Alex Holliday a65515fc08 logger type 2026-02-16 19:42:10 +00:00
Alex Holliday 302ac6f4f1 update gitignore 2026-02-16 19:32:28 +00:00
Alexander Holliday b51d40fe77 Merge pull request #3290 from bluewave-labs/feat/incident-on-threshold-exception
Feat/incident on threshold exception
2026-02-16 10:47:24 -08:00
Alex Holliday 27f3def9bd correct logger type 2026-02-16 18:43:03 +00:00
Alex Holliday e7a7c0e603 remove grouping 2026-02-16 18:39:41 +00:00
Alex Holliday 55885ea007 remove global decision 2026-02-16 18:25:35 +00:00
karenvicent 475801e91c fix traslation object 2026-02-16 00:47:23 -05:00
karenvicent cd501e46cd Merge branch 'develop' into settings-zod-schema 2026-02-16 00:12:08 -05:00
karenvicent 93c15588a4 implement schema zod and validation on settings 2026-02-16 00:09:08 -05:00
Alex Holliday a8ecd44abf inject settings service, extract client host 2026-02-16 00:39:34 +00:00
Alex Holliday 412a3b7cc9 remove redundant method 2026-02-16 00:17:51 +00:00
Alex Holliday be8580ee99 update providers 2026-02-15 18:35:54 +00:00
Alex Holliday 8020bfde21 refactor handleIncidents 2026-02-15 18:20:42 +00:00
Alex Holliday 7061eb8eb0 use decision object in incidentService 2026-02-15 18:09:21 +00:00
Alex Holliday 70ae15725e conditinally render breached stauts box 2026-02-14 23:09:27 +00:00
Alex Holliday 32a7bd6543 add breached status to summary header 2026-02-14 22:57:27 +00:00
Alex Holliday 68db4b8701 breached status 2026-02-14 22:52:14 +00:00
Alex Holliday b75c1a4fcb create incident on hardware breach 2026-02-14 22:29:06 +00:00
Alex Holliday e50240885d debug logging 2026-02-14 22:25:15 +00:00
Alex Holliday 22645d86f7 handle infra thresholds 2026-02-14 22:14:51 +00:00
Alex Holliday 62778886e1 use counters 2026-02-14 21:55:47 +00:00
Alex Holliday 9c467c763d add threshold counters 2026-02-14 21:49:32 +00:00
Alex Holliday 97ace1e058 remove unused checks 2026-02-14 21:38:00 +00:00
Alex Holliday 2fb6cb0b6f remove alertThreshold 2026-02-14 21:29:30 +00:00
Alex Holliday a7b0ce4d8f remove thresholds 2026-02-14 21:21:18 +00:00
Alex Holliday 4a73170583 initial commit 2026-02-14 21:05:31 +00:00
Alexander Holliday 41310cb552 Merge pull request #3286 from bluewave-labs/feat/infra-thresholds
feat: infra thresholds
2026-02-13 17:23:49 -08:00
Alex Holliday 111b68b8df revert slider size change 2026-02-14 01:21:02 +00:00
Alex Holliday 2430bbeebb remove slider hover 2026-02-14 01:15:12 +00:00
Alex Holliday f694917dd4 remove unused 2026-02-14 01:07:46 +00:00
Alex Holliday 47b18e1d9d use sliderwithlabels 2026-02-14 01:02:15 +00:00
Alexander Holliday 0d863aefe8 Merge pull request #3285 from bluewave-labs/fix/create-monitors-port
fix port input
2026-02-13 16:45:01 -08:00
Alex Holliday c319738048 add config box for thresholds 2026-02-14 00:40:59 +00:00
Alex Holliday 002a82bb9a fix port input 2026-02-14 00:19:01 +00:00
Alexander Holliday 8719efab5d Merge pull request #3284 from bluewave-labs/fix/routes
fix: routes
2026-02-13 16:17:57 -08:00
Alex Holliday 86fd535eaf routes to TS 2026-02-14 00:08:28 +00:00
Alex Holliday 2f6dbe9a3a update gitignore 2026-02-13 23:56:35 +00:00
Alexander Holliday 649eaae712 Merge pull request #3283 from bluewave-labs/fix/string-cleanup
clean up strings
2026-02-13 13:22:34 -08:00
Alex Holliday 1888ee0478 remove unused monitor import 2026-02-13 21:19:55 +00:00
Alex Holliday fd2c54d3d3 remove unused 2026-02-13 20:42:26 +00:00
Alex Holliday bd941db771 clean up strings 2026-02-13 20:41:27 +00:00
Alexander Holliday 88fceddbbf Merge pull request #3282 from bluewave-labs/fix/monitor-stats
fix: initializing stats
2026-02-13 11:18:54 -08:00
Alex Holliday 13251ca7c5 fix initializing stats 2026-02-13 19:04:19 +00:00
Alexander Holliday 6d6b597f99 Merge pull request #3281 from bluewave-labs/fix/cleanup
fix: cleanup
2026-02-13 11:01:43 -08:00
Alex Holliday dfc1733820 remove icons 2026-02-13 18:51:29 +00:00
mannilakash 39a449e594 fix: padding fix for stats text 2026-02-13 11:14:25 -05:00
mannilakash 974d33b3de enhancement: add average and max response time for histogram 2026-02-13 10:32:52 -05:00
Alex Holliday afe2b9d3bb remove unused images 2026-02-12 23:37:08 +00:00
Alex Holliday 5ab5cbe622 status box fix 2026-02-12 23:31:37 +00:00
Alex Holliday b6b1b8464c remove old DB references 2026-02-12 23:03:16 +00:00
Alex Holliday f337ca5ff0 initial commit 2026-02-12 22:37:40 +00:00
Alexander Holliday f5bc8e78e4 Merge pull request #3278 from bluewave-labs/fix/failed-check-warn
Fix/failed check warn
2026-02-12 11:44:48 -08:00
Alex Holliday 8089e71f96 warn instead of throw on failure to build check 2026-02-12 19:42:58 +00:00
karenvicent 6195650dc1 Merge branch 'develop' into settings-zod-schema 2026-02-12 14:25:05 -05:00
Alex Holliday f600647ff0 fix ts error 2026-02-12 19:16:16 +00:00
Alex Holliday cafea2bf5d remove unused utils, convert utils to TS 2026-02-12 19:13:53 +00:00
Alexander Holliday 4e3a82d25f Merge pull request #3276 from bluewave-labs/fix/monitor-status
fix: monitor status
2026-02-12 10:32:38 -08:00
Alexander Holliday 33e0136a2a Merge pull request #3275 from bluewave-labs/fix/ts
Fix/ts
2026-02-12 10:32:22 -08:00
Alex Holliday b0b7b6a435 fix status change issue 2026-02-12 18:30:44 +00:00
Alex Holliday 85dbe34e00 store to ts 2026-02-12 18:30:15 +00:00
Alex Holliday f38b305ba7 ui slice to TS 2026-02-12 18:07:48 +00:00
Alexander Holliday f9c32cb19b Merge pull request #3274 from bluewave-labs/fix/css-cleanup
fix: clean up index.css
2026-02-12 10:02:39 -08:00
Alex Holliday b540e48aab clean up index.css 2026-02-12 18:01:12 +00:00
Alexander Holliday a50c2578e9 Merge pull request #3273 from bluewave-labs/fix/settings
fix: put -> patch
2026-02-12 09:28:40 -08:00
Alex Holliday 2e30b622d3 put -> patch 2026-02-12 17:26:17 +00:00
karenvicent f59d8bf1bf Merge branch 'develop' into settings-zod-schema 2026-02-12 12:18:05 -05:00
karenvicent 0941f3ce1a zod schema and hook 2026-02-12 12:13:36 -05:00
Alexander Holliday 3838b18931 Merge pull request #3270 from bluewave-labs/feat/monitor-status
feat: monitor status
2026-02-11 12:30:59 -08:00
Alex Holliday 71cdd05b15 type errors 2026-02-11 20:29:50 +00:00
Alex Holliday 4b1c588f5a extract header 2026-02-11 20:11:22 +00:00
Alex Holliday cbd8b5db47 update monitor status on maintenance 2026-02-11 19:38:32 +00:00
Alex Holliday 94afe19b37 update services/repositories to reflect new monitorstatus type 2026-02-11 19:22:35 +00:00
Alex Holliday d622bce6c1 update summary type 2026-02-11 19:00:04 +00:00
Alex Holliday 7b658a9e52 initial commit 2026-02-11 18:58:12 +00:00
Alex Holliday d296aec02e add monitor statuses 2026-02-11 18:44:21 +00:00
Alexander Holliday 82c8122a5a Merge pull request #3269 from bluewave-labs/fix/cleanup
fix: cleanup
2026-02-11 10:38:27 -08:00
Alex Holliday 446c791f88 avatar text color 2026-02-11 18:36:20 +00:00
Alex Holliday e214d9a54c sidebar tweak 2026-02-11 18:34:55 +00:00
Alex Holliday 6f6920f3b2 migrate protected routes 2026-02-11 18:15:26 +00:00
Alex Holliday de447d7be2 move admin check to regsiter page 2026-02-11 17:52:26 +00:00
Alex Holliday ee3062fcb4 remove unused components 2026-02-11 17:37:50 +00:00
Alex Holliday 6443802024 cleanup imports 2026-02-11 17:21:32 +00:00
Alexander Holliday d88c73bb20 Merge pull request #3267 from bluewave-labs/feat/v2-layout
feat: v2 layout
2026-02-11 09:09:28 -08:00
Alex Holliday 10bfdc9da1 hook up offline banner 2026-02-10 23:53:06 +00:00
Alex Holliday 0503c25663 cleanup 2026-02-10 23:32:58 +00:00
Alex Holliday 442fc2ce22 account tab fix 2026-02-10 23:24:51 +00:00
Alex Holliday 71f3c90472 strings 2026-02-10 23:16:12 +00:00
Alex Holliday 4c5c2d2d5e auth footer 2026-02-10 23:12:33 +00:00
Alex Holliday a390ee5f21 auth footer 2026-02-10 22:54:23 +00:00
Alex Holliday 0d98353625 auth footer 2026-02-10 22:47:39 +00:00
Alex Holliday 0e1b074c6c styling 2026-02-10 22:28:47 +00:00
Alex Holliday a289a197a1 starprompt 2026-02-10 22:24:35 +00:00
Alex Holliday 0f6c273dad bottom menu 2026-02-10 22:17:13 +00:00
Alex Holliday 1db5f4b70f sidebar 2026-02-10 21:54:31 +00:00
Alex Holliday d1d96bd2ba sidebar 2026-02-10 19:21:57 +00:00
Alex Holliday 7ef10b4fcd initial commit 2026-02-10 17:53:00 +00:00
Alexander Holliday a2dfbd29c7 Merge pull request #3266 from bluewave-labs/fix/cleanup
fix; cleanup
2026-02-10 09:33:33 -08:00
Alex Holliday 14ba3016f6 Merge branch 'develop' into fix/cleanup 2026-02-10 17:06:29 +00:00
Alexander Holliday ac98f783e3 Merge pull request #3257 from bluewave-labs/fix/search-input-debounce
fix: changes for debounce search in uptime/infrastructure pages
2026-02-10 07:18:46 -08:00
mannilakash 98943ad461 fix: removing JSdoc for usedebounce and comments 2026-02-10 08:10:44 -05:00
Alex Holliday 28dc61e1a6 v1 cleanup 2026-02-10 00:01:27 +00:00
Alex Holliday a20fdb423a remove unused 2026-02-09 23:51:18 +00:00
Alex Holliday 1236b9191e fix email config 2026-02-09 23:41:21 +00:00
Alex Holliday 34762ef475 remove network servcie 2026-02-09 23:37:07 +00:00
Alex Holliday b817a16459 remove settings hooks 2026-02-09 23:28:46 +00:00
Alex Holliday 2091d8dcd7 remove monitorHooks 2026-02-09 23:16:14 +00:00
Alex Holliday 3d1bd94279 remove unused hooks 2026-02-09 23:02:26 +00:00
Alex Holliday 07aad0204e update slice 2026-02-09 22:56:56 +00:00
Alexander Holliday 7c0d45d287 Merge pull request #3265 from bluewave-labs/feat/v2-not-found
feat: v2 not found
2026-02-09 14:23:56 -08:00
Alex Holliday 06c571d89f img -> box 2026-02-09 22:22:01 +00:00
Alex Holliday 9b60e32125 initial commit 2026-02-09 22:19:10 +00:00
Alexander Holliday c681074bb2 Merge pull request #3264 from bluewave-labs/feat/v2-set-new-password
feat: v2 set new password
2026-02-09 14:09:39 -08:00
Alex Holliday b47e17fe91 cleanup 2026-02-09 22:07:02 +00:00
Alex Holliday 28affa1cd1 submit 2026-02-09 21:56:28 +00:00
Alex Holliday f5372b4d86 ui and strings 2026-02-09 21:29:55 +00:00
Alex Holliday 4db4947220 basic UI 2026-02-09 21:21:17 +00:00
Alex Holliday 48fb422e0f initial commit 2026-02-09 21:00:27 +00:00
Alexander Holliday 20d4f2c092 Merge pull request #3262 from bluewave-labs/feat/v2-edit-user
feat: v2 edit user
2026-02-09 12:50:17 -08:00
Alex Holliday 48081d85ee cleanup 2026-02-09 20:48:12 +00:00
Alex Holliday ece6970fa7 hook up role 2026-02-09 20:38:04 +00:00
Alex Holliday 4434189c72 remove breadcrumb 2026-02-09 20:09:08 +00:00
Alex Holliday 197e075ba1 strings 2026-02-09 20:07:45 +00:00
Alex Holliday c4ab56095e strings 2026-02-09 19:46:11 +00:00
Alex Holliday 550d5cc555 initial commit 2026-02-09 19:26:58 +00:00
Alexander Holliday b08e69764c Merge pull request #3261 from bluewave-labs/feat/v2-account
feat: v2 account
2026-02-09 11:17:25 -08:00
Alex Holliday 08e4b0c39e remove unused 2026-02-09 19:15:22 +00:00
Alex Holliday a46e282633 role column 2026-02-09 18:57:24 +00:00
Alex Holliday 8b7fd826b3 createUser endpoint 2026-02-09 18:49:31 +00:00
Alex Holliday 264e6f7d1f form submission 2026-02-09 18:29:36 +00:00
Alex Holliday fafd581f06 add team member initial commit 2026-02-09 18:14:06 +00:00
Alex Holliday d69cec9730 dialog 2026-02-09 17:52:14 +00:00
mannilakash c670653ab3 fix: format fix 2026-02-09 12:40:44 -05:00
mannilakash e75606aad3 refactor: debounce.jsx to typescript and moving it to Hooks 2026-02-09 12:38:39 -05:00
mannilakash 07b0483352 fix: changes for debounce search in uptime/infrastructure pages 2026-02-09 10:43:29 -05:00
Alex Holliday 1552018796 optional additionalButtons 2026-02-08 16:30:51 +00:00
Alex Holliday 3fc4160b88 add buttons 2026-02-08 16:23:24 +00:00
Alex Holliday 9ca79ea793 team initial commit 2026-02-08 16:14:35 +00:00
Alex Holliday 2e3b32462f remove mongo hash middleware 2026-02-07 21:15:58 +00:00
Alex Holliday 7e527b8eb2 submit 2026-02-07 21:05:45 +00:00
Alex Holliday 021b4e8ae5 controllers 2026-02-07 20:57:03 +00:00
Alex Holliday b6004241e7 validation 2026-02-07 20:54:17 +00:00
Alex Holliday af4c9d8f91 fix unset 2026-02-07 20:33:11 +00:00
Alex Holliday f2b740a8f9 put -> patch 2026-02-07 20:17:59 +00:00
Alex Holliday b26d135a4f submit 2026-02-07 20:11:30 +00:00
Alex Holliday 4f4203dbb3 remove delete 2026-02-07 19:59:28 +00:00
Alex Holliday 4945b29334 wire up form 2026-02-07 01:13:58 +00:00
Alex Holliday 1808db5cd7 form 2026-02-07 01:04:20 +00:00
Alex Holliday 31cc9cd294 fix strings 2026-02-07 00:51:04 +00:00
Alex Holliday 19b370d89b initial commit 2026-02-07 00:49:49 +00:00
Alexander Holliday 8555e7c122 Merge pull request #3254 from bluewave-labs/feat/v2-register
feat/v2 register
2026-02-06 15:39:17 -08:00
Alex Holliday 734ec41ca0 cleanup 2026-02-06 23:37:25 +00:00
Alex Holliday 5152336591 fix validation, derrive teamID and Roles from tokens 2026-02-06 23:31:07 +00:00
Alex Holliday 78b920aaf6 missing fields 2026-02-06 23:07:17 +00:00
Alex Holliday 2d84fae470 form 2026-02-06 22:53:41 +00:00
Alex Holliday 97e4b105a2 Merge branch 'develop' into feat/v2-register 2026-02-06 22:35:19 +00:00
Alex Holliday a053386f4a ui 2026-02-06 22:34:50 +00:00
Alexander Holliday e0991e28e5 Merge pull request #3253 from bluewave-labs/feat/v2/password-reset
feat: v2/password reset
2026-02-06 14:22:37 -08:00
Alex Holliday 0f26648855 remove old page 2026-02-06 22:18:44 +00:00
Alex Holliday 6a0f3426b3 recovery form, fix email service 2026-02-06 22:14:42 +00:00
Alex Holliday 16e8035299 ui 2026-02-06 21:50:45 +00:00
Alexander Holliday 31b09c316a Merge pull request #3251 from bluewave-labs/feat/v2-auth-login
feat: v2 auth login
2026-02-06 13:36:27 -08:00
Alex Holliday 8f2072e497 cleanup 2026-02-06 21:35:25 +00:00
Alex Holliday 514570da78 login page refactor 2026-02-06 21:11:46 +00:00
Alex Holliday ea68e660fc login UI 2026-02-06 20:53:48 +00:00
Alex Holliday 527543d79f base page, auth header, language and theme switches 2026-02-06 20:32:10 +00:00
Alexander Holliday 1c390a8baf Merge pull request #3250 from bluewave-labs/fix/unit-formatting
Fix/unit formatting
2026-02-06 11:43:48 -08:00
Alex Holliday 43ac5b57cf time formatting 2026-02-06 19:42:06 +00:00
Alex Holliday 1558b27497 bytes and percentage 2026-02-06 19:25:53 +00:00
Alexander Holliday 9caa6c830f Merge pull request #3249 from bluewave-labs/feat/v2-logs
Feat/v2 logs
2026-02-06 11:00:31 -08:00
Alex Holliday ea115ede53 cleanup 2026-02-06 18:56:10 +00:00
Alex Holliday 8470631162 diagnostics 2026-02-06 18:46:06 +00:00
Alex Holliday 597732e109 stats 2026-02-06 18:20:26 +00:00
Alex Holliday 9974fa4010 headers 2026-02-06 17:24:16 +00:00
Alex Holliday b0686c0576 queue tab 2026-02-05 21:40:36 +00:00
Alex Holliday a148f56b22 logs tab 2026-02-05 20:23:04 +00:00
Alexander Holliday 3b25b37c22 Merge pull request #3247 from bluewave-labs/fix/empty
Fix: empty state
2026-02-05 10:59:44 -08:00
Alexander Holliday 1e04345aa0 Merge pull request #3185 from llpingll/change-password
feat: Allow all users to change password (not just admins)
2026-02-05 10:59:20 -08:00
Alexander Holliday 174c3cd47a Merge pull request #3246 from bluewave-labs/fix/alignment
Fix/alignment
2026-02-05 10:56:59 -08:00
Alex Holliday 0e149fa477 fix empty state 2026-02-05 18:56:54 +00:00
Alex Holliday 5b4a99af3d fix select alignment 2026-02-05 18:53:51 +00:00
Alex Holliday 2ecdc2096e empty field label 2026-02-05 18:49:47 +00:00
Alexander Holliday 6e66a18535 Merge pull request #3245 from bluewave-labs/fix/pagination-delete
fix: pagination and empty state fix
2026-02-05 10:33:49 -08:00
Alex Holliday bd6aa0fa3a pagination and empty state fix 2026-02-05 18:30:43 +00:00
Alexander Holliday 4e678a4ce7 Merge pull request #3244 from bluewave-labs/feat/husky-lint
feat: add husky
2026-02-05 09:55:50 -08:00
Alex Holliday 5999315791 add gitignore 2026-02-05 17:54:06 +00:00
Alexander Holliday 0c8b8a9358 Merge pull request #3243 from bluewave-labs/feat/v2-create-maintenance
feat: v2 create maintenance
2026-02-05 09:45:20 -08:00
Alex Holliday dfe62bd301 remove old 2026-02-05 17:29:27 +00:00
Alex Holliday 747ff824cc format 2026-02-05 17:23:14 +00:00
Alex Holliday f12c544751 validation fixes 2026-02-05 17:22:24 +00:00
llpingll 278ced2a6c Removed additional lines to avoid en.json conflict 2026-02-04 23:56:16 +00:00
llpingll 88d539b2a4 Hard coded strings to avoid en.json conflicts 2026-02-04 23:32:14 +00:00
Alex Holliday f0b152fc8f duration 2026-02-04 22:55:54 +00:00
Alex Holliday dc0e198a8e monitors 2026-02-04 22:17:04 +00:00
Alex Holliday 0a5067ef46 grab monitors 2026-02-04 22:10:52 +00:00
Alex Holliday 4f0017c031 strings 2026-02-04 21:47:56 +00:00
Alex Holliday f0b2292f0a general 2026-02-04 21:45:39 +00:00
Alexander Holliday d74d79c332 Merge pull request #3242 from bluewave-labs/feat/v2-maintenance
feat: v2 maintenance
2026-02-04 12:06:22 -08:00
Alex Holliday 1a096b66f7 remove unused 2026-02-04 20:03:05 +00:00
Alex Holliday 7cb866ce4a legacy put -> patch 2026-02-04 19:57:36 +00:00
Alex Holliday c54582ca70 format 2026-02-04 19:54:01 +00:00
Alex Holliday 362034ba26 pretty-ms 2026-02-04 19:53:19 +00:00
Alex Holliday b536099d03 create header 2026-02-04 19:40:32 +00:00
Alex Holliday 04fef2ef75 put -> patch 2026-02-04 19:30:41 +00:00
Alex Holliday a89e5ed565 MaintenanceWindow table 2026-02-04 19:22:05 +00:00
Alexander Holliday ca153b65a9 Merge pull request #3241 from bluewave-labs/fix/incidents-selector
fix/incidents selector
2026-02-04 10:31:57 -08:00
Alex Holliday cc188b2ba3 Merge branch 'develop' into fix/incidents-selector 2026-02-04 18:29:49 +00:00
Alexander Holliday 18018ceef1 Merge pull request #3240 from bluewave-labs/fix/invite-permisisons
fix/invite permisisons
2026-02-04 10:13:25 -08:00
Alex Holliday 4f3bcd4624 incident page fixed 2026-02-03 23:45:23 +00:00
Alex Holliday d469590ef3 filter fix 2026-02-03 23:37:16 +00:00
llpingll 090d002578 Add comment to PasswordPanel to encourage additional error handling and remove helperText comments | Remove explicit special characters from en.json 2026-02-03 16:44:15 +00:00
llpingll 69f95723f1 Combine useNavigate and useLocation into one import 2026-02-02 22:23:48 +00:00
Lui Duarte 464b16ec9d Merge branch 'develop' into change-password 2026-02-02 15:49:30 +00:00
Alexander Holliday 1244ff6d93 Merge pull request #3212 from bluewave-labs/develop
develop -> master
2026-01-27 13:58:48 -08:00
Alexander Holliday c0ea804c98 Merge pull request #3209 from bluewave-labs/develop
develop -> master
2026-01-27 13:40:25 -08:00
Alexander Holliday c21cf29db6 Merge pull request #3207 from bluewave-labs/develop
develop -> master
2026-01-27 13:20:18 -08:00
Alexander Holliday 3d0da70ce6 Merge pull request #3204 from bluewave-labs/develop
develop -> master
2026-01-27 13:09:47 -08:00
llpingll 04cf47544a Fix ForgotPassword format 2026-01-25 23:48:12 +00:00
Lui Duarte e1dfd98840 Merge branch 'develop' into change-password 2026-01-25 23:23:04 +00:00
llpingll 4ce4a124d6 Refactor PasswordPanel to pass navigation context via state to ForgotPassword using TextLink | Update ForgotPassword and CheckEmail to handle navigation context for Back links, ensuring users return to the correct page after password reset | Fix TextLink prop usage to avoid warnings by passing state and href correctly | Ensure error messages and navigation links use translation keys for localization 2026-01-25 22:57:36 +00:00
llpingll f564636cb4 Fix missingKey warning with helper text 2026-01-25 19:11:25 +00:00
llpingll 14a08a303b Fix helper text to correctly use translation to display message not key 2026-01-25 15:45:34 +00:00
llpingll 4645bde025 Allow all users to change password and handle expired sessions 2026-01-25 15:19:21 +00:00
Alexander Holliday feb061240b Merge pull request #3042 from bluewave-labs/develop
develp->master
2025-10-27 12:09:00 -07:00
Alexander Holliday a924227805 Merge pull request #2964 from bluewave-labs/develop
develop -> master
2025-09-18 13:40:45 -07:00
Alexander Holliday 2144d3f8c0 Merge pull request #2962 from bluewave-labs/develop
develop -> master
2025-09-18 12:04:51 -07:00
Alexander Holliday fcd6786149 Merge pull request #2936 from bluewave-labs/develop
develop -> master
2025-09-08 20:43:21 -07:00
Alexander Holliday ea968ebf3c Merge pull request #2933 from bluewave-labs/develop
develop -> master
2025-09-08 20:33:35 -07:00
Alexander Holliday 8a7b852974 Merge pull request #2929 from bluewave-labs/develop
develop -> master
2025-09-08 07:24:16 -07:00
Alexander Holliday c8d3e3b70c Merge pull request #2921 from bluewave-labs/develop
develop -> master
2025-09-04 15:46:30 -07:00
Alexander Holliday 7a90a6f9f4 Merge pull request #2915 from bluewave-labs/develop
develop->master
2025-09-01 10:33:09 -07:00
488 changed files with 10966 additions and 24484 deletions

No files matched your search

@@ -107,6 +107,7 @@ jobs:
context: .
file: ./docker/dist-arm/server.Dockerfile
push: true
no-cache: true
tags: |
ghcr.io/bluewave-labs/checkmate-backend-mono-multiarch:${{ steps.extract_tag.outputs.version }}
platforms: linux/amd64,linux/arm64
+1
View File
@@ -102,6 +102,7 @@ jobs:
context: .
file: ./docker/dist-arm/server.Dockerfile
push: true
no-cache: true
tags: |
ghcr.io/bluewave-labs/checkmate-backend-mono-multiarch:latest
platforms: linux/amd64,linux/arm64
+3
View File
@@ -3,3 +3,6 @@
.VSCodeCounter
*.sh
mongo
node_modules/
docs/architecture
docs/reviews
+4
View File
@@ -0,0 +1,4 @@
#!/bin/sh
echo "Running lint-staged..."
cd client && npx lint-staged && cd ..
cd server && npx lint-staged && cd ..
+1 -1
View File
@@ -147,7 +147,7 @@ If you would like to sponsor an additional feature, [see this page](https://chec
## Contributing
We are [Alex](http://github.com/ajhollid) (team lead), [Gorkem](http://github.com/gorkem-bwl/), [Owaise](http://github.com/Owaiseimdad), [Aryaman](https://github.com/Br0wnHammer), [Mert](https://github.com/mertssmnoglu) and [Karen](https://github.com/karenvicent) helping individuals and businesses monitor their infra and servers.
We are [Alex](http://github.com/ajhollid) (team lead), [Gorkem](http://github.com/gorkem-bwl/), [Aryaman](https://github.com/Br0wnHammer), [Mert](https://github.com/mertssmnoglu) and [Karen](https://github.com/karenvicent) helping individuals and businesses monitor their infra and servers.
We pride ourselves on building strong connections with contributors at every level. Despite being a young project, Checkmate has already earned 7000+ stars and attracted 90+ contributors from around the globe.
+1 -1
View File
@@ -18,7 +18,7 @@
<div id="root"></div>
<script
type="module"
src="/src/main.jsx"
src="/src/main.tsx"
></script>
</body>
</html>
+582 -4
View File
@@ -26,6 +26,7 @@
"joi": "17.13.3",
"lucide-react": "^0.562.0",
"mui-color-input": "^7.0.0",
"pretty-bytes": "^7.1.0",
"pretty-ms": "^9.3.0",
"react": "18.3.1",
"react-dom": "^18.2.0",
@@ -51,6 +52,7 @@
"eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.6",
"lint-staged": "^16.2.7",
"prettier": "^3.3.3",
"typescript": "5.9.3",
"vite": "6.3.6"
@@ -2277,6 +2279,22 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ansi-escapes": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz",
"integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==",
"dev": true,
"license": "MIT",
"dependencies": {
"environment": "^1.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"dev": true,
@@ -2454,11 +2472,13 @@
}
},
"node_modules/axios": {
"version": "1.13.4",
"version": "1.13.5",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
"integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
"follow-redirects": "^1.15.11",
"form-data": "^4.0.5",
"proxy-from-env": "^1.1.0"
}
},
@@ -2503,6 +2523,19 @@
"concat-map": "0.0.1"
}
},
"node_modules/braces": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"license": "MIT",
"dependencies": {
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/browserslist": {
"version": "4.28.1",
"funding": [
@@ -2628,6 +2661,39 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/cli-cursor": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
"integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
"dev": true,
"license": "MIT",
"dependencies": {
"restore-cursor": "^5.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cli-truncate": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz",
"integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==",
"dev": true,
"license": "MIT",
"dependencies": {
"slice-ansi": "^7.1.0",
"string-width": "^8.0.0"
},
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/clsx": {
"version": "2.1.1",
"license": "MIT",
@@ -2651,6 +2717,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/colorette": {
"version": "2.0.20",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
"dev": true,
"license": "MIT"
},
"node_modules/combined-stream": {
"version": "1.0.8",
"license": "MIT",
@@ -2661,6 +2734,16 @@
"node": ">= 0.8"
}
},
"node_modules/commander": {
"version": "14.0.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz",
"integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=20"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"dev": true,
@@ -3006,6 +3089,13 @@
"version": "1.5.283",
"license": "ISC"
},
"node_modules/emoji-regex": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
"integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
"dev": true,
"license": "MIT"
},
"node_modules/entities": {
"version": "4.5.0",
"license": "BSD-2-Clause",
@@ -3016,6 +3106,19 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/environment": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
"integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/error-ex": {
"version": "1.3.4",
"license": "MIT",
@@ -3512,6 +3615,19 @@
"node": "^10.12.0 || >=12.0.0"
}
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/find-root": {
"version": "1.1.0",
"license": "MIT"
@@ -3667,6 +3783,19 @@
"node": ">=6.9.0"
}
},
"node_modules/get-east-asian-width": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
"integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"license": "MIT",
@@ -4149,6 +4278,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz",
"integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"get-east-asian-width": "^1.3.1"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-generator-function": {
"version": "1.1.2",
"dev": true,
@@ -4200,6 +4345,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/is-number-object": {
"version": "1.1.1",
"dev": true,
@@ -4478,6 +4633,56 @@
"version": "1.2.4",
"license": "MIT"
},
"node_modules/lint-staged": {
"version": "16.2.7",
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.7.tgz",
"integrity": "sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==",
"dev": true,
"license": "MIT",
"dependencies": {
"commander": "^14.0.2",
"listr2": "^9.0.5",
"micromatch": "^4.0.8",
"nano-spawn": "^2.0.0",
"pidtree": "^0.6.0",
"string-argv": "^0.3.2",
"yaml": "^2.8.1"
},
"bin": {
"lint-staged": "bin/lint-staged.js"
},
"engines": {
"node": ">=20.17"
},
"funding": {
"url": "https://opencollective.com/lint-staged"
}
},
"node_modules/listr2": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz",
"integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==",
"dev": true,
"license": "MIT",
"dependencies": {
"cli-truncate": "^5.0.0",
"colorette": "^2.0.20",
"eventemitter3": "^5.0.1",
"log-update": "^6.1.0",
"rfdc": "^1.4.1",
"wrap-ansi": "^9.0.0"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/listr2/node_modules/eventemitter3": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
"integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==",
"dev": true,
"license": "MIT"
},
"node_modules/locate-path": {
"version": "6.0.0",
"dev": true,
@@ -4501,6 +4706,55 @@
"dev": true,
"license": "MIT"
},
"node_modules/log-update": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
"integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-escapes": "^7.0.0",
"cli-cursor": "^5.0.0",
"slice-ansi": "^7.1.0",
"strip-ansi": "^7.1.0",
"wrap-ansi": "^9.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/log-update/node_modules/ansi-regex": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/log-update/node_modules/strip-ansi": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/loose-envify": {
"version": "1.4.0",
"license": "MIT",
@@ -4539,6 +4793,33 @@
"node": ">= 0.4"
}
},
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"license": "MIT",
"dependencies": {
"braces": "^3.0.3",
"picomatch": "^2.3.1"
},
"engines": {
"node": ">=8.6"
}
},
"node_modules/micromatch/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"license": "MIT",
@@ -4556,6 +4837,19 @@
"node": ">= 0.6"
}
},
"node_modules/mimic-function": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
"integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/minimatch": {
"version": "3.1.2",
"dev": true,
@@ -4593,6 +4887,19 @@
}
}
},
"node_modules/nano-spawn": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz",
"integrity": "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=20.17"
},
"funding": {
"url": "https://github.com/sindresorhus/nano-spawn?sponsor=1"
}
},
"node_modules/nanoid": {
"version": "3.3.11",
"funding": [
@@ -4731,6 +5038,22 @@
"wrappy": "1"
}
},
"node_modules/onetime": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
"integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"mimic-function": "^5.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/optionator": {
"version": "0.9.4",
"dev": true,
@@ -4876,6 +5199,19 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pidtree": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz",
"integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==",
"dev": true,
"license": "MIT",
"bin": {
"pidtree": "bin/pidtree.js"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/possible-typed-array-names": {
"version": "1.1.0",
"dev": true,
@@ -4932,6 +5268,18 @@
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/pretty-bytes": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-7.1.0.tgz",
"integrity": "sha512-nODzvTiYVRGRqAOvE84Vk5JDPyyxsVk0/fbA/bq7RqlnhksGpset09XTxbpvLTIjoaF7K8Z8DG8yHtKGTPSYRw==",
"license": "MIT",
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/pretty-ms": {
"version": "9.3.0",
"license": "MIT",
@@ -5280,6 +5628,23 @@
"node": ">=4"
}
},
"node_modules/restore-cursor": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
"integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
"dev": true,
"license": "MIT",
"dependencies": {
"onetime": "^7.0.0",
"signal-exit": "^4.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/reusify": {
"version": "1.1.0",
"dev": true,
@@ -5289,6 +5654,13 @@
"node": ">=0.10.0"
}
},
"node_modules/rfdc": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
"dev": true,
"license": "MIT"
},
"node_modules/rimraf": {
"version": "3.0.2",
"dev": true,
@@ -5565,6 +5937,49 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/slice-ansi": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz",
"integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.2.1",
"is-fullwidth-code-point": "^5.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
}
},
"node_modules/slice-ansi/node_modules/ansi-styles": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/snake-case": {
"version": "3.0.4",
"license": "MIT",
@@ -5599,6 +6014,62 @@
"node": ">= 0.4"
}
},
"node_modules/string-argv": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
"integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.6.19"
}
},
"node_modules/string-width": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.1.tgz",
"integrity": "sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==",
"dev": true,
"license": "MIT",
"dependencies": {
"get-east-asian-width": "^1.3.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/string-width/node_modules/ansi-regex": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/string-width/node_modules/strip-ansi": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/string.prototype.matchall": {
"version": "4.0.12",
"dev": true,
@@ -5779,6 +6250,19 @@
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/tslib": {
"version": "2.8.1",
"license": "0BSD"
@@ -6175,6 +6659,84 @@
"node": ">=0.10.0"
}
},
"node_modules/wrap-ansi": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
"integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.2.1",
"string-width": "^7.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi/node_modules/ansi-regex": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/wrap-ansi/node_modules/ansi-styles": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/wrap-ansi/node_modules/string-width": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^10.3.0",
"get-east-asian-width": "^1.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/wrap-ansi/node_modules/strip-ansi": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"dev": true,
@@ -6184,6 +6746,22 @@
"version": "3.1.1",
"license": "ISC"
},
"node_modules/yaml": {
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
"devOptional": true,
"license": "ISC",
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14.6"
},
"funding": {
"url": "https://github.com/sponsors/eemeli"
}
},
"node_modules/yocto-queue": {
"version": "0.1.0",
"dev": true,
@@ -6203,4 +6781,4 @@
}
}
}
}
}
+5
View File
@@ -12,6 +12,9 @@
"format": "prettier --write .",
"format-check": "prettier --check ."
},
"lint-staged": {
"**/*": "npm run format"
},
"dependencies": {
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
@@ -31,6 +34,7 @@
"joi": "17.13.3",
"lucide-react": "^0.562.0",
"mui-color-input": "^7.0.0",
"pretty-bytes": "^7.1.0",
"pretty-ms": "^9.3.0",
"react": "18.3.1",
"react-dom": "^18.2.0",
@@ -56,6 +60,7 @@
"eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.6",
"lint-staged": "^16.2.7",
"prettier": "^3.3.3",
"typescript": "5.9.3",
"vite": "6.3.6"
+12 -21
View File
@@ -1,27 +1,16 @@
import { useEffect } from "react";
import { type CSSProperties } from "react";
import { useSelector } from "react-redux";
import "react-toastify/dist/ReactToastify.css";
import { ToastContainer } from "react-toastify";
import { ThemeProvider } from "@emotion/react";
import lightTheme from "./Utils/Theme/lightTheme";
import darkTheme from "./Utils/Theme/darkTheme";
import { CssBaseline, GlobalStyles } from "@mui/material";
import { logger } from "./Utils/Logger"; // Import the logger
import { networkService } from "./main";
import { Routes } from "./Routes";
import AppLayout from "@/Components/v1/Layouts/AppLayout";
import AppLayout from "@/Components/layout/AppLayout";
import type { RootState } from "@/Types/state";
import { lightTheme, darkTheme } from "@/Utils/Theme/Theme";
function App() {
const mode = useSelector((state) => state.ui.mode);
// Cleanup
useEffect(() => {
return () => {
logger.cleanup();
networkService.cleanup();
};
}, []);
const mode = useSelector((state: RootState) => state.ui.mode);
const theme = mode === "light" ? lightTheme : darkTheme;
return (
@@ -30,7 +19,7 @@ function App() {
<GlobalStyles
styles={{
body: {
backgroundColor: theme.palette.background.main,
backgroundColor: theme.palette.background.default,
},
}}
/>
@@ -40,10 +29,12 @@ function App() {
<ToastContainer
newestOnTop={true}
theme={mode}
style={{
"--toastify-color-progress-light": "#7C8BA1",
"--toastify-color-progress-dark": "#7C8BA1",
}}
style={
{
"--toastify-color-progress-light": "#7C8BA1",
"--toastify-color-progress-dark": "#7C8BA1",
} as CSSProperties
}
/>
</ThemeProvider>
);
@@ -1,53 +0,0 @@
.wallet-adapter-dropdown {
width: 100%;
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
gap: var(--env-var-spacing-1);
}
.wallet-adapter-button {
display: flex;
align-items: center;
justify-content: center;
width: auto;
height: var(--env-var-height-2) !important;
font-size: var(--env-var-font-size-medium-plus) !important;
font-weight: 500 !important;
margin: 0;
padding: calc(
(var(--env-var-height-2) - var(--env-var-font-size-medium-plus) * 1.2) / 2
)
var(--env-var-spacing-1);
white-space: nowrap;
}
.wallet-adapter-modal-title {
font-size: var(--font-size-h5) !important;
}
/* Separator styling */
.wallet-adapter-modal-divider {
background-color: var(--border-color) !important;
margin: var(--spacing-md) 0 !important;
}
/* Responsive fixes */
@media (max-width: 1200px) {
.wallet-adapter-button {
font-size: var(--env-var-font-size-medium) !important;
padding: calc((var(--env-var-height-2) - var(--env-var-font-size-medium) * 1.2) / 2)
var(--env-var-spacing-1-minus) !important;
}
}
@media (max-width: 900px) {
.wallet-adapter-modal-wrapper {
flex-direction: column !important;
}
.wallet-adapter-modal-divider {
margin: var(--spacing-sm) 0 !important;
}
}
@@ -1,50 +0,0 @@
// import { useMemo } from "react";
// import { ConnectionProvider, WalletProvider } from "@solana/wallet-adapter-react";
// import { WalletAdapterNetwork } from "@solana/wallet-adapter-base";
// import {
// UnsafeBurnerWalletAdapter,
// PhantomWalletAdapter,
// } from "@solana/wallet-adapter-wallets";
// import { WalletModalProvider } from "@solana/wallet-adapter-react-ui";
// import { clusterApiUrl } from "@solana/web3.js";
// import PropTypes from "prop-types";
// import "./index.css";
// // Default styles that can be overridden by your app
// import "@solana/wallet-adapter-react-ui/styles.css";
// export const Wallet = ({ children }) => {
// // The network can be set to 'devnet', 'testnet', or 'mainnet-beta'.
// const network = WalletAdapterNetwork.Mainnet;
// // You can also provide a custom RPC endpoint.
// const endpoint = useMemo(() => clusterApiUrl(network), [network]);
// const wallets = useMemo(
// () => [new PhantomWalletAdapter()],
// // eslint-disable-next-line react-hooks/exhaustive-deps
// [network]
// );
// return (
// <ConnectionProvider endpoint={endpoint}>
// <WalletProvider
// wallets={wallets}
// autoConnect
// >
// <WalletModalProvider>{children}</WalletModalProvider>
// </WalletProvider>
// </ConnectionProvider>
// );
// };
// Wallet.propTypes = {
// children: PropTypes.node,
// };
const Wallet = ({ children }) => {
return children;
};
export default Wallet;
@@ -2,7 +2,7 @@ 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 { HeatmapResponseTimeTooltip } from "@/Components/common/charts/HeatmapResponseTimeTooltip";
import type { SxProps } from "@mui/material/styles";
import type { ResponsiveStyleValue } from "@mui/system";
@@ -0,0 +1,155 @@
import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { useTheme } from "@mui/material/styles";
import type { ResponsiveStyleValue } from "@mui/system";
import type { CheckSnapshot } from "@/Types/Check";
import { HeatmapResponseTimeTooltip } from "./HeatmapResponseTimeTooltip";
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
interface HistogramResponseTimeProps {
checks: CheckSnapshot[];
height?: ResponsiveStyleValue<number | string>;
gap?: ResponsiveStyleValue<number | string>;
showStats?: boolean;
statsPosition?: "left" | "right";
}
interface ResponseTimeStats {
max: number | string;
avg: number | string;
}
const DEFAULT_HEIGHT = 50;
const calculateResponseTimeStats = (checks: CheckSnapshot[]): ResponseTimeStats => {
if (!Array.isArray(checks) || checks.length === 0) {
return { max: "-", avg: "-" };
}
const validChecks = checks.filter((check) => check.originalResponseTime != null);
if (validChecks.length === 0) {
return { max: "-", avg: "-" };
}
const responseTimes = validChecks.map((check) => check.originalResponseTime);
const max = Math.max(...responseTimes);
const avg = Math.round(
responseTimes.reduce((sum, time) => sum + time, 0) / responseTimes.length
);
return { max, avg };
};
export const HistogramResponseTime = ({
checks,
height = DEFAULT_HEIGHT,
gap,
showStats = true,
statsPosition = "right",
}: HistogramResponseTimeProps) => {
const theme = useTheme();
const { t } = useTranslation();
const stats = useMemo(() => calculateResponseTimeStats(checks), [checks]);
if (!Array.isArray(checks) || checks.length === 0) return null;
const data =
checks.length !== 25
? [...checks, ...Array(25 - checks.length).fill({ status: "placeholder" })]
: checks;
const chartHeight = typeof height === "number" ? `${height}px` : height;
const gridGap = gap ?? theme.spacing(0.5);
const statsContent = showStats &&
(typeof stats.max === "number" || typeof stats.avg === "number") && (
<Stack
justifyContent="center"
alignItems={statsPosition === "left" ? "flex-end" : "flex-start"}
minWidth={70}
pr={statsPosition === "left" ? theme.spacing(8) : 0}
pl={statsPosition === "right" ? theme.spacing(8) : 0}
>
<Typography variant="body2">
{t("common.charts.histogram.avg", { value: stats.avg })}
</Typography>
<Typography variant="body2">
{t("common.charts.histogram.max", { value: stats.max })}
</Typography>
</Stack>
);
return (
<Stack
direction={statsPosition === "left" ? "row-reverse" : "row"}
alignItems="center"
sx={{ width: "100%" }}
>
<Box sx={{ flex: 1 }}>
<Box
sx={{
width: "100%",
display: "grid",
gridTemplateColumns: "repeat(25, 1fr)",
gap: gridGap,
alignItems: "end",
height: chartHeight,
}}
>
{data.map((check, index) => {
const isPlaceholder = (check as any).status === "placeholder";
const heightPct = `${Math.max(0, Math.min(100, (check as any).responseTime ?? 0))}%`;
const barColor =
check.status === true
? theme.palette.success.light
: theme.palette.error.light;
const bar = (
<Box
sx={{
position: "relative",
width: "100%",
height: "100%",
borderRadius: theme.spacing(1),
bgcolor: theme.palette.action.hover,
overflow: "hidden",
}}
>
<Box
sx={{
position: "absolute",
bottom: 0,
left: 0,
width: "100%",
height: isPlaceholder ? 0 : heightPct,
bgcolor: barColor,
transition: "height 500ms cubic-bezier(0.4, 0, 0.2, 1)",
}}
/>
</Box>
);
return isPlaceholder ? (
<Box
key={index}
sx={{ height: "100%" }}
>
{bar}
</Box>
) : (
<HeatmapResponseTimeTooltip
key={index}
check={check}
>
{bar}
</HeatmapResponseTimeTooltip>
);
})}
</Box>
</Box>
{statsContent}
</Stack>
);
};
@@ -1,7 +1,7 @@
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import { ToggleButtonGroup, ToggleButton } from "@/Components/v2/inputs";
import { ToggleButtonGroup, ToggleButton } from "@/Components/inputs";
import { useTheme } from "@mui/material/styles";
import CircularProgress from "@mui/material/CircularProgress";
import { useTranslation } from "react-i18next";
+5
View File
@@ -0,0 +1,5 @@
export * from "./charts/HistogramResponseTime";
export * from "./charts/HeatmapResponseTime";
export * from "./charts/HeatmapResponseTimeTooltip";
export * from "./controls/HeaderCreate";
export * from "./controls/HeaderTimeRange";
@@ -0,0 +1,48 @@
import { Avatar as MuiAvatar } from "@mui/material";
import { useSelector } from "react-redux";
import { useEffect, useState } from "react";
import { useTheme } from "@mui/material";
import type { RootState } from "@/Types/state";
interface AvatarProps {
src?: string;
small?: boolean;
sx?: object;
onClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
}
export const Avatar = ({ src, small, sx, onClick = () => {} }: AvatarProps) => {
const { user } = useSelector((state: RootState) => state.auth);
const theme = useTheme();
if (!user) return null;
const style = small ? { width: 32, height: 32 } : { width: 64, height: 64 };
const [image, setImage] = useState<string>();
useEffect(() => {
if (user.avatarImage) {
setImage(`data:image/png;base64,${user.avatarImage}`);
}
}, [user?.avatarImage]);
return (
<MuiAvatar
onClick={onClick}
alt={`${user?.firstName} ${user?.lastName}`}
src={src ? src : user?.avatarImage ? image : undefined}
sx={{
color: theme.palette.primary.contrastText,
fontSize: small ? "16px" : "22px",
fontWeight: 400,
backgroundColor: theme.palette.primary.main,
display: "inline-flex",
...style,
...sx,
}}
>
{user.firstName?.charAt(0)}
{user.lastName?.charAt(0) || ""}
</MuiAvatar>
);
};
@@ -1,4 +1,4 @@
import { BaseBox } from "../design-elements";
import { BaseBox } from ".";
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
@@ -1,4 +1,6 @@
import Logo from "@/assets/icons/checkmate-icon.svg?react";
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import Alert from "@mui/material/Alert";
import Link from "@mui/material/Link";
import {
@@ -6,9 +8,10 @@ import {
EmptyFallback,
EmptyMonitorFallback,
BaseFallback,
} from "@/Components/v2/design-elements/Fallback";
import { Breadcrumb } from "@/Components/v2/design-elements/Breadcrumb";
} from "@/Components/design-elements/Fallback";
import { Breadcrumb } from "@/Components/design-elements/Breadcrumb";
import CircularProgress from "@mui/material/CircularProgress";
import { HeaderAuthControls } from "@/Pages/Auth/components/HeaderAuthControls";
import type { StackProps } from "@mui/material/Stack";
import { useTheme } from "@mui/material/styles";
@@ -100,7 +103,7 @@ export const BasePage = ({
interface BasePageWithStatesProps extends StackProps {
loading: boolean;
error: any;
items: any[];
totalCount: number;
bullets: string[] | unknown;
page: string;
actionButtonText: string;
@@ -111,7 +114,7 @@ interface BasePageWithStatesProps extends StackProps {
export const BasePageWithStates = ({
loading,
error,
items,
totalCount,
page,
bullets,
actionButtonText,
@@ -119,9 +122,9 @@ export const BasePageWithStates = ({
children,
...props
}: BasePageWithStatesProps) => {
const showLoading = loading && (!items || items.length === 0);
const showLoading = loading && totalCount === 0;
if (isEmpty(items)) {
if (!loading && totalCount === 0) {
return (
<EmptyFallback
bullets={bullets}
@@ -146,23 +149,17 @@ export const BasePageWithStates = ({
interface MonitorBasePageWithStatesProps extends StackProps {
loading: boolean;
error: any;
items: any[];
totalCount: number;
page: string;
actionLink?: string;
children: React.ReactNode;
priorityFallback?: React.ReactNode;
}
const isEmpty = (items: any[]) => {
if (!items) return true;
if (Array.isArray(items) && items.length === 0) return true;
return false;
};
export const MonitorBasePageWithStates = ({
loading,
error,
items,
totalCount,
page,
actionLink,
children,
@@ -171,7 +168,7 @@ export const MonitorBasePageWithStates = ({
}: MonitorBasePageWithStatesProps) => {
const { t } = useTranslation();
const showLoading = loading && (!items || items.length === 0);
const showLoading = loading && totalCount === 0;
if (priorityFallback) {
return (
@@ -180,12 +177,12 @@ export const MonitorBasePageWithStates = ({
error={error}
{...props}
>
{priorityFallback}
<Stack height={"100%"}>{priorityFallback}</Stack>
</BasePage>
);
}
if (!loading && isEmpty(items)) {
if (!loading && totalCount === 0) {
return (
<EmptyMonitorFallback
page={page}
@@ -207,3 +204,61 @@ export const MonitorBasePageWithStates = ({
</BasePage>
);
};
interface BaseAuthPageProps extends React.PropsWithChildren, StackProps {
title: string;
subtitle: string;
}
export const BaseAuthPage = ({
title,
subtitle,
children,
...props
}: BaseAuthPageProps) => {
const theme = useTheme();
return (
<BasePage
breadcrumbOverride={[]}
gap={theme.spacing(8)}
alignItems={"center"}
justifyContent={"center"}
minHeight="100vh"
position={"relative"}
{...props}
>
<HeaderAuthControls
hideLogo
py={theme.spacing(4)}
position={"absolute"}
top={0}
left={0}
/>
<Box width={{ xs: 60, sm: 70, md: 80 }}>
<Logo
width={"100%"}
height={"100%"}
/>
</Box>
<Stack alignItems={"center"}>
<Typography
variant="h1"
mb={theme.spacing(2)}
>
{title}
</Typography>
<Typography variant="h2">{subtitle}</Typography>
</Stack>
<Stack
gap={theme.spacing(8)}
width={{
xs: "80%",
md: "25%",
lg: "15%",
}}
>
{children}
</Stack>
</BasePage>
);
};
@@ -27,6 +27,7 @@ export const BulletPointCheck = ({
<Check
size={16}
strokeWidth={1.5}
color={variant === "info" ? theme.palette.text.secondary : colors[variant]}
style={{
flexShrink: 0,
}}
@@ -1,7 +1,7 @@
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { BulletPointCheck, SkeletonCard } from "@/Components/v2/design-elements";
import { Button } from "@/Components/v2/inputs";
import { BulletPointCheck, SkeletonCard } from "@/Components/design-elements";
import { Button } from "@/Components/inputs";
import { useNavigate } from "react-router";
import useMediaQuery from "@mui/material/useMediaQuery";
@@ -85,12 +85,7 @@ export const EmptyFallback = ({
zIndex={1}
alignItems="center"
>
<Typography
component="h1"
color={theme.palette.primary.contrastText}
>
{title}
</Typography>
<Typography component="h1">{title}</Typography>
<Stack
sx={{
flexWrap: "wrap",
@@ -1,3 +1,5 @@
import { BaseChart } from "@/Components/design-elements";
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
@@ -104,3 +106,50 @@ export const Gauge = ({
</Box>
);
};
export const DetailGauge = ({
title,
progress,
upperLabel,
upperValue,
lowerLabel,
lowerValue,
}: {
title: string;
progress: number;
upperLabel?: string;
upperValue?: string | number;
lowerLabel?: string;
lowerValue?: string | number;
}) => {
const theme = useTheme();
return (
<BaseChart
icon={null}
title={title}
maxWidth={225}
>
<Stack
alignItems={"center"}
mb={theme.spacing(4)}
gap={theme.spacing(4)}
>
<Gauge progress={progress} />
</Stack>
<Stack
direction={"row"}
justifyContent={"space-between"}
>
<Typography>{upperLabel}</Typography>
<Typography>{upperValue}</Typography>
</Stack>
<Stack
direction={"row"}
justifyContent={"space-between"}
>
<Typography>{lowerLabel}</Typography>
<Typography>{lowerValue}</Typography>
</Stack>
</BaseChart>
);
};
@@ -4,6 +4,7 @@ interface IconProps {
icon: LucideIcon;
size?: number;
strokeWidth?: number;
stroke?: string;
}
const Icon = ({ icon: Icon, size = 20, strokeWidth = 1.5 }: IconProps) => {
@@ -1,11 +1,11 @@
import type { Monitor } from "@/Types/Monitor";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { PulseDot, Dot } from "@/Components/v2/design-elements";
import { PulseDot, Dot } from "@/Components/design-elements";
import { getStatusColor, formatUrl } from "@/Utils/MonitorUtils";
import { useTheme } from "@mui/material/styles";
import prettyMilliseconds from "pretty-ms";
import { typographyLevels } from "@/Utils/Theme/v2Palette";
import { typographyLevels } from "@/Utils/Theme/Palette";
import useMediaQuery from "@mui/material/useMediaQuery";
export const MonitorStatus = ({ monitor }: { monitor: Monitor }) => {
const theme = useTheme();
@@ -19,11 +19,9 @@ export const MonitorStatus = ({ monitor }: { monitor: Monitor }) => {
<Typography
fontSize={typographyLevels.xl}
fontWeight={500}
color={theme.palette.primary.contrastText}
overflow={"hidden"}
textOverflow={"ellipsis"}
whiteSpace={"nowrap"}
maxWidth={isSmall ? "100%" : "calc((100vw - var(--env-var-width-2)) / 2)"}
>
{monitor.name}
</Typography>
@@ -40,7 +38,6 @@ export const MonitorStatus = ({ monitor }: { monitor: Monitor }) => {
overflow={"hidden"}
textOverflow={"ellipsis"}
whiteSpace={"nowrap"}
maxWidth={isSmall ? "100%" : "calc((100vw - var(--env-var-width-2)) / 2)"}
>
{formatUrl(monitor?.url)}
</Typography>
@@ -0,0 +1,63 @@
import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { useTheme } from "@mui/material/styles";
import { useTranslation } from "react-i18next";
import { WifiOff } from "lucide-react";
import { useState, useEffect } from "react";
interface OfflineBannerProps {
visible: boolean;
}
export const OfflineBanner = ({ visible }: OfflineBannerProps) => {
const theme = useTheme();
const { t } = useTranslation();
const [shouldRender, setShouldRender] = useState(visible);
const [isAnimating, setIsAnimating] = useState(false);
useEffect(() => {
if (visible) {
setShouldRender(true);
requestAnimationFrame(() => setIsAnimating(true));
} else {
setIsAnimating(false);
const timer = setTimeout(() => setShouldRender(false), 1000);
return () => clearTimeout(timer);
}
}, [visible]);
if (!shouldRender) return null;
return (
<Box
sx={{
position: "fixed",
top: isAnimating ? 0 : "-100%",
left: 0,
right: 0,
zIndex: theme.zIndex.snackbar,
backgroundColor: theme.palette.error.main,
color: theme.palette.error.contrastText,
px: theme.spacing(8),
py: theme.spacing(4),
transition: "top 1s ease-in-out",
}}
>
<Stack
direction="row"
alignItems="center"
justifyContent="center"
gap={theme.spacing(4)}
>
<WifiOff size={20} />
<Typography
variant="body2"
fontWeight={500}
>
{t("components.offlineBanner.serverUnreachable")}
</Typography>
</Stack>
</Box>
);
};
@@ -1,4 +1,17 @@
import { Box, Stack, useTheme } from "@mui/material";
import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import { useTheme, keyframes } from "@mui/material";
const ripple = keyframes`
from {
opacity: 1;
transform: scale(0);
}
to {
opacity: 0;
transform: scale(2);
}
`;
interface PulseDotProps {
color: string;
@@ -28,7 +41,7 @@ export const PulseDot = ({ color }: PulseDotProps) => {
height: "100%",
backgroundColor: "inherit",
borderRadius: "50%",
animation: "ripple 1.8s ease-out infinite",
animation: `${ripple} 1.8s ease-out infinite`,
},
"&::after": {
content: `""`,
@@ -4,8 +4,8 @@ import Box from "@mui/material/Box";
import { useTheme } from "@mui/material/styles";
import { lighten } from "@mui/material/styles";
import useMediaQuery from "@mui/material/useMediaQuery";
import type { PaletteKey } from "@/Utils/Theme/v2Theme";
import { BaseBox, TooltipWithInfo } from "@/Components/v2/design-elements";
import type { PaletteKey } from "@/Utils/Theme/Theme";
import { BaseBox, TooltipWithInfo } from "@/Components/design-elements";
import type { SxProps } from "@mui/material";
type GradientBox = React.PropsWithChildren<{
@@ -1,8 +1,7 @@
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import Box from "@mui/material/Box";
import { BaseBox } from "@/Components/v2/design-elements";
import Background from "@/assets/Images/background-grid.svg?react";
import { BaseBox } from "@/Components/design-elements";
import { useTranslation } from "react-i18next";
import type { SxProps } from "@mui/material";
@@ -18,6 +17,7 @@ export const BGBox = ({ children, sx }: StatusBoxProps) => {
return (
<BaseBox
sx={{
backgroundColor: theme.palette.background.default,
overflow: "hidden",
position: "relative",
flex: 1,
@@ -27,11 +27,23 @@ export const BGBox = ({ children, sx }: StatusBoxProps) => {
>
<Box
position="absolute"
top="-10%"
left="5%"
>
<Background />
</Box>
top={0}
left={0}
right={0}
bottom={0}
sx={{
pointerEvents: "none",
backgroundImage: `
linear-gradient(${theme.palette.divider} 1px, transparent 1px),
linear-gradient(90deg, ${theme.palette.divider} 1px, transparent 1px)
`,
backgroundSize: "24px 24px",
maskImage:
"linear-gradient(135deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0) 30%, rgba(0,0,0,0.4) 100%)",
WebkitMaskImage:
"linear-gradient(135deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0) 30%, rgba(0,0,0,0.4) 100%)",
}}
/>
{children}
</BaseBox>
);
@@ -105,6 +117,40 @@ export const PausedStatusBox = ({ n }: { n: number }) => {
/>
);
};
export const MaintenanceStatusBox = ({ n }: { n: number }) => {
const theme = useTheme();
const { t } = useTranslation();
return (
<StatusBox
label={t("pages.common.monitors.status.maintenance")}
n={n}
color={theme.palette.warning.light}
/>
);
};
export const InitializingStatusBox = ({ n }: { n: number }) => {
const theme = useTheme();
const { t } = useTranslation();
return (
<StatusBox
label={t("pages.common.monitors.status.initializing")}
n={n}
color={theme.palette.warning.light}
/>
);
};
export const BreachedStatusBox = ({ n }: { n: number }) => {
const theme = useTheme();
const { t } = useTranslation();
return (
<StatusBox
label={t("pages.common.monitors.status.breached")}
n={n}
color={theme.palette.warning.main}
/>
);
};
export const TotalChecksBox = ({ n }: { n: number }) => {
const theme = useTheme();
const { t } = useTranslation();
@@ -138,15 +184,3 @@ export const UpChecksBox = ({ n }: { n: number }) => {
/>
);
};
export const InitializingStatusBox = ({ n }: { n: number }) => {
const theme = useTheme();
const { t } = useTranslation();
return (
<StatusBox
label={t("pages.common.monitors.status.initializing")}
n={n}
color={theme.palette.warning.light}
/>
);
};
@@ -1,5 +1,5 @@
import Box from "@mui/material/Box";
import { BaseBox } from "@/Components/v2/design-elements";
import { BaseBox } from "@/Components/design-elements";
import Typography from "@mui/material/Typography";
import type { MonitorStatus } from "@/Types/Monitor";
@@ -11,33 +11,26 @@ import { useTranslation } from "react-i18next";
export const ValueTypes = ["positive", "negative", "neutral"] as const;
export type ValueType = (typeof ValueTypes)[number];
export const StatusLabel = ({
status,
isActive,
sx,
}: {
status: MonitorStatus;
isActive?: boolean;
sx?: SxProps;
}) => {
export const StatusLabel = ({ status, sx }: { status: MonitorStatus; sx?: SxProps }) => {
const { t } = useTranslation();
const theme = useTheme();
const palette = getStatusPalette(status);
const determineStatus = (
isActive: boolean | undefined,
status: MonitorStatus
): string => {
if (isActive === false) {
const determineStatus = (status: MonitorStatus): string => {
if (status === "up") {
return t("pages.common.monitors.status.up");
} else if (status === "down") {
return t("pages.common.monitors.status.down");
} else if (status === "breached") {
return t("pages.common.monitors.status.breached");
} else if (status === "maintenance") {
return t("pages.common.monitors.status.maintenance");
} else if (status === "paused") {
return t("pages.common.monitors.status.paused");
} else if (status === "initializing") {
return t("pages.common.monitors.status.initializing");
}
if (status === true) {
return t("pages.common.monitors.status.up");
}
if (status === false) {
return t("pages.common.monitors.status.down");
}
return t("pages.common.monitors.status.initializing");
};
@@ -64,9 +57,7 @@ export const StatusLabel = ({
borderRadius="50%"
marginRight="5px"
/>
<Typography textTransform={"capitalize"}>
{determineStatus(isActive, status)}
</Typography>
<Typography textTransform={"capitalize"}>{determineStatus(status)}</Typography>
</BaseBox>
);
};
@@ -27,8 +27,9 @@ import TablePagination from "@mui/material/TablePagination";
import type { TablePaginationOwnProps } from "@mui/material/TablePagination";
import { useTranslation } from "react-i18next";
import { useState, Fragment } from "react";
import { useState, Fragment, useEffect } from "react";
import { useTheme } from "@mui/material/styles";
import type { SxProps, Theme } from "@mui/material/styles";
import useMediaQuery from "@mui/material/useMediaQuery";
export type Header<T> = {
@@ -47,6 +48,7 @@ type DataTableProps<T extends { id?: string | number; _id?: string | number }> =
renderExpandedContent?: (row: T) => React.ReactNode;
emptyViewText?: string;
emptyViewPositive?: boolean;
getRowSx?: (row: T) => SxProps<Theme>;
};
export function DataTable<
@@ -64,6 +66,7 @@ export function DataTable<
renderExpandedContent,
emptyViewText,
emptyViewPositive,
getRowSx,
}: DataTableProps<T>) {
const theme = useTheme();
const [expanded, setExpanded] = useState<(string | number) | null>(null);
@@ -204,6 +207,7 @@ export function DataTable<
<TableRow
sx={{
cursor: onRowClick ? "pointer" : "default",
...(getRowSx?.(row) as object),
}}
onClick={() => {
if (expandableRows) handleExpand(row);
@@ -454,12 +458,26 @@ function HasMoreTablePaginationActions(props: HasMoreTablePaginationActionsProps
interface PaginationProps extends TablePaginationOwnProps {
component?: React.ElementType;
hasMore?: boolean;
itemsOnPage?: number;
}
export const Pagination = ({ ...props }: PaginationProps) => {
const { hasMore, ...rest } = props;
const { hasMore, itemsOnPage, ...rest } = props;
const isSmall = useMediaQuery((theme: any) => theme.breakpoints.down("sm"));
const theme = useTheme();
useEffect(() => {
if (
typeof itemsOnPage === "number" &&
itemsOnPage === 0 &&
rest.count > 0 &&
rest.page > 0 &&
rest.onPageChange
) {
rest.onPageChange(null, rest.page - 1);
}
}, [itemsOnPage, rest.count, rest.page, rest.onPageChange]);
const labelDisplayedRows = ({
from,
to,
@@ -0,0 +1,43 @@
import Typography from "@mui/material/Typography";
import Stack from "@mui/material/Stack";
import type { StackProps } from "@mui/material/Stack";
import Link from "@mui/material/Link";
import { Link as RouterLink } from "react-router-dom";
import { useTheme } from "@mui/material/styles";
interface TextLinkProps extends StackProps {
text: string;
linkText: string;
href: string;
target?: "_self" | "_blank" | "_parent" | "_top";
}
export const TextLink = ({
text,
linkText,
href,
target = "_self",
...props
}: TextLinkProps) => {
const theme = useTheme();
return (
<Stack
direction="row"
gap={theme.spacing(4)}
{...props}
>
<Typography>{text}</Typography>
<Link
color="primary"
to={href}
component={RouterLink}
target={target}
{...(target === "_blank" && { rel: "noopener noreferrer" })}
>
{linkText}
</Link>
</Stack>
);
};
@@ -18,3 +18,6 @@ export * from "./BaseChart";
export * from "./Gauge";
export * from "./Tabs";
export * from "./SplitBox";
export * from "./TextLink";
export * from "./OfflineBanner";
export * from "./Avatar";
@@ -1,8 +1,9 @@
import i18n from "../../../Utils/i18n.js";
import i18n from "@/Utils/i18n.js";
import { useSelector } from "react-redux";
import { useEffect } from "react";
import type { RootState } from "@/store";
const I18nLoader = () => {
const language = useSelector((state) => state.ui.language ?? "en");
const language = useSelector((state: RootState) => state.ui.language ?? "en");
useEffect(() => {
if (language && i18n.language !== language) {
@@ -1,6 +1,6 @@
import Autocomplete from "@mui/material/Autocomplete";
import type { AutocompleteProps } from "@mui/material/Autocomplete";
import { TextField, Checkbox } from "@/Components/v2/inputs";
import { TextField, Checkbox } from "@/Components/inputs";
import ListItem from "@mui/material/ListItem";
import Stack from "@mui/material/Stack";
import { useTheme } from "@mui/material/styles";
@@ -1,9 +1,9 @@
import { MuiColorInput } from "mui-color-input";
import type { MuiColorInputProps } from "mui-color-input";
import { typographyLevels } from "@/Utils/Theme/v2Palette";
import { typographyLevels } from "@/Utils/Theme/Palette";
import { useTheme } from "@mui/material";
import Stack from "@mui/material/Stack";
import { FieldLabel } from "@/Components/v2/inputs/FieldLabel";
import { FieldLabel } from "@/Components/inputs/FieldLabel";
interface ColorPickerProps extends MuiColorInputProps {
fieldLabel?: string;
+100
View File
@@ -0,0 +1,100 @@
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
import type { DatePickerProps } from "@mui/x-date-pickers/DatePicker";
import type { Dayjs } from "dayjs";
import { useTheme } from "@mui/material/styles";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { FieldLabel } from "./FieldLabel";
import { Calendar } from "lucide-react";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import { LocalizationProvider } from "@mui/x-date-pickers";
interface DatePickerComponentProps extends Omit<DatePickerProps<Dayjs>, "label"> {
fieldLabel?: string;
required?: boolean;
error?: boolean;
helperText?: string;
}
export const DatePickerComponent = ({
fieldLabel,
required,
error,
helperText,
...props
}: DatePickerComponentProps) => {
const theme = useTheme();
const picker = (
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DatePicker
{...props}
slots={{
openPickerIcon: () => (
<Calendar
size={20}
stroke={theme.palette.text.secondary}
/>
),
...props.slots,
}}
slotProps={{
switchViewButton: { sx: { display: "none" } },
nextIconButton: { sx: { ml: theme.spacing(2) } },
day: {
sx: {
"&.MuiPickersDay-root.Mui-disabled": {
color: theme.palette.text.disabled,
},
},
},
field: {
sx: {
width: "fit-content",
"& > .MuiOutlinedInput-root": {
flexDirection: "row-reverse",
},
"& input": {
minHeight: 34,
p: 0,
pl: theme.spacing(3),
pr: theme.spacing(5),
},
"& fieldset": {
borderColor: error ? theme.palette.error.main : theme.palette.divider,
borderRadius: theme.shape.borderRadius,
},
"&:not(:has(.Mui-disabled)):not(:has(.Mui-error)) .MuiOutlinedInput-root:not(:has(input:focus)):hover fieldset":
{
borderColor: error ? theme.palette.error.main : theme.palette.divider,
},
},
},
inputAdornment: { sx: { ml: 0 } },
openPickerButton: {
sx: {
py: 0,
mr: 0,
},
},
...props.slotProps,
}}
/>
</LocalizationProvider>
);
return (
<Stack spacing={theme.spacing(2)}>
{fieldLabel && <FieldLabel required={required}>{fieldLabel}</FieldLabel>}
{picker}
{helperText && (
<Typography
variant="caption"
color={error ? "error" : "text.secondary"}
>
{helperText}
</Typography>
)}
</Stack>
);
};
@@ -4,8 +4,8 @@ import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
import DialogContentText from "@mui/material/DialogContentText";
import DialogTitle from "@mui/material/DialogTitle";
import { Button } from "@/Components/v2/inputs";
import { typographyLevels } from "@/Utils/Theme/v2Palette";
import { Button } from "@/Components/inputs";
import { typographyLevels } from "@/Utils/Theme/Palette";
import { useTranslation } from "react-i18next";
import type { ReactNode } from "react";
@@ -23,6 +23,7 @@ export const DialogInput = ({
children,
maxWidth,
fullWidth = false,
additionalButtons,
}: {
open: boolean;
title?: string;
@@ -37,6 +38,7 @@ export const DialogInput = ({
children?: ReactNode;
maxWidth?: DialogProps["maxWidth"];
fullWidth?: boolean;
additionalButtons?: ReactNode;
}) => {
const { t } = useTranslation();
return (
@@ -59,14 +61,17 @@ export const DialogInput = ({
>
{cancelText ?? t("common.buttons.cancel")}
</Button>
<Button
loading={loading}
variant="contained"
color={confirmColor}
onClick={onConfirm}
>
{confirmText ?? t("common.buttons.confirm")}
</Button>
{additionalButtons}
{onConfirm && (
<Button
loading={loading}
variant="contained"
color={confirmColor}
onClick={onConfirm}
>
{confirmText ?? t("common.buttons.confirm")}
</Button>
)}
</DialogActions>
</Dialog>
);
@@ -9,7 +9,9 @@ interface FieldLabelProps {
export const FieldLabel = ({ children, required = false, htmlFor }: FieldLabelProps) => {
const theme = useTheme();
if (children === " ") {
children = "\u00A0";
}
return (
<Typography
component="label"
@@ -38,11 +38,11 @@ export const ImageUpload = ({
const isValidType = accept.some((type) => file.type.includes(type));
const isValidSize = file.size <= maxSize;
if (!isValidType) {
setLocalError(t("common.errors.invalidFileFormat"));
setLocalError(t("components.imageUpload.errors.invalidFileFormat"));
return;
}
if (!isValidSize) {
setLocalError(t("common.errors.invalidFileSize"));
setLocalError(t("components.imageUpload.errors.invalidFileSize"));
return;
}
setLocalError(null);
@@ -137,15 +137,15 @@ export const ImageUpload = ({
color="primary"
fontWeight={500}
>
{t("common.imageUpload.clickToUpload")}
{t("components.imageUpload.clickToUpload")}
</Typography>{" "}
{t("common.imageUpload.orDragAndDrop")}
{t("components.imageUpload.orDragAndDrop")}
</Typography>
<Typography
variant="caption"
color="text.disabled"
>
{accept.join(", ").toUpperCase()} • {t("common.imageUpload.maxSize")}{" "}
{accept.join(", ").toUpperCase()} • {t("components.imageUpload.maxSize")}{" "}
{Math.round(maxSize / 1024 / 1024)}MB
</Typography>
</Stack>
@@ -0,0 +1,100 @@
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import MenuItem from "@mui/material/MenuItem";
import { Select } from "@/Components/inputs";
import "flag-icons/css/flag-icons.min.css";
import { useTranslation } from "react-i18next";
import { useTheme } from "@mui/material";
import { useSelector } from "react-redux";
import { useDispatch } from "react-redux";
import { setLanguage } from "@/Features/UI/uiSlice";
import type { RootState } from "@/Types/state";
const langMap: Record<string, string> = {
cs: "cz",
ja: "jp",
uk: "ua",
vi: "vn",
};
export const LanguageSelector = () => {
const { i18n } = useTranslation();
const theme = useTheme();
const { language = "en" } = useSelector((state: RootState) => state.ui);
const dispatch = useDispatch();
const handleChange = (event: any) => {
const newLang = event.target.value;
dispatch(setLanguage(newLang));
};
const languages = Object.keys(i18n.options.resources || {});
return (
<Select
value={language}
onChange={handleChange}
size="small"
// sx={{
// minWidth: 80,
// "& .MuiSelect-select": {
// display: "flex",
// alignItems: "center",
// justifyContent: "center",
// },
// "& .MuiSelect-icon": {
// alignSelf: "center",
// },
// }}
>
{languages.map((lang) => {
let parsedLang = lang === "en" ? "gb" : lang;
if (parsedLang.includes("-")) {
parsedLang = parsedLang.split("-")[1].toLowerCase();
}
parsedLang = langMap[parsedLang] || parsedLang;
const flag = parsedLang ? `fi fi-${parsedLang}` : null;
return (
<MenuItem
key={lang}
value={lang}
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<Stack
direction="row"
spacing={theme.spacing(2)}
alignItems="center"
justifyContent="center"
>
<Box
component="span"
sx={{
display: "flex",
alignItems: "center",
}}
>
{flag && <span className={flag} />}
</Box>
<Box
component="span"
sx={{ textTransform: "uppercase" }}
>
{lang}
</Box>
</Stack>
</MenuItem>
);
})}
</Select>
);
};
export default LanguageSelector;
@@ -72,6 +72,12 @@ const SelectInputInner = <T,>(
)}
sx={{
height: "34px",
"& .MuiSelect-select": {
display: "flex",
alignItems: "center",
height: "100%",
boxSizing: "border-box",
},
"& .MuiSelect-icon": {
right: theme.spacing(3),
},
@@ -41,7 +41,10 @@ export const SliderInput = forwardRef<HTMLSpanElement, SliderInputProps>(
"& .MuiSlider-thumb": {
backgroundColor: "#fff",
"&:hover, &.Mui-focusVisible": {
boxShadow: `0 0 0 8px ${theme.palette.primary.main}20`,
boxShadow: "none",
},
"&:active": {
boxShadow: "none",
},
},
"& .MuiSlider-valueLabel": {
@@ -0,0 +1,48 @@
import IconButton from "@mui/material/IconButton";
import { Moon, Sun } from "lucide-react";
import { setMode } from "@/Features/UI/uiSlice.js";
import { useTheme } from "@mui/material";
import { useDispatch, useSelector } from "react-redux";
import type { RootState } from "@/Types/state";
export const SwitchTheme = () => {
const mode = useSelector((state: RootState) => state.ui.mode);
const dispatch = useDispatch();
const theme = useTheme();
const handleChange = () => {
dispatch(setMode(mode === "light" ? "dark" : "light"));
};
return (
<IconButton
id="theme-toggle"
onClick={handleChange}
sx={{
display: "flex",
alignItems: "center",
justifyContent: "center",
"& svg": {
transition: "stroke 0.2s ease",
},
"&:hover svg path, &:hover svg line, &:hover svg polyline, &:hover svg rect, &:hover svg circle":
{
stroke: theme.palette.primary.main,
},
}}
>
{mode === "light" ? (
<Moon
size={16}
strokeWidth={1.5}
/>
) : (
<Sun
size={16}
strokeWidth={1.5}
/>
)}
</IconButton>
);
};
@@ -1,7 +1,7 @@
import { forwardRef } from "react";
import TextField from "@mui/material/TextField";
import type { TextFieldProps } from "@mui/material";
import { typographyLevels } from "@/Utils/Theme/v2Palette";
import { typographyLevels } from "@/Utils/Theme/Palette";
import { useTheme } from "@mui/material/styles";
import Stack from "@mui/material/Stack";
import { FieldLabel } from "./FieldLabel";
@@ -0,0 +1,61 @@
import { MobileTimePicker } from "@mui/x-date-pickers/MobileTimePicker";
import type { MobileTimePickerProps } from "@mui/x-date-pickers/MobileTimePicker";
import type { Dayjs } from "dayjs";
import { useTheme } from "@mui/material/styles";
import Stack from "@mui/material/Stack";
import { FieldLabel } from "./FieldLabel";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import { LocalizationProvider } from "@mui/x-date-pickers";
interface TimePickerComponentProps extends Omit<MobileTimePickerProps<Dayjs>, "label"> {
fieldLabel?: string;
required?: boolean;
}
export const TimePickerComponent = ({
fieldLabel,
required,
...props
}: TimePickerComponentProps) => {
const theme = useTheme();
const picker = (
<LocalizationProvider dateAdapter={AdapterDayjs}>
<MobileTimePicker
{...props}
slotProps={{
field: {
sx: {
width: "fit-content",
"& input": {
minHeight: 34,
p: 0,
px: theme.spacing(5),
},
"& fieldset": {
borderColor: theme.palette.divider,
borderRadius: theme.shape.borderRadius,
},
"&:not(:has(.Mui-disabled)):not(:has(.Mui-error)) .MuiOutlinedInput-root:not(:has(input:focus)):hover fieldset":
{
borderColor: theme.palette.divider,
},
},
},
...props.slotProps,
}}
/>
</LocalizationProvider>
);
if (fieldLabel) {
return (
<Stack spacing={theme.spacing(2)}>
<FieldLabel required={required}>{fieldLabel}</FieldLabel>
{picker}
</Stack>
);
}
return picker;
};
@@ -14,3 +14,7 @@ export * from "./Switch";
export * from "./Slider";
export * from "./ImageUpload";
export * from "./ColorPicker";
export { DatePickerComponent as DatePicker } from "./DatePicker";
export { TimePickerComponent as TimePicker } from "./TimePicker";
export * from "./LanguageSelector";
export * from "./SwitchTheme";
@@ -0,0 +1,62 @@
import Box from "@mui/material/Box";
import { useState, useEffect, useRef } from "react";
import { useTheme } from "@mui/material/styles";
import { useSelector } from "react-redux";
import BackgroundSVG from "@/assets/Images/background.svg";
import type { RootState } from "@/Types/state";
import { OfflineBanner } from "@/Components/design-elements";
import { setServerUnreachableCallback, get } from "@/Utils/ApiClient";
interface AppLayoutProps {
children: React.ReactNode;
}
const AppLayout = ({ children }: AppLayoutProps) => {
const theme = useTheme();
const mode = useSelector((state: RootState) => state.ui.mode);
const [serverUnreachable, setServerUnreachable] = useState(false);
const retryIntervalRef = useRef<number | null>(null);
useEffect(() => {
setServerUnreachableCallback(setServerUnreachable);
}, []);
useEffect(() => {
if (serverUnreachable) {
retryIntervalRef.current = window.setInterval(async () => {
try {
await get("/health", { timeout: 5000 });
} catch {
// NO_OP
}
}, 5000);
} else if (retryIntervalRef.current) {
clearInterval(retryIntervalRef.current);
retryIntervalRef.current = null;
}
return () => {
if (retryIntervalRef.current) {
clearInterval(retryIntervalRef.current);
}
};
}, [serverUnreachable]);
return (
<Box
sx={{
minHeight: "100vh",
backgroundColor: theme.palette.background.default,
backgroundImage: mode === "dark" ? `url("${BackgroundSVG}")` : "none",
backgroundSize: "100% 100%",
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
}}
>
<OfflineBanner visible={serverUnreachable} />
{children}
</Box>
);
};
export default AppLayout;
@@ -0,0 +1,44 @@
import { Sidebar } from "@/Components/sidebar";
import { Outlet } from "react-router";
import Stack from "@mui/material/Stack";
import { useMediaQuery } from "@mui/material";
import { useSidebar } from "@/Hooks/useSidebar";
import { useTheme } from "@mui/material";
const RootLayout = () => {
const theme = useTheme();
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
const { collapsedWidth } = useSidebar();
return (
<Stack flexDirection="row">
<Sidebar />
<Stack
flex={1}
padding={6}
overflow={"hidden"}
sx={{
backgroundColor:
theme.palette.mode === "dark"
? "rgba(255, 255, 255, 0.01)"
: "rgba(0, 0, 0, 0.01)",
display: "flex",
alignItems: "center",
paddingLeft: isSmall ? `${collapsedWidth + 12}px` : 12,
}}
>
<Stack
maxWidth={1280}
width="100%"
paddingY={theme.spacing(6)}
flex={1}
>
<Outlet />
</Stack>
</Stack>
</Stack>
);
};
export default RootLayout;
@@ -1,5 +1,5 @@
import Stack from "@mui/material/Stack";
import { Select, Button } from "@/Components/v2/inputs";
import { Select, Button } from "@/Components/inputs";
import MenuItem from "@mui/material/MenuItem";
import useMediaQuery from "@mui/material/useMediaQuery";
@@ -1,6 +1,6 @@
import Stack from "@mui/material/Stack";
import { Icon, MonitorStatus } from "@/Components/v2/design-elements";
import { Button } from "@/Components/v2/inputs";
import { Icon, MonitorStatus } from "@/Components/design-elements";
import { Button } from "@/Components/inputs";
import { Settings, Pause, Play, Mail, Bug, Trash } from "lucide-react";
import { useTranslation } from "react-i18next";
@@ -0,0 +1,35 @@
import {
UpStatusBox,
DownStatusBox,
PausedStatusBox,
InitializingStatusBox,
BreachedStatusBox,
} from "@/Components/design-elements";
import Stack from "@mui/material/Stack";
import type { MonitorsSummary } from "@/Types/Monitor";
import { useTheme } from "@mui/material";
interface MonitorsSummaryProps {
summary: MonitorsSummary | null;
showBreached?: boolean;
}
export const HeaderMonitorsSummary = ({
summary,
showBreached = false,
}: MonitorsSummaryProps) => {
const theme = useTheme();
return (
<Stack
direction={{ xs: "column", md: "row" }}
gap={theme.spacing(8)}
>
<UpStatusBox n={summary?.upMonitors || 0} />
<DownStatusBox n={summary?.downMonitors || 0} />
{showBreached && <BreachedStatusBox n={summary?.breachedMonitors || 0} />}
<PausedStatusBox n={summary?.pausedMonitors || 0} />
<InitializingStatusBox n={summary?.initializingMonitors || 0} />
</Stack>
);
};
@@ -1,9 +1,9 @@
import Stack from "@mui/material/Stack";
import { StatBox } from "@/Components/v2/design-elements";
import { StatBox } from "@/Components/design-elements";
import prettyMilliseconds from "pretty-ms";
import { useTheme } from "@mui/material/styles";
import type { MonitorStats, Monitor } from "@/Types/Monitor";
import { getHumanReadableDuration } from "@/Utils/timeUtilsLegacy.js";
import { getStatusPalette } from "@/Utils/MonitorUtils";
import { useTranslation } from "react-i18next";
@@ -32,11 +32,16 @@ export const MonitorStatBoxes = ({
// Determine time since last check
const timeOfLastCheck = monitorStats?.lastCheckTimestamp || 0;
const timeSinceLastCheck = Date.now() - timeOfLastCheck;
const timeSinceLastCheck = Date.now() - timeOfLastCheck || 0;
const streakTime = getHumanReadableDuration(timeSinceLastFailure);
const options = {
secondsDecimalDigits: 0,
millisecondsDecimalDigits: 0,
};
const lastCheckTime = getHumanReadableDuration(timeSinceLastCheck);
const streakTime = prettyMilliseconds(timeSinceLastFailure, options);
const lastCheckTime = prettyMilliseconds(timeSinceLastCheck, options);
const palette = getStatusPalette(monitor?.status);
return (
@@ -55,7 +60,7 @@ export const MonitorStatBoxes = ({
/>
<StatBox
title={t("pages.common.monitors.statBoxes.lastResponseTime")}
subtitle={monitorStats?.lastResponseTime + " ms"}
subtitle={prettyMilliseconds(monitorStats?.lastResponseTime ?? 0)}
/>
{monitor?.type === "http" && (
@@ -1,4 +1,4 @@
import { BaseChart, BaseBox } from "@/Components/v2/design-elements";
import { BaseChart, BaseBox } from "@/Components/design-elements";
import { Clock } from "lucide-react";
import {
AreaChart,
@@ -1,4 +1,4 @@
import { BaseChart } from "@/Components/v2/design-elements";
import { BaseChart } from "@/Components/design-elements";
import {
AreaChart,
Area,
@@ -8,7 +8,7 @@ import {
ResponsiveContainer,
} from "recharts";
import { Fragment, useId } from "react";
import { XTick } from "@/Components/v2/monitors";
import { XTick } from "@/Components/monitors";
import { useTheme } from "@mui/material/styles";
import type { HardwareCheckStats } from "@/Types/Monitor";
@@ -1,6 +1,6 @@
import Stack from "@mui/material/Stack";
import { Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip } from "recharts";
import { HistogramPageSpeedTooltip } from "@/Components/v2/monitors/charts/HistogramPageSpeedTooltip";
import { HistogramPageSpeedTooltip } from "@/Components/monitors/charts/HistogramPageSpeedTooltip";
import { useTheme } from "@mui/material/styles";
import type { CheckSnapshot } from "@/Types/Check";
import type { MonitorStatus } from "@/Types/Monitor";
@@ -1,6 +1,6 @@
import { BaseChart } from "@/Components/v2/design-elements";
import { BaseChart } from "@/Components/design-elements";
import { TrendingUp } from "lucide-react";
import { XTick } from "@/Components/v2/monitors/";
import { XTick } from "@/Components/monitors";
import {
XAxis,
AreaChart,
@@ -9,7 +9,7 @@ import {
CartesianGrid,
ResponsiveContainer,
} from "recharts";
import { HistogramPageSpeedScoresTooltip } from "@/Components/v2/monitors";
import { HistogramPageSpeedScoresTooltip } from "@/Components/monitors";
import { useTheme } from "@mui/material/styles";
import type { CheckSnapshot } from "@/Types/Check";
@@ -3,7 +3,7 @@ import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { useTheme } from "@mui/material/styles";
import { formatDateWithTz } from "@/Utils/TimeUtils";
import type { ConfigItem } from "@/Components/v2/monitors";
import type { ConfigItem } from "@/Components/monitors";
import type { TooltipProps } from "recharts";
import { useSelector } from "react-redux";
import type { RootState } from "@/Types/state";
@@ -1,9 +1,9 @@
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { BaseChart } from "@/Components/v2/design-elements";
import { BaseChart } from "@/Components/design-elements";
import { useTheme } from "@mui/material/styles";
import { useSelector } from "react-redux";
import { formatDateWithTz } from "@/Utils/timeUtilsLegacy";
import { formatDateWithTz } from "@/Utils/TimeUtils";
import { useTranslation } from "react-i18next";
import { ResponsiveContainer, BarChart, XAxis, Bar, Cell, Tooltip } from "recharts";
import { getResponseTimeColor } from "@/Utils/MonitorUtils";
@@ -1,4 +1,4 @@
import { BaseChart } from "@/Components/v2/design-elements";
import { BaseChart } from "@/Components/design-elements";
import { FileText } from "lucide-react";
import type { CheckAudits, CheckSnapshot } from "@/Types/Check";
import { Pie, PieChart, ResponsiveContainer, Label } from "recharts";
@@ -1,7 +1,7 @@
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import { BarChart3 } from "lucide-react";
import { BaseChart } from "@/Components/v2/design-elements";
import { BaseChart } from "@/Components/design-elements";
import Typography from "@mui/material/Typography";
import type { CheckSnapshot } from "@/Types/Check";
@@ -1,4 +1,4 @@
import { BaseChart } from "@/Components/v2/design-elements";
import { BaseChart } from "@/Components/design-elements";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { Gauge } from "lucide-react";
@@ -11,3 +11,4 @@ export * from "./charts/PiePageSpeedLegend";
export * from "./charts/HistogramPageSpeedDetails";
export * from "./charts/HistogramPageSpeedDetailsTooltip";
export * from "./charts/HistogramInfrastructure";
export * from "./HeaderMonitorsSummary";
@@ -0,0 +1,41 @@
import { Navigate } from "react-router-dom";
import { useSelector } from "react-redux";
import type { RootState } from "@/Types/state";
import type { UserRole } from "@/Types/User";
interface ProtectedRouteProps {
children: React.ReactNode;
}
export const ProtectedRoute = ({ children }: ProtectedRouteProps) => {
const authState = useSelector((state: RootState) => state.auth);
return authState.authToken ? (
children
) : (
<Navigate
to="/login"
replace
/>
);
};
interface RoleProtectedRouteProps {
roles: UserRole[];
children: React.ReactNode;
}
export const RoleProtectedRoute = ({ roles, children }: RoleProtectedRouteProps) => {
const authState = useSelector((state: RootState) => state.auth);
const userRoles = authState?.user?.role || [];
const canAccess = userRoles.some((role) => roles.includes(role));
return canAccess ? (
children
) : (
<Navigate
to="/uptime"
replace
/>
);
};
@@ -0,0 +1,183 @@
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import IconButton from "@mui/material/IconButton";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import { useTheme } from "@mui/material";
import { useSelector, useDispatch } from "react-redux";
import { useTranslation } from "react-i18next";
import { useState } from "react";
import { useNavigate } from "react-router";
import { MoreVertical, LogOut } from "lucide-react";
import { Avatar, Icon } from "@/Components/design-elements";
import { clearAuthState } from "@/Features/Auth/authSlice.js";
import type { RootState } from "@/Types/state.js";
interface AuthFooterProps {
collapsed: boolean;
accountMenuItems: Array<{
name: string;
path: string;
icon: React.ReactNode;
}>;
}
export const AuthFooter = ({ collapsed, accountMenuItems }: AuthFooterProps) => {
const { t } = useTranslation();
const theme = useTheme();
const user = useSelector((state: RootState) => state.auth.user);
const navigate = useNavigate();
const dispatch = useDispatch();
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const menuOpen = Boolean(anchorEl);
const handleMenuOpen = (e: React.MouseEvent<HTMLElement>) =>
setAnchorEl(e.currentTarget);
const handleMenuClose = () => setAnchorEl(null);
const handleNavigate = (path: string) => {
handleMenuClose();
navigate(path);
};
const handleLogout = () => {
dispatch(clearAuthState());
navigate("/login");
};
const getRoleText = () => {
const role = user?.role ?? "";
if (role.includes("superadmin"))
return t("components.sidebar.authFooter.roles.superAdmin");
if (role.includes("admin")) return t("components.sidebar.authFooter.roles.admin");
if (role.includes("user")) return t("components.sidebar.authFooter.roles.user");
if (role.includes("demo")) return t("components.sidebar.authFooter.roles.demoUser");
return role;
};
const filteredMenuItems = accountMenuItems.filter((item) => {
if (!user) return false;
if (item.name === "Password" && user.role?.includes("demo")) return false;
if (item.name === "Team" && !user.role?.includes("superadmin")) return false;
return true;
});
const menuItemSx = {
gap: theme.spacing(2),
borderRadius: theme.shape.borderRadius,
pl: theme.spacing(4),
"& svg": { stroke: theme.palette.text.secondary },
};
return (
<Stack
direction="row"
alignItems="center"
py={theme.spacing(4)}
px={theme.spacing(8)}
gap={theme.spacing(4)}
>
<Avatar
small
onClick={collapsed ? handleMenuOpen : undefined}
sx={{ cursor: collapsed ? "pointer" : "default" }}
/>
{!collapsed && (
<Stack
direction="row"
alignItems="center"
gap={theme.spacing(2)}
minWidth={0}
flex={1}
>
<Stack
minWidth={0}
flex={1}
>
<Typography
fontWeight={500}
noWrap
sx={{ color: theme.palette.text.primary }}
>
{user?.firstName} {user?.lastName}
</Typography>
<Typography
noWrap
sx={{ textTransform: "capitalize", color: theme.palette.text.secondary }}
>
{getRoleText()}
</Typography>
</Stack>
<IconButton
onClick={handleMenuOpen}
sx={{
"&:focus": { outline: "none" },
"& svg": { stroke: theme.palette.text.secondary },
}}
>
<Icon
icon={MoreVertical}
size={22}
/>
</IconButton>
</Stack>
)}
<Menu
anchorEl={anchorEl}
open={menuOpen}
onClose={handleMenuClose}
disableScrollLock
anchorOrigin={{ vertical: "top", horizontal: "right" }}
slotProps={{
paper: { sx: { mt: theme.spacing(-4), ml: collapsed ? theme.spacing(2) : 0 } },
}}
MenuListProps={{ sx: { p: 2, "& li": { m: 0 } } }}
sx={{ ml: theme.spacing(4) }}
>
{collapsed && (
<Box
px={2}
pb={2}
sx={{ pointerEvents: "none" }}
>
<Typography
fontWeight={500}
fontSize={13}
>
{user?.firstName} {user?.lastName}
</Typography>
<Typography
fontSize={12}
sx={{ textTransform: "capitalize" }}
>
{getRoleText()}
</Typography>
</Box>
)}
{filteredMenuItems.map((item) => (
<MenuItem
key={item.name}
onClick={() => handleNavigate(item.path)}
sx={menuItemSx}
>
{item.icon}
{item.name}
</MenuItem>
))}
<MenuItem
onClick={handleLogout}
sx={menuItemSx}
>
<Icon
icon={LogOut}
size={20}
/>
{t("components.sidebar.authFooter.logOut")}
</MenuItem>
</Menu>
</Stack>
);
};
@@ -1,37 +1,37 @@
import Stack from "@mui/material/Stack";
import Stack, { type StackProps } 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 { toggleSidebar } from "@/Features/UI/uiSlice.js";
import { useDispatch } from "react-redux";
import { useTranslation } from "react-i18next";
import PropTypes from "prop-types";
import useSidebar from "@/Hooks/useSidebar";
const Logo = ({ collapsed }) => {
export const Logo = (props: StackProps) => {
const { t } = useTranslation();
const theme = useTheme();
const navigate = useNavigate();
const dispatch = useDispatch();
const { collapsed } = useSidebar();
return (
<Stack
pt={theme.spacing(6)}
pb={theme.spacing(12)}
pl={theme.spacing(8)}
direction="row"
alignItems="center"
gap={theme.spacing(4)}
onClick={() => navigate("/")}
onClick={() => {
dispatch(toggleSidebar());
}}
sx={{ cursor: "pointer" }}
{...props}
>
<Typography
pl={theme.spacing("1px")}
minWidth={theme.spacing(16)}
minHeight={theme.spacing(16)}
display={"flex"}
justifyContent={"center"}
alignItems={"center"}
backgroundColor={theme.palette.accent.main}
bgcolor={theme.palette.primary.main}
borderRadius={theme.shape.borderRadius}
color={theme.palette.accent.contrastText}
color={theme.palette.primary.contrastText}
fontSize={18}
>
C
@@ -49,9 +49,8 @@ const Logo = ({ collapsed }) => {
<Typography
lineHeight={1}
mt={theme.spacing(2)}
color={theme.palette.primary.contrastText}
fontSize={"var(--env-var-font-size-medium-plus)"}
sx={{ fontWeight: 500 }}
variant="h2"
fontWeight={500}
>
{t("common.appName")}
</Typography>
@@ -59,9 +58,3 @@ const Logo = ({ collapsed }) => {
</Stack>
);
};
Logo.propTypes = {
collapsed: PropTypes.bool,
};
export default Logo;
+120
View File
@@ -0,0 +1,120 @@
import { Icon } from "@/Components/design-elements";
import {
Globe,
Gauge,
Link,
Bell,
FileText,
AlertTriangle,
Wifi,
Wrench,
Database,
Settings,
HelpCircle,
MessageCircle,
Code,
User,
Lock,
Users,
} from "lucide-react";
export const getMenu = (t: Function) => {
return [
{
name: t("components.sidebar.menu.uptime"),
path: "uptime",
icon: <Icon icon={Globe} />,
},
{
name: t("components.sidebar.menu.pagespeed"),
path: "pagespeed",
icon: <Icon icon={Gauge} />,
},
{
name: t("components.sidebar.menu.infrastructure"),
path: "infrastructure",
icon: <Icon icon={Link} />,
},
{
name: t("components.sidebar.menu.notifications"),
path: "notifications",
icon: <Icon icon={Bell} />,
},
{
name: t("components.sidebar.menu.checks"),
path: "checks",
icon: <Icon icon={FileText} />,
},
{
name: t("components.sidebar.menu.incidents"),
path: "incidents",
icon: <Icon icon={AlertTriangle} />,
},
{
name: t("components.sidebar.menu.statusPages"),
path: "status",
icon: <Icon icon={Wifi} />,
},
{
name: t("components.sidebar.menu.maintenance"),
path: "maintenance",
icon: <Icon icon={Wrench} />,
},
{
name: t("components.sidebar.menu.logs"),
path: "logs",
icon: <Icon icon={Database} />,
},
{
name: t("components.sidebar.menu.settings"),
icon: <Icon icon={Settings} />,
path: "settings",
},
];
};
export const getBottomMenu = (t: Function) => {
return [
{
name: t("components.sidebar.bottomMenu.support"),
path: "support",
icon: <Icon icon={HelpCircle} />,
},
{
name: t("components.sidebar.bottomMenu.discussions"),
path: "discussions",
icon: <Icon icon={MessageCircle} />,
},
{
name: t("components.sidebar.bottomMenu.docs"),
path: "docs",
icon: <Icon icon={FileText} />,
},
{
name: t("components.sidebar.bottomMenu.changelog"),
path: "changelog",
icon: <Icon icon={Code} />,
},
];
};
export const getAccountMenu = (t: Function) => {
return [
{
name: t("components.sidebar.accountMenu.profile"),
path: "account/profile",
icon: <Icon icon={User} />,
},
{
name: t("components.sidebar.accountMenu.password"),
path: "account/password",
icon: <Icon icon={Lock} />,
},
{
name: t("components.sidebar.accountMenu.team"),
path: "account/team",
icon: <Icon icon={Users} />,
},
];
};
@@ -3,19 +3,31 @@ 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 "@emotion/react";
import PropTypes from "prop-types";
import { useTheme } from "@mui/material/styles";
import useSidebar from "@/Hooks/useSidebar";
const NavItem = ({ item, collapsed, selected, onClick }) => {
export interface NavData {
name: string;
icon: React.ReactElement;
}
export const NavItem = ({
item,
selected,
onClick,
}: {
item: NavData;
selected: boolean;
onClick: (event: React.MouseEvent) => void;
}) => {
const { collapsed } = useSidebar();
const theme = useTheme();
const iconStroke = selected
? theme.palette.primary.contrastText
: theme.palette.primary.contrastTextTertiary;
const iconStroke = selected ? theme.palette.primary.main : theme.palette.text.secondary;
const buttonBgColor = selected ? theme.palette.secondary.main : "transparent";
const buttonBgColor = selected ? theme.palette.action.selected : "transparent";
const buttonBgHoverColor = selected
? theme.palette.secondary.main
: theme.palette.tertiary.main;
? theme.palette.action.selected
: theme.palette.action.hover;
const fontWeight = selected ? 600 : 400;
return (
<Tooltip
@@ -38,10 +50,14 @@ const NavItem = ({ item, collapsed, selected, onClick }) => {
<ListItemButton
sx={{
backgroundColor: buttonBgColor,
backgroundImage: "none",
border: 1,
borderColor: "transparent",
"&:hover": {
backgroundColor: buttonBgHoverColor,
backgroundImage: "none",
},
height: 37,
height: 32,
gap: theme.spacing(4),
borderRadius: theme.shape.borderRadius,
px: theme.spacing(4),
@@ -52,13 +68,20 @@ const NavItem = ({ item, collapsed, selected, onClick }) => {
<ListItemIcon
sx={{
minWidth: 0,
color: iconStroke,
"& svg": {
height: 20,
width: 20,
height: 16,
width: 16,
opacity: 0.81,
transition: "stroke 0.2s ease",
},
"& svg path, & svg line, & svg polyline, & svg rect, & svg circle": {
stroke: iconStroke,
},
".MuiListItemButton-root:hover &": {
"& svg path, & svg line, & svg polyline, & svg rect, & svg circle": {
stroke: theme.palette.primary.main,
},
},
}}
>
{item.icon}
@@ -73,7 +96,6 @@ const NavItem = ({ item, collapsed, selected, onClick }) => {
>
<Typography
variant="body1"
color={theme.palette.primary.contrastText}
sx={{
fontWeight: fontWeight,
opacity: 0.9,
@@ -86,11 +108,3 @@ const NavItem = ({ item, collapsed, selected, onClick }) => {
</Tooltip>
);
};
NavItem.propTypes = {
item: PropTypes.object,
collapsed: PropTypes.bool,
selected: PropTypes.bool,
onClick: PropTypes.func,
};
export default NavItem;
@@ -1,18 +1,25 @@
import React from "react";
import { Typography, IconButton, Stack, Box } from "@mui/material";
import Icon from "../Icon";
import { useTheme } from "@emotion/react";
import { Icon } from "@/Components/design-elements";
import { X } from "lucide-react";
import { useTheme } from "@mui/material";
import { useSelector, useDispatch } from "react-redux";
import { useTranslation } from "react-i18next";
import { setStarPromptOpen } from "../../../Features/UI/uiSlice.js";
import { setStarPromptOpen } from "@/Features/UI/uiSlice.js";
import type { RootState } from "@/Types/state.js";
import useSidebar from "@/Hooks/useSidebar.js";
const StarPrompt = ({ repoUrl = "https://github.com/bluewave-labs/checkmate" }) => {
export const StarPrompt = ({
repoUrl = "https://github.com/bluewave-labs/checkmate",
}: {
repoUrl?: string;
}) => {
const theme = useTheme();
const dispatch = useDispatch();
const { t } = useTranslation();
const isOpen = useSelector((state) => state.ui?.starPromptOpen ?? true);
const mode = useSelector((state) => state.ui.mode);
const isOpen = useSelector((state: RootState) => state.ui?.starPromptOpen ?? true);
const mode = useSelector((state: RootState) => state.ui.mode);
const { collapsed } = useSidebar();
const handleClose = () => {
dispatch(setStarPromptOpen(false));
};
@@ -21,6 +28,7 @@ const StarPrompt = ({ repoUrl = "https://github.com/bluewave-labs/checkmate" })
window.open(repoUrl, "_blank");
};
if (collapsed) return null;
if (!isOpen) return null;
return (
@@ -29,8 +37,8 @@ const StarPrompt = ({ repoUrl = "https://github.com/bluewave-labs/checkmate" })
sx={{
width: "100%",
padding: `${theme.spacing(6)} ${theme.spacing(6)}`,
borderTop: `1px solid ${theme.palette.primary.lowContrast}`,
borderBottom: `1px solid ${theme.palette.primary.lowContrast}`,
borderTop: `1px solid ${theme.palette.divider}`,
borderBottom: `1px solid ${theme.palette.divider}`,
borderRadius: 0,
gap: theme.spacing(1.5),
}}
@@ -44,15 +52,9 @@ const StarPrompt = ({ repoUrl = "https://github.com/bluewave-labs/checkmate" })
>
<Typography
variant="subtitle2"
sx={{
color:
mode === "dark"
? theme.palette.primary.contrastText
: theme.palette.text.primary,
mt: theme.spacing(3),
}}
mt={theme.spacing(3)}
>
{t("starPromptTitle")}
{t("components.sidebar.starPrompt.title")}
</Typography>
<IconButton
onClick={handleClose}
@@ -60,7 +62,6 @@ const StarPrompt = ({ repoUrl = "https://github.com/bluewave-labs/checkmate" })
sx={{
color: theme.palette.text.primary,
padding: 0,
marginTop: theme.spacing(-5),
"&:hover": {
backgroundColor: "transparent",
opacity: 0.8,
@@ -68,23 +69,19 @@ const StarPrompt = ({ repoUrl = "https://github.com/bluewave-labs/checkmate" })
}}
>
<Icon
name="X"
icon={X}
size={20}
/>
</IconButton>
</Stack>
<Typography
variant="body1"
sx={{
color: theme.palette.primary.contrastTextTertiary,
fontSize: "0.938rem",
lineHeight: 1.5,
mb: 1,
px: theme.spacing(4),
}}
>
{t("starPromptDescription")}
{t("components.sidebar.starPrompt.description")}
</Typography>
<Box
@@ -106,5 +103,3 @@ const StarPrompt = ({ repoUrl = "https://github.com/bluewave-labs/checkmate" })
</Stack>
);
};
export default StarPrompt;
+131
View File
@@ -0,0 +1,131 @@
import { useEffect } from "react";
import Backdrop from "@mui/material/Backdrop";
import Stack from "@mui/material/Stack";
import List from "@mui/material/List";
import Divider from "@mui/material/Divider";
import { useSidebar } from "@/Hooks/useSidebar.js";
import { Logo } from "@/Components/sidebar/Logo";
import { getMenu, getBottomMenu, getAccountMenu } from "@/Components/sidebar/Menu";
import { NavItem } from "@/Components/sidebar/NavItem";
import { StarPrompt } from "@/Components/sidebar/StarPrompt";
import { AuthFooter } from "@/Components/sidebar/Authfooter";
import { useNavigate, useLocation } from "react-router";
import { useTranslation } from "react-i18next";
import { useTheme, useMediaQuery } from "@mui/material";
import { useDispatch } from "react-redux";
import { setCollapsed } from "@/Features/UI/uiSlice";
const URL_MAP: Record<string, string> = {
support: "https://discord.com/invite/NAb6H3UTjK",
discussions: "https://github.com/bluewave-labs/checkmate/discussions",
docs: "https://bluewavelabs.gitbook.io/checkmate",
changelog: "https://github.com/bluewave-labs/checkmate/releases",
};
export const Sidebar = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const location = useLocation();
const { t } = useTranslation();
const { width, transition, collapsed } = useSidebar();
const theme = useTheme();
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
const menu = getMenu(t);
const bottomMenu = getBottomMenu(t);
const accountMenu = getAccountMenu(t);
useEffect(() => {
dispatch(setCollapsed({ collapsed: isSmall }));
}, [isSmall, dispatch]);
const handleNavClick = (path: string) => {
const url = URL_MAP[path];
if (url) {
window.open(url, "_blank", "noreferrer");
} else {
navigate(`/${path}`);
}
if (isSmall) {
dispatch(setCollapsed({ collapsed: true }));
}
};
return (
<>
<Backdrop
open={!collapsed && isSmall}
onClick={() => dispatch(setCollapsed({ collapsed: true }))}
sx={{ zIndex: 999 }}
/>
<Stack
component="aside"
position={isSmall ? "fixed" : "sticky"}
top={0}
left={0}
minHeight={"100vh"}
maxHeight={"100vh"}
paddingTop={theme.spacing(6)}
paddingBottom={theme.spacing(6)}
gap={theme.spacing(6)}
borderRight={`1px solid ${theme.palette.divider}`}
width={width}
sx={{
touchAction: "none",
transition: transition,
zIndex: 1000,
backdropFilter: "blur(8px)",
}}
>
<List
component="nav"
disablePadding
sx={{
px: theme.spacing(6),
flex: 1,
}}
>
<Logo
pt={theme.spacing(8)}
pb={theme.spacing(10)}
/>
{menu.map((item) => {
const selected = location.pathname.startsWith(`/${item.path}`);
return (
<NavItem
key={item.path}
item={item}
selected={selected}
onClick={() => handleNavClick(item.path)}
/>
);
})}
</List>
<StarPrompt />
<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}
selected={selected}
onClick={() => handleNavClick(item.path)}
/>
);
})}
</List>
<Divider sx={{ borderColor: theme.palette.divider }} />
<AuthFooter
collapsed={collapsed}
accountMenuItems={accountMenu}
/>
</Stack>
</>
);
};
@@ -1,228 +0,0 @@
// Components
import IconButton from "@mui/material/IconButton";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import Icon from "../Icon";
import Dialog from "../Dialog/index.jsx";
// Utils
import { useState } from "react";
import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router-dom";
import { createToast } from "../../../Utils/toastUtils.jsx";
import PropTypes from "prop-types";
import { usePauseMonitor, useDeleteMonitor } from "../../../Hooks/monitorHooks.js";
const ActionsMenu = ({
monitor,
isAdmin,
updateRowCallback,
pauseCallback,
setIsLoading = () => {},
}) => {
const [anchorEl, setAnchorEl] = useState(null);
const [actions, setActions] = useState({});
const [isOpen, setIsOpen] = useState(false);
const theme = useTheme();
const [pauseMonitor, isPausing, error] = usePauseMonitor();
const [deleteMonitor, isDeleting] = useDeleteMonitor();
const handleRemove = async (event) => {
event.preventDefault();
event.stopPropagation();
let monitor = { id: actions.id };
await deleteMonitor({ monitor });
updateRowCallback();
};
const handlePause = async () => {
try {
setIsLoading(true);
await pauseMonitor({ monitorId: monitor.id });
pauseCallback();
} catch (error) {
createToast({ body: "Failed to pause monitor." });
} finally {
setIsLoading(false);
}
};
const openMenu = (event, id, url) => {
event.preventDefault();
event.stopPropagation();
setAnchorEl(event.currentTarget);
setActions({ id: id, url: url });
};
const openRemove = (e) => {
closeMenu(e);
setIsOpen(true);
};
const closeMenu = (e) => {
e.stopPropagation();
setAnchorEl(null);
};
const navigate = useNavigate();
return (
<>
<IconButton
aria-label="monitor actions"
onClick={(event) => {
event.stopPropagation();
openMenu(event, monitor.id, monitor.type === "ping" ? null : monitor.url);
}}
sx={{
"&:focus": {
outline: "none",
},
"& svg path": {
stroke: theme.palette.primary.contrastTextTertiary,
},
}}
>
<Icon
name="Settings"
size={20}
/>
</IconButton>
<Menu
className="actions-menu"
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={(e) => closeMenu(e)}
disableScrollLock
slotProps={{
paper: {
sx: {
"& ul": {
p: theme.spacing(2.5),
backgroundColor: theme.palette.primary.main,
},
"& li": { m: 0, color: theme.palette.primary.contrastTextSecondary },
/*
This should not be set automatically on the last of type
"& li:last-of-type": {
color: theme.palette.error.main,
}, */
},
},
}}
>
{actions.url !== null ? (
<MenuItem
onClick={(e) => {
closeMenu(e);
e.stopPropagation();
window.open(actions.url, "_blank", "noreferrer");
}}
>
Open site
</MenuItem>
) : (
""
)}
<MenuItem
onClick={(e) => {
e.stopPropagation();
navigate(`/uptime/${actions.id}`);
}}
>
Details
</MenuItem>
{/* TODO - pass monitor id to Incidents page */}
<MenuItem
onClick={(e) => {
e.stopPropagation();
navigate(`/incidents/${actions.id}`);
}}
>
Incidents
</MenuItem>
{isAdmin && (
<MenuItem
onClick={(e) => {
e.stopPropagation();
navigate(`/uptime/configure/${actions.id}`);
}}
>
Configure
</MenuItem>
)}
{isAdmin && (
<MenuItem
onClick={(e) => {
e.stopPropagation();
navigate(`/uptime/create/${actions.id}`);
}}
>
Clone
</MenuItem>
)}
{isAdmin && (
<MenuItem
onClick={(e) => {
closeMenu(e);
e.stopPropagation();
handlePause(e);
}}
>
{monitor?.isActive === true ? "Pause" : "Resume"}
</MenuItem>
)}
{isAdmin && (
<MenuItem
onClick={(e) => {
e.stopPropagation();
openRemove(e);
}}
sx={{ "&.MuiButtonBase-root": { color: theme.palette.error.main } }}
>
Remove
</MenuItem>
)}
</Menu>
<Dialog
open={isOpen}
theme={theme}
title="Do you really want to delete this monitor?"
description="Once deleted, this monitor cannot be retrieved."
/* Do we need stop propagation? */
onCancel={(e) => {
e.stopPropagation();
setIsOpen(false);
}}
confirmationButtonLabel="Delete"
/* Do we need stop propagation? */
onConfirm={(e) => {
console.log(e);
e.stopPropagation();
handleRemove(e);
}}
isLoading={isDeleting}
modelTitle="modal-delete-monitor"
modelDescription="delete-monitor-confirmation"
/>
</>
);
};
ActionsMenu.propTypes = {
monitor: PropTypes.shape({
id: PropTypes.string,
url: PropTypes.string,
type: PropTypes.string,
isActive: PropTypes.bool,
}).isRequired,
isAdmin: PropTypes.bool,
updateRowCallback: PropTypes.func,
pauseCallback: PropTypes.func,
setIsLoading: PropTypes.func,
};
export default ActionsMenu;
-9
View File
@@ -1,9 +0,0 @@
.alert {
margin: 0;
width: fit-content;
}
.alert,
.alert button,
.alert .MuiTypography-root {
font-size: var(--env-var-font-size-medium);
}
-138
View File
@@ -1,138 +0,0 @@
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { Box, Button, IconButton, Stack, Typography } from "@mui/material";
import Icon from "../Icon";
import "./index.css";
/**
* Icons mapping for different alert variants.
* @type {Object<string, JSX.Element>}
*/
const icons = {
info: (
<Icon
name="Info"
size={24}
/>
),
error: (
<Icon
name="AlertCircle"
size={24}
/>
),
warning: (
<Icon
name="AlertTriangle"
size={24}
/>
),
};
/**
* @param {Object} props
* @param {'info' | 'error' | 'warning'} props.variant - The type of alert.
* @param {string} [props.title] - The title of the alert.
* @param {string} [props.body] - The body text of the alert.
* @param {boolean} [props.isToast] - Indicates if the alert is used as a toast notification.
* @param {boolean} [props.hasIcon] - Whether to display an icon in the alert.
* @param {function} props.onClick - Toast dismiss function.
* @returns {JSX.Element}
*/
const Alert = ({ variant, title, body, isToast, hasIcon = true, onClick }) => {
const theme = useTheme();
/* TODO
Do we need other variants for alert?
*/
const text = theme.palette.secondary.contrastText;
const border = theme.palette.alert.contrastText;
const bg = theme.palette.alert.main;
const icon = icons[variant];
return (
<Stack
direction="row"
justifyContent="flex-start"
alignItems={hasIcon ? "" : "center"}
className="alert row-stack"
gap={theme.spacing(8)}
sx={{
padding: hasIcon ? theme.spacing(8) : `${theme.spacing(4)} ${theme.spacing(8)}`,
backgroundColor: bg,
border: `solid 1px ${border}`,
borderRadius: theme.shape.borderRadius,
}}
>
{hasIcon && <Box sx={{ color: text }}>{icon}</Box>}
<Stack
direction="column"
gap="2px"
sx={{ flex: 1 }}
>
{title && (
<Typography sx={{ fontWeight: "700", color: `${text}` }}>{title}</Typography>
)}
{body && (
<Typography sx={{ fontWeight: "400", color: `${text}` }}>{body}</Typography>
)}
{hasIcon && isToast && (
<Button
variant="text"
color="info"
onClick={onClick}
sx={{
fontWeight: "600",
width: "fit-content",
mt: theme.spacing(4),
padding: 0,
minWidth: 0,
}}
>
Dismiss
</Button>
)}
</Stack>
{isToast && (
<IconButton
onClick={onClick}
sx={{
alignSelf: "flex-start",
ml: "auto",
mr: "-5px",
mt: hasIcon ? "-5px" : 0,
padding: "5px",
"&:focus": {
outline: "none",
},
}}
>
<Icon
name="X"
size={20}
/>
</IconButton>
)}
</Stack>
);
};
Alert.propTypes = {
variant: PropTypes.oneOf(["info", "error", "warning"]).isRequired,
title: PropTypes.string,
body: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
isToast: PropTypes.bool,
hasIcon: PropTypes.bool,
onClick: function (props, propName, componentName) {
if (props.isToast && !props[propName]) {
return new Error(
`Prop '${propName}' is required when 'isToast' is true in '${componentName}'.`
);
}
return null;
},
};
export default Alert;
@@ -1,68 +0,0 @@
import PropTypes from "prop-types";
import { Box, Stack, useTheme } from "@mui/material";
// import useUtils from "../../Pages/Uptime/utils";
/**
* A component that renders a pulsating dot with a specified color.
*
* @component
* @example
* // Example usage:
* <PulseDot color="#f00" />
*
* @param {Object} props
* @param {string} props.color - The color of the dot.
* @returns {JSX.Element} The PulseDot component.
*/
const PulseDot = ({ color }) => {
const theme = useTheme();
// const { statusToTheme } = useUtils();
/* TODO refactor so it gets status and gets theme color. Then uses theme.palette.[themeColor].lowContrast */
/* const themeColor = statusToTheme[status]; */
return (
<Stack
width="26px"
height="24px"
alignItems="center"
justifyContent="center"
>
<Box
minWidth="18px"
minHeight="18px"
sx={{
position: "relative",
backgroundColor: color,
borderRadius: "50%",
"&::before": {
content: `""`,
position: "absolute",
width: "100%",
height: "100%",
backgroundColor: "inherit",
borderRadius: "50%",
animation: "ripple 1.8s ease-out infinite",
},
"&::after": {
content: `""`,
position: "absolute",
width: "7px",
height: "7px",
borderRadius: "50%",
backgroundColor: theme.palette.accent.contrastText,
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
},
}}
/>
</Stack>
);
};
PulseDot.propTypes = {
color: PropTypes.string.isRequired,
};
export default PulseDot;
@@ -1,36 +0,0 @@
import Icon from "../Icon";
import PropTypes from "prop-types";
const ArrowLeft = ({ type, color = "#667085", ...props }) => {
if (type === "double") {
return (
<Icon
name="ChevronsLeft"
color={color}
{...props}
/>
);
} else if (type === "long") {
return (
<Icon
name="ArrowLeft"
color={color}
{...props}
/>
);
} else {
return (
<Icon
name="ChevronLeft"
color={color}
{...props}
/>
);
}
};
ArrowLeft.propTypes = {
color: PropTypes.string,
type: PropTypes.oneOf(["double", "long", "default"]),
};
export default ArrowLeft;
@@ -1,29 +0,0 @@
import Icon from "../Icon";
import PropTypes from "prop-types";
const ArrowRight = ({ type, color = "#667085", ...props }) => {
if (type === "double") {
return (
<Icon
name="ChevronsRight"
color={color}
{...props}
/>
);
} else {
return (
<Icon
name="ChevronRight"
color={color}
{...props}
/>
);
}
};
ArrowRight.propTypes = {
type: PropTypes.oneOf(["double", "default"]),
color: PropTypes.string,
};
export default ArrowRight;
Whitespace-only changes.
-73
View File
@@ -1,73 +0,0 @@
import { Avatar as MuiAvatar } from "@mui/material";
import PropTypes from "prop-types";
import { useSelector } from "react-redux";
import { useEffect, useState } from "react";
import { useTheme } from "@emotion/react";
/**
* @component
* @param {Object} props
* @param {string} props.src - Path to image for avatar
* @param {boolean} props.small - Specifies if avatar should be large
* @param {Object} [props.sx] - Additional styles to apply to the button.
* @returns {JSX.Element}
* @example
* // Render a red label
* <Avatar src="assets/img" first="Alex" last="Holliday" small />
*/
const Avatar = ({ src, small, sx, onClick = () => {} }) => {
const { user } = useSelector((state) => state.auth);
const theme = useTheme();
const style = small ? { width: 32, height: 32 } : { width: 64, height: 64 };
const border = small ? 1 : 3;
const [image, setImage] = useState();
useEffect(() => {
if (user.avatarImage) {
setImage(`data:image/png;base64,${user.avatarImage}`);
}
}, [user?.avatarImage]);
return (
<MuiAvatar
onClick={onClick}
alt={`${user?.firstName} ${user?.lastName}`}
/* TODO What is the /static/images/avatar/2.jpg ?*/
src={src ? src : user?.avatarImage ? image : "/static/images/avatar/2.jpg"}
sx={{
fontSize: small ? "16px" : "22px",
fontWeight: 400,
color: theme.palette.accent.contrastText,
backgroundColor: theme.palette.accent.main, // Same BG color as checkmate BG in sidebar
display: "inline-flex",
/*
TODO not sure what this is for*/
"&::before": {
content: `""`,
position: "absolute",
top: 0,
left: 0,
width: `100%`,
height: `100%`,
border: `${border}px solid rgba(255,255,255,0.2)`,
borderRadius: "50%",
},
...style,
...sx,
}}
>
{user.firstName?.charAt(0)}
{user.lastName?.charAt(0) || ""}
</MuiAvatar>
);
};
Avatar.propTypes = {
src: PropTypes.string,
small: PropTypes.bool,
sx: PropTypes.object,
onClick: PropTypes.func,
};
export default Avatar;
@@ -1,22 +0,0 @@
.MuiBreadcrumbs-root {
min-height: 34px;
}
.MuiBreadcrumbs-root svg {
width: 16px;
min-height: 16px;
}
.MuiBreadcrumbs-root .MuiBreadcrumbs-li a {
font-size: var(--env-var-font-size-medium);
font-weight: 400;
}
.MuiBreadcrumbs-root .MuiBreadcrumbs-li:not(:last-child) {
cursor: pointer;
}
.MuiBreadcrumbs-root .MuiBreadcrumbs-li:last-child a {
font-weight: 500;
opacity: 1;
cursor: default;
}
.MuiBreadcrumbs-root .MuiBreadcrumbs-separator {
margin: 0;
}
@@ -1,78 +0,0 @@
import PropTypes from "prop-types";
import { Box, Breadcrumbs as MUIBreadcrumbs } from "@mui/material";
import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router-dom";
import ArrowRight from "../ArrowRight/index.jsx";
import "./index.css";
/**
* Breadcrumbs component that displays a list of breadcrumb items.
*
* @param {Object} props
* @param {Array} props.list - Array of breadcrumb items. Each item should have `name` and `path` properties.
* @param {string} props.list.name - The name to display for the breadcrumb.
* @param {string} props.list.path - The path to navigate to when the breadcrumb is clicked.
*
* @returns {JSX.Element} The rendered Breadcrumbs component.
*/
const Breadcrumbs = ({ list }) => {
const theme = useTheme();
const navigate = useNavigate();
return (
<MUIBreadcrumbs
separator={<ArrowRight />}
aria-label="breadcrumb"
px={theme.spacing(2)}
py={theme.spacing(3.5)}
width="fit-content"
backgroundColor={theme.palette.secondary.main}
borderRadius={theme.shape.borderRadius}
lineHeight="18px"
sx={{
"& .MuiBreadcrumbs-li a": {
transition: "background-color 0.2s ease-in-out, color 0.2s ease-in-out",
},
"& .MuiBreadcrumbs-li:not(:last-of-type):hover a": {
backgroundColor: theme.palette.secondary.contrastText,
color: theme.palette.secondary.main,
},
}}
>
{list.map((item, index) => {
return (
<Box
component="a"
key={`${item.name}-${index}`}
px={theme.spacing(4)}
pt={theme.spacing(2)}
pb={theme.spacing(3)}
borderRadius={theme.shape.borderRadius}
onClick={() => navigate(item.path)}
sx={{
opacity: 0.8,
textTransform: "capitalize",
"&, &:hover": {
color: theme.palette.secondary.contrastText,
},
}}
>
{item.name}
</Box>
);
})}
</MUIBreadcrumbs>
);
};
Breadcrumbs.propTypes = {
list: PropTypes.arrayOf(
PropTypes.shape({
name: PropTypes.string.isRequired,
path: PropTypes.string.isRequired,
}).isRequired
).isRequired,
};
export default Breadcrumbs;
@@ -1,30 +0,0 @@
import React from "react";
import Button from "@mui/material/Button";
import { styled } from "@mui/material/styles";
const RoundGradientButton = styled(Button)(({ theme }) => ({
position: "relative",
border: "5px solid transparent",
backgroundClip: "padding-box",
borderRadius: 30,
fontSize: "1.2rem",
color: theme.palette.primary.contrastText,
backgroundColor: theme.palette.background.main,
"&:after": {
position: "absolute",
top: -3,
left: -3,
right: -3,
bottom: -3,
background:
theme.palette.mode === "dark"
? "linear-gradient(90deg, #842bd2, #ff5451, #8c52ff)"
: "linear-gradient(90deg, #842bd2, #ff5451, #8c52ff)",
content: '""',
zIndex: -1,
borderRadius: 30,
},
}));
export default RoundGradientButton;
@@ -1,214 +0,0 @@
/**
* CustomAreaChart component for rendering an area chart with optional gradient and custom ticks.
*
* @param {Object} props - The properties object.
* @param {Array} props.data - The data array for the chart.
* @param {Array} props.dataKeys - An array of data keys to be plotted as separate areas.
* @param {string} props.xKey - The key for the x-axis data.
* @param {string} [props.yKey] - The key for the y-axis data (optional).
* @param {Object} [props.xTick] - Custom tick component for the x-axis.
* @param {Object} [props.yTick] - Custom tick component for the y-axis.
* @param {string} [props.strokeColor] - The base stroke color for the areas.
* If not provided, uses a predefined color palette.
* @param {string} [props.fillColor] - The base fill color for the areas.
* @param {boolean} [props.gradient=false] - Whether to apply a gradient fill to the areas.
* @param {string} [props.gradientDirection="vertical"] - The direction of the gradient.
* @param {string} [props.gradientStartColor] - The start color of the gradient.
* Defaults to the area's stroke color if not provided.
* @param {string} [props.gradientEndColor] - The end color of the gradient.
* @param {Object} [props.customTooltip] - Custom tooltip component for the chart.
* @param {string|number} [props.height="100%"] - Height of the chart container.
*
* @returns {JSX.Element} The rendered area chart component.
*
* @example
* // Single series chart
* <CustomAreaChart
* data={temperatureData}
* dataKeys={["temperature"]}
* xKey="date"
* yKey="temperature"
* gradient={true}
* gradientStartColor="#ff6b6b"
* gradientEndColor="#4ecdc4"
* />
*
* @example
* // Multi-series chart with custom tooltip
* <CustomAreaChart
* data={performanceData}
* dataKeys={["cpu.usage", "memory.usage"]}
* xKey="timestamp"
* xTick={<CustomTimeTick />}
* yTick={<PercentageTick />}
* gradient={true}
* customTooltip={({ active, payload, label }) => (
* <CustomTooltip
* label={label}
* payload={payload}
* active={active}
* />
* )}
* />
*/
import {
AreaChart,
Area,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
} from "recharts";
import { createGradient } from "../Utils/gradientUtils.jsx";
import PropTypes from "prop-types";
import { useTheme } from "@mui/material";
import { useId } from "react";
import { Fragment } from "react";
const CustomAreaChart = ({
data,
dataKeys,
xKey,
xDomain,
yKey,
yDomain,
xTick,
yTick,
strokeColor,
fillColor,
gradient = false,
gradientDirection = "vertical",
gradientStartColor,
gradientEndColor,
customTooltip,
height = "100%",
}) => {
const theme = useTheme();
const uniqueId = useId();
const AREA_COLORS = [
// Blues
"#3182bd", // Deep blue
"#6baed6", // Medium blue
"#9ecae1", // Light blue
// Greens
"#74c476", // Soft green
"#a1d99b", // Light green
"#c7e9c0", // Pale green
// Oranges
"#fdae6b", // Warm orange
"#fdd0a2", // Light orange
"#feedde", // Pale orange
// Purples
"#9467bd", // Lavender
"#a55194", // Deep magenta
"#c994c7", // Soft magenta
// Reds
"#ff9896", // Soft red
"#de2d26", // Deep red
"#fc9272", // Medium red
// Cyans/Teals
"#17becf", // Cyan
"#7fcdbb", // Teal
"#a1dab4", // Light teal
// Yellows
"#fec44f", // Mustard
"#fee391", // Light yellow
"#ffffd4", // Pale yellow
// Additional colors
"#e377c2", // Soft pink
"#bcbd22", // Olive
"#2ca02c", // Vibrant green
];
return (
<ResponsiveContainer
width="100%"
height={height}
// FE team HELP! Why does this overflow if set to 100%?
>
<AreaChart data={data}>
<XAxis
dataKey={xKey}
{...(xDomain && { domain: xDomain })}
{...(xTick && { tick: xTick })}
/>
<YAxis
dataKey={yKey}
{...(yDomain && { domain: yDomain })}
{...(yTick && { tick: yTick })}
/>
<CartesianGrid
stroke={theme.palette.primary.lowContrast}
strokeWidth={1}
strokeOpacity={1}
fill="transparent"
vertical={false}
/>
{dataKeys.map((dataKey, index) => {
const gradientId = `gradient-${uniqueId}-${index}`;
return (
<Fragment key={dataKey}>
{gradient === true &&
createGradient({
id: gradientId,
startColor: gradientStartColor || AREA_COLORS[index],
endColor: gradientEndColor,
direction: gradientDirection,
})}
<Area
yKey={dataKey}
key={dataKey}
type="monotone"
dataKey={dataKey}
stroke={strokeColor || AREA_COLORS[index]}
fill={gradient === true ? `url(#${gradientId})` : fillColor}
/>
</Fragment>
);
})}
{customTooltip ? (
<Tooltip
cursor={{ stroke: theme.palette.primary.lowContrast }}
content={customTooltip}
wrapperStyle={{ pointerEvents: "none" }}
/>
) : (
<Tooltip />
)}
</AreaChart>
</ResponsiveContainer>
);
};
CustomAreaChart.propTypes = {
data: PropTypes.array.isRequired,
dataKeys: PropTypes.array.isRequired,
xTick: PropTypes.object, // Recharts takes an instance of component, so we can't pass the component itself
yTick: PropTypes.object, // Recharts takes an instance of component, so we can't pass the component itself
xKey: PropTypes.string.isRequired,
xDomain: PropTypes.array,
yKey: PropTypes.string,
yDomain: PropTypes.array,
fillColor: PropTypes.string,
strokeColor: PropTypes.string,
gradient: PropTypes.bool,
gradientDirection: PropTypes.string,
gradientStartColor: PropTypes.string,
gradientEndColor: PropTypes.string,
customTooltip: PropTypes.object,
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
};
export default CustomAreaChart;
Whitespace-only changes.
@@ -1,181 +0,0 @@
import { useTheme } from "@emotion/react";
import { Box, Stack, Tooltip, Typography } from "@mui/material";
import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js";
import { useEffect, useState } from "react";
import "./index.css";
import { useSelector } from "react-redux";
/* TODO add prop validation and jsdocs */
const BarChart = ({ checks = [] }) => {
const theme = useTheme();
const [animate, setAnimate] = useState(false);
const uiTimezone = useSelector((state) => state.ui.timezone);
useEffect(() => {
setAnimate(true);
});
// set responseTime to average if there's only one check
if (checks.length === 1) {
checks[0] = { ...checks[0], responseTime: 50 };
}
if (checks.length !== 25) {
const placeholders = Array(25 - checks.length).fill("placeholder");
checks = [...checks, ...placeholders];
}
return (
<Stack
direction="row"
flexWrap="nowrap"
gap={theme.spacing(1.5)}
height="50px"
width="fit-content"
onClick={(event) => event.stopPropagation()}
sx={{
cursor: "default",
}}
>
{checks.map((check, index) =>
check === "placeholder" ? (
/* TODO what is the purpose of this box? */
// CAIO_REVIEW the purpose of this box is to make sure there are always at least 25 bars
// even if there are less than 25 checks
<Box
key={`${check}-${index}`}
position="relative"
width={theme.spacing(4.5)}
height="100%"
backgroundColor={theme.palette.primary.lowContrast}
sx={{
borderRadius: theme.spacing(1.5),
}}
/>
) : (
<Tooltip
title={
<>
<Typography>
{formatDateWithTz(
check.updatedAt,
"ddd, MMMM D, YYYY, HH:mm A",
uiTimezone
)}
</Typography>
<Box mt={theme.spacing(2)}>
<Box
display="inline-block"
width={theme.spacing(4)}
height={theme.spacing(4)}
backgroundColor={
check.status
? theme.palette.success.lowContrast
: theme.palette.error.lowContrast
}
sx={{ borderRadius: "50%" }}
/>
<Stack
display="inline-flex"
direction="row"
justifyContent="space-between"
ml={theme.spacing(2)}
gap={theme.spacing(12)}
>
<Typography
component="span"
sx={{ opacity: 0.8 }}
>
Response Time
</Typography>
<Typography component="span">
{check.originalResponseTime}
<Typography
component="span"
sx={{ opacity: 0.8 }}
>
{" "}
ms
</Typography>
</Typography>
</Stack>
</Box>
</>
}
placement="top"
key={`check-${check?.id}`}
slotProps={{
popper: {
className: "bar-tooltip",
modifiers: [
{
name: "offset",
options: {
offset: [0, -10],
},
},
],
sx: {
"& .MuiTooltip-tooltip": {
backgroundColor: theme.palette.secondary.main,
border: 1,
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
boxShadow: theme.shape.boxShadow,
px: theme.spacing(4),
py: theme.spacing(3),
},
"& .MuiTooltip-tooltip p": {
/* TODO Font size should point to theme */
fontSize: 12,
color: theme.palette.secondary.contrastText,
fontWeight: 500,
},
"& .MuiTooltip-tooltip span": {
/* TODO Font size should point to theme */
fontSize: 11,
color: theme.palette.secondary.contrastText,
fontWeight: 600,
},
},
},
}}
>
<Box
position="relative"
width="9px"
height="100%"
backgroundColor={theme.palette.primary.lowContrast}
sx={{
borderRadius: theme.spacing(1.5),
/*
TODO this doesn't seem to be used
"&:hover > .MuiBox-root": {
filter: "brightness(0.8)",
}, */
}}
>
<Box
position="absolute"
bottom={0}
width="100%"
height={`${animate ? check.responseTime : 0}%`}
backgroundColor={
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>
</Tooltip>
)
)}
</Stack>
);
};
export default BarChart;
@@ -1,106 +0,0 @@
// Components
import { Typography, Stack } from "@mui/material";
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import IconBox from "../../IconBox/index.jsx";
/**
* `EmptyView` is a functional React component that displays an empty state view with an optional icon, header, and message.
*
* @component
* @param {Object} props - The properties that define the `EmptyView` component.
* @param {React.ReactNode} [props.icon] - An optional icon to display at the top of the empty view.
* @param {string} [props.header] - An optional header text displayed next to the icon.
* @param {string} [props.message="No Data"] - The message to be displayed in the empty view.
* @param {'h1' | 'h2' | 'h3'} [props.headingLevel="h2"] - The heading level for the message text.
* @param {string} [props.justifyContent="flex-start"] - The CSS `justify-content` value to align elements vertically.
* @param {string} [props.height="100%"] - The height of the empty view container.
*
* @example
* // Example usage of EmptyView component:
* <EmptyView
* icon={<SomeIcon />}
* header="Average Response Time"
* message="No Response Time Available"
* headingLevel="h2"
* justifyContent="center"
* height="50%"
* />
*
* @returns {React.Element} The `EmptyView` component with customizable icon, header, and message.
*/
const EmptyView = ({
icon,
header,
message = "No Data",
headingLevel = "h2",
justifyContent = "flex-start",
height = "100%",
}) => {
const theme = useTheme();
return (
<Stack
flex={1}
direction="row"
sx={{
backgroundColor: theme.palette.primary.main,
border: 1,
borderStyle: "solid",
borderColor: theme.palette.primary.lowContrast,
borderRadius: 2,
borderTopRightRadius: 4,
borderBottomRightRadius: 4,
}}
>
<Stack
flex={1}
alignItems="center"
sx={{
padding: theme.spacing(8),
justifyContent,
gap: theme.spacing(8),
height,
"& h2": {
color: theme.palette.primary.contrastTextSecondary,
fontSize: 15,
fontWeight: 500,
},
"& tspan, & text": {
fill: theme.palette.primary.contrastTextTertiary,
},
}}
>
<Stack
alignSelf="flex-start"
direction="row"
alignItems="center"
gap={theme.spacing(6)}
>
{icon && <IconBox>{icon}</IconBox>}
{header && <Typography component="h2">{header}</Typography>}
</Stack>
<Stack
flex={1}
justifyContent="center"
alignItems="center"
>
<Typography component={headingLevel}>{message}</Typography>
</Stack>
</Stack>
</Stack>
);
};
EmptyView.propTypes = {
message: PropTypes.string,
icon: PropTypes.node,
header: PropTypes.string,
headingLevel: PropTypes.oneOf(["h1", "h2", "h3"]),
justifyContent: PropTypes.string,
height: PropTypes.string,
};
export default EmptyView;
@@ -1,104 +0,0 @@
import { Stack, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import IconBox from "../../IconBox/index.jsx";
import EmptyView from "./EmptyView.jsx";
import PropTypes from "prop-types";
const ChartBox = ({
children,
icon,
header,
height = "300px",
justifyContent = "space-between",
Legend,
borderRadiusRight = 4,
sx,
noDataMessage,
isEmpty = false,
}) => {
const theme = useTheme();
if (isEmpty) {
return (
<EmptyView
icon={icon}
header={header}
message={noDataMessage}
/>
);
}
return (
<Stack
flex={1}
direction="row"
sx={{
backgroundColor: theme.palette.primary.main,
border: 1,
borderStyle: "solid",
borderColor: theme.palette.primary.lowContrast,
borderRadius: 2,
borderTopRightRadius: borderRadiusRight,
borderBottomRightRadius: borderRadiusRight,
}}
>
<Stack
flex={1}
sx={{
padding: theme.spacing(8),
justifyContent,
gap: theme.spacing(8),
height,
minWidth: 250,
"& h2": {
color: theme.palette.primary.contrastTextSecondary,
fontSize: 15,
fontWeight: 500,
},
"& .MuiBox-root:not(.area-tooltip) p": {
color: theme.palette.primary.contrastTextTertiary,
fontSize: 13,
},
"& .MuiBox-root > span": {
color: theme.palette.primary.contrastText,
fontSize: 20,
"& span": {
opacity: 0.8,
marginLeft: 2,
fontSize: 15,
},
},
"& tspan, & text": {
fill: theme.palette.primary.contrastTextTertiary,
},
"& path": {
transition: "fill 300ms ease, stroke-width 400ms ease",
},
}}
>
<Stack
alignSelf="flex-start"
direction="row"
alignItems="center"
gap={theme.spacing(6)}
>
{icon && <IconBox>{icon}</IconBox>}
{header && <Typography component="h2">{header}</Typography>}
</Stack>
{children}
</Stack>
{Legend && Legend}
</Stack>
);
};
export default ChartBox;
ChartBox.propTypes = {
children: PropTypes.node,
icon: PropTypes.node,
header: PropTypes.string,
height: PropTypes.string,
noDataMessage: PropTypes.string,
isEmpty: PropTypes.bool,
};
Loaded 100 of 488 files, more files were not shown because too many files have changed in this diff. Show more