Compare commits

..
Author SHA1 Message Date
mannilakash c27393942e fix: minor refactoring infrastructureMetrics 2026-03-17 06:02:23 -04:00
mannilakash d1077a1314 fix: changing spacing to constants 2026-03-13 12:07:14 -04:00
mannilakash 22bcb74653 fix: prettier fix 2026-03-11 16:53:51 -04:00
mannilakash 78f5ce180b fix: migration query changes 2026-03-11 16:50:33 -04:00
mannilakash a9e9097a1f fix: refactoring changes and adding type migration to statusPageType 2026-03-11 14:03:25 -04:00
mannilakash be1ca4ac28 feat: adding infrastructure display support to status page 2026-03-11 13:57:33 -04:00
Alexander Holliday 7a0e0bccf4 Merge pull request #3405 from bluewave-labs/fix/diagnostic-controller-types
fix: properly type diagnosticController
2026-03-11 10:51:42 -07:00
Alexander Holliday 01401d56b8 Merge pull request #3404 from bluewave-labs/fix/controller-utils-types
fix: controller utils types
2026-03-11 10:51:11 -07:00
Alex Holliday f69b7f6a35 properly type diagnosticController 2026-03-11 10:50:10 -07:00
Alex Holliday cb5316e6f5 fix ssl-checker type 2026-03-11 10:48:12 -07:00
Alexander Holliday 25baff3013 Merge pull request #3400 from bluewave-labs/fix/dns-cache-ttl
fix: set DNS cache TTL to prevent stale lookups
2026-03-11 10:36:08 -07:00
Gorkem Cetin 85d2d34a90 Fix links to Checkmate documentation and user guide
Updated links to documentation and user guides in README.
2026-03-11 02:35:54 -04:00
Gorkem Cetin f6d9b7566b Remove project references from README
Removed references to other BlueWave projects from the README.
2026-03-11 01:47:48 -04:00
gorkem-bwl 84cac870d5 fix: set DNS cache TTL to prevent stale lookups
CacheableLookup defaults to maxTtl=Infinity, meaning successful DNS
lookups are cached forever until process restart. This causes monitors
to fail permanently if DNS changes or if a resolver outage causes
ENOTFOUND errors to persist after recovery.

Set maxTtl=300s (5 min) for successful lookups and errorTtl=30s for
failed lookups so the cache recovers automatically.
2026-03-10 19:04:04 -04:00
Alex Holliday 2884ab524d type fetchMonitorCertificate 2026-03-10 11:35:05 -07:00
Alexander Holliday eac470cb68 Merge pull request #3399 from bluewave-labs/fix/auth-controller
fix: type authController correctly, update services accordingly
2026-03-10 11:31:57 -07:00
Alex Holliday a15b9837c6 fix getUserById 2026-03-10 11:29:40 -07:00
Alex Holliday 562d723fcf remove redundant checks, use validated body 2026-03-10 11:25:55 -07:00
Alex Holliday 2fdd5a72f0 type authController correctly, update services accordingly 2026-03-10 11:18:59 -07:00
Alexander Holliday 3e26387b9d Merge pull request #3398 from bluewave-labs/feat/retention
feat: retention
2026-03-10 10:45:24 -07:00
Alex Holliday dc7d442ace remove unnecessary check 2026-03-10 10:44:02 -07:00
Alex Holliday 839f1beb41 remove __v from client type 2026-03-10 10:37:44 -07:00
Alex Holliday bbdadb0bf5 remove expiry from repository and service 2026-03-10 10:34:09 -07:00
Alex Holliday cadf68740a remove old expiry field, remove vestigal checks 2026-03-10 10:33:18 -07:00
Alex Holliday 6ad0a6a29d validation fix, batch deleteOlderThan 2026-03-10 10:20:23 -07:00
Alex Holliday 77af9d2e93 FE UI, userService type fix 2026-03-09 11:32:01 -07:00
Alex Holliday b1147b8bf9 FE implementation 2026-03-09 11:28:46 -07:00
Alex Holliday 5ab230f77b remove any types 2026-03-09 11:15:50 -07:00
Alex Holliday d9cb81e71d backend implementation 2026-03-09 11:14:40 -07:00
Alexander Holliday 0058772f61 Merge commit from fork
Fixed the Mass Assignment Privilege Escalation in Checkmate
2026-03-09 10:27:39 -07:00
Alexander Holliday bbc2e4073b Merge pull request #3375 from tb102122/feature/teams-notification-channel
add teams
2026-03-09 09:39:56 -07:00
Alexander Holliday 7967b363c2 Merge pull request #3395 from bluewave-labs/fix/demo-monitors
fix: demo monitor import
2026-03-09 09:10:27 -07:00
Aman Rawat 27dffe9fa6 Fixed 2026-03-09 13:12:01 +05:30
tobias.bruckert@mcon-group.com 7213bace08 Merge branch 'feature/teams-notification-channel' of https://github.com/tb102122/Checkmate into feature/teams-notification-channel 2026-03-09 08:43:30 +11:00
tobias.bruckert@mcon-group.com 2cbc667f30 add type checks and remove unneeded vars 2026-03-09 08:43:14 +11:00
Tobias Bruckert 978dc4cda6 Merge branch 'develop' into feature/teams-notification-channel 2026-03-09 08:33:36 +11:00
Alex Holliday a960e3aba2 fix demo monitor import 2026-03-06 14:27:57 -08:00
Alexander Holliday ff1d473b81 Merge pull request #3392 from bluewave-labs/feat/version-bump
bump bersion
2026-03-06 12:48:48 -08:00
Alex Holliday bd384bde4a bump version 2026-03-06 12:47:00 -08:00
Alexander Holliday e7f052dfa0 Merge pull request #3386 from bluewave-labs/feat/remove-team-member
Add ability to remove team members
2026-03-06 12:37:55 -08:00
Alex Holliday 9903c72ff5 merge 2026-03-06 12:36:26 -08:00
Alex Holliday d7d237d37b move button strings to correct location, flex-end 2026-03-06 12:35:38 -08:00
Alexander Holliday 5d03f6f0f0 Merge pull request #3391 from ajhollid/fix/bump-zod
explicitly bump zod
2026-03-06 12:27:34 -08:00
Alex Holliday 38e2a64255 explicitly bump zod 2026-03-06 12:25:32 -08:00
Alexander Holliday 1f56338697 Merge pull request #3390 from bluewave-labs/fix/port-env-var
Fix: restore PORT env var
2026-03-06 12:20:02 -08:00
gorkem-bwl 172467c0a6 fix: address PR review feedback
- Use useIsAdmin and useIsSuperAdmin hooks instead of manual role checks
- Close delete dialog on failure, not just on success
2026-03-06 15:19:01 -05:00
Alexander Holliday ff2b6dff2b Merge pull request #3387 from bluewave-labs/feat/uptime-websockets
Feat: Uptime - Websockets
2026-03-06 12:12:11 -08:00
Br0wnHammer 9f600f87af Fix: Dev Dependencies in Server 2026-03-07 01:40:07 +05:30
Br0wnHammer 7eeff7a396 Fix: Dev Dependencies 2026-03-07 01:37:37 +05:30
Alex Holliday afd6a12e7d restore PORT env var 2026-03-06 12:07:18 -08:00
Br0wnHammer 125d70c4b1 Format Checks 2026-03-07 01:13:00 +05:30
Br0wnHammer 5ebe46a81c Feat: Websocket Implementation 2026-03-07 01:11:20 +05:30
Br0wnHammer 19174ab3e5 Feat: update filter controls and default types 2026-03-07 01:02:30 +05:30
gorkem-bwl 7c953978ed refactor: simplify EditUser imports and canDeleteUser logic
- Merge duplicate react-router-dom imports into one
- Replace useIsAdmin + useIsSuperAdmin + useSelector with single useSelector
- Remove redundant !includes("superadmin") check from canDeleteUser
2026-03-06 14:31:59 -05:00
Br0wnHammer db5441d0e2 Feat: Frontend - update form hook and create/edit page 2026-03-07 01:01:49 +05:30
Br0wnHammer 1d529e3be4 Feat: Frontend - Add websocket to client types and validations 2026-03-07 01:00:38 +05:30
Br0wnHammer 23247d4319 Feat: Update server validation 2026-03-07 00:59:39 +05:30
Br0wnHammer 216d9b2996 Feat: Backend - Config 2026-03-07 00:58:55 +05:30
Br0wnHammer 62f1e62a26 Feat: Backend service - Implement WebSocket check in NetworkService 2026-03-07 00:57:52 +05:30
Br0wnHammer 55bdae77f9 Feat: Backend types - Register websocket type and payload 2026-03-07 00:56:58 +05:30
gorkem-bwl 82998a7795 feat: add ability to remove team members
## Changes
- Add DELETE /api/v1/auth/users/:userId endpoint (admin + superadmin)
- Add deleteUserById service method with role hierarchy enforcement
- Superadmins can remove admins and regular users
- Admins can only remove regular users
- No one can remove superadmins, demo users, or themselves via this endpoint
- Add "Remove user" button with confirmation dialog on edit user page
- Allow admins to view user details and access the edit user page
- Add i18n strings for remove user button and confirmation dialog

## Access control
- superadmin -> can remove admin, user
- admin -> can remove user only
- admin cannot remove superadmin or other admins (enforced on both backend and frontend)
2026-03-06 14:18:27 -05:00
Alexander Holliday 9fe8b7572f Merge pull request #3374 from bluewave-labs/fix/dark-mode-background-color
Fix dark mode background to use neutral gray
2026-03-06 11:14:21 -08:00
Alex Holliday a424092fbd remove unused imports 2026-03-06 11:12:52 -08:00
Alexander Holliday ad427dc765 Merge pull request #3385 from bluewave-labs/feat/globalping-ping-type
feat: globalping ping type
2026-03-06 11:10:45 -08:00
Alex Holliday 116eb80fc9 format 2026-03-06 11:07:48 -08:00
Alex Holliday c094a5fd73 format 2026-03-06 11:02:20 -08:00
Alex Holliday 206e28d1b9 user helper 2026-03-06 11:01:38 -08:00
Alex Holliday 2f1a164c26 remove rtt 2026-03-06 10:46:00 -08:00
Alex Holliday 124a8c0b07 ping type globalping checks 2026-03-06 10:37:29 -08:00
Alexander Holliday 188445c57a Merge pull request #3384 from bluewave-labs/fix/prettier
fix prettier hook
2026-03-06 09:46:08 -08:00
Alex Holliday 35f018623f fix prettier hook 2026-03-06 09:45:33 -08:00
Alexander Holliday d64526fed2 Merge pull request #3383 from bluewave-labs/fix/providers
fix: providers
2026-03-06 09:39:14 -08:00
Alex Holliday 9a8a175f7c format 2026-03-06 09:37:45 -08:00
Alex Holliday 6ecb27d522 remove buildStatusResponse 2026-03-06 09:35:52 -08:00
Alex Holliday 24b9b64979 manually build response 2026-03-06 09:35:07 -08:00
Alexander Holliday a4e4e1313e Merge pull request #3382 from bluewave-labs/fix/ts-errors
fix: resolve ts errors
2026-03-06 09:19:02 -08:00
Alex Holliday 990dc7875a format 2026-03-06 09:16:22 -08:00
Alex Holliday 69e98a51ec resolve ts errors 2026-03-06 09:14:12 -08:00
tobias.bruckert@mcon-group.com 08fd09f59b fix build errors 2026-03-06 19:48:19 +11:00
tobias.bruckert@mcon-group.com 6c421717f8 run formatting 2026-03-06 17:58:04 +11:00
tobias.bruckert@mcon-group.com 083db95151 Merge branch 'feature/teams-notification-channel' of https://github.com/tb102122/Checkmate into feature/teams-notification-channel 2026-03-06 17:49:08 +11:00
tobias.bruckert@mcon-group.com b11ba68ff8 adjust based on review 2026-03-06 17:47:35 +11:00
Tobias Bruckert 12a4112055 Merge branch 'develop' into feature/teams-notification-channel 2026-03-06 13:08:41 +11:00
Alexander Holliday 3063b9cf22 Merge pull request #3379 from bluewave-labs/fix/network-providers
fix: network providers
2026-03-05 14:52:33 -08:00
Alex Holliday bd32854c7a cleanup services 2026-03-05 14:46:28 -08:00
Alex Holliday 0718e63e2f cleanup network service 2026-03-05 14:11:11 -08:00
Alexander Holliday cdb70f9171 Merge pull request #3378 from bluewave-labs/fix/email
Fix/email
2026-03-05 14:01:03 -08:00
Alex Holliday 69e07975e3 bump dependencies 2026-03-05 13:57:45 -08:00
Alex Holliday c5c9a1a88b fix missing await 2026-03-05 13:57:39 -08:00
Alexander Holliday 31efa791bd Merge pull request #3377 from bluewave-labs/fix/providers
fix/providers
2026-03-05 13:23:27 -08:00
Alex Holliday f8c0b57d21 remove unused imports 2026-03-05 19:37:32 +00:00
Alex Holliday 605c969d2f merge commit 2026-03-05 19:37:03 +00:00
Alex Holliday ad7d4e54c3 cleanup 2026-03-05 19:34:57 +00:00
Alex Holliday c7689cc64a grpc 2026-03-05 19:18:29 +00:00
Alex Holliday 27c44a0fbd config 2026-03-05 18:48:19 +00:00
Alex Holliday 190d9dd2b1 gameProvider 2026-03-05 18:45:27 +00:00
Alex Holliday 49672db563 port provider 2026-03-05 18:35:19 +00:00
Alex Holliday 80603c9a42 port provider 2026-03-05 18:30:06 +00:00
Alex Holliday f3b156f2d0 docker provider 2026-03-05 18:19:03 +00:00
Alex Holliday ec82efebb4 pagespeed provider, hardware provider 2026-03-05 17:54:16 +00:00
tobias.bruckert@mcon-group.com 33201096b7 add teams 2026-03-05 19:16:08 +11:00
gorkem-bwl a1068f1d7e refactor: move dark background colors to colors object
Addresses review feedback — define gray900 and gray850 in the colors
object and reference them in the dark palette.
2026-03-04 19:56:54 -05:00
Alex Holliday 8dd846d6cd harden http provider 2026-03-04 23:36:36 +00:00
Alex Holliday 82175f166f pingProvider, httpProvider 2026-03-04 23:24:48 +00:00
Alex Holliday 3c99809e8e rix reveresed checks 2026-03-04 23:24:05 +00:00
Alexander Holliday 26db2b3a41 Merge pull request #3371 from bluewave-labs/fix/infrastructure-threshold-migration
Fix infrastructure threshold defaults via migration (#3327)
2026-03-04 14:28:49 -08:00
Alexander Holliday 232a71c502 Merge pull request #3369 from bluewave-labs/fix/swagger-dynamic-server-url
fix: dynamically set Swagger UI server URL from request
2026-03-04 14:28:06 -08:00
gorkem-bwl 8df263fd93 fix: also migrate threshold value of 0 to 100, fix formatting
Addresses review feedback — thresholds set to 0 also caused issues
and should be migrated to 100 (disabled) alongside the value of 5.
2026-03-04 16:44:20 -05:00
gorkem-bwl d2f3335b1a fix: add type annotations to Swagger middleware parameters
Fixes TypeScript build error TS7006 for implicit 'any' types on req, res, next.
2026-03-04 16:32:01 -05:00
gorkem-bwl 75eca743b4 fix: use neutral gray background for dark mode
## Changes
- Replace MUI default blue-tinted dark background with neutral grays
  (#151518 for default, #1c1c21 for paper/cards)
- Remove decorative blue/purple SVG blob overlay from dark mode background

## Benefits
- Dark mode now has a clean, neutral gray appearance instead of a blue tint
2026-03-04 16:30:19 -05:00
Alexander Holliday 7bfd68e374 Merge pull request #3372 from bluewave-labs/fix/ipv6-support
Fix IPv6 address handling in ping monitor (#3053)
2026-03-04 13:00:15 -08:00
gorkem-bwl b2e2b11ef2 fix: support IPv6 addresses in ping monitor
## Changes
- Fixed ping host sanitization regex that stripped everything after
  the first colon, which destroyed IPv6 addresses like 2001:db8::1
- New regex properly handles bracketed IPv6 ([::1]:port), bare IPv6,
  and IPv4/hostname:port formats separately

## Closes
Fixes #3053
2026-03-04 15:41:01 -05:00
gorkem-bwl 93b9abd508 fix: add migration to reset infrastructure thresholds from 5 to 100
## Changes
- Added migration 0004_fixInfrastructureThresholds that finds hardware
  monitors with threshold values of 5 (the old incorrect default) and
  updates them to 100 (effectively disabled)
- Runs automatically on server startup like other migrations

## Closes
Fixes #3327
2026-03-04 15:38:34 -05:00
gorkem-bwl 888b399a4f fix: dynamically set Swagger UI server URL from request
## Changes
- Swagger UI now prepends the current server URL (protocol + host) to
  the servers list, so it works correctly in remote/Docker environments
  instead of always pointing to localhost.

## Closes
Fixes #3339
2026-03-04 15:29:44 -05:00
Alexander Holliday 189293b04d Merge pull request #3366 from bluewave-labs/fix/eslint-warnings
fix: eslint warnings
2026-03-04 10:29:35 -08:00
Alex Holliday 13e9185011 bump version 2026-03-04 18:24:06 +00:00
Alex Holliday a7cf78cecc emailService 2026-03-04 18:16:31 +00:00
Alex Holliday 0b30e23783 globalPing 2026-03-04 17:50:27 +00:00
Alexander Holliday 1155619835 Merge pull request #3365 from bluewave-labs/fix/eslint
fix: eslint
2026-03-04 09:47:51 -08:00
Alex Holliday e0608ad3ed notificationMessageBuilder 2026-03-04 17:43:25 +00:00
Alex Holliday d665e95f02 discord 2026-03-04 17:39:31 +00:00
Alex Holliday afc27c804d email 2026-03-04 17:32:50 +00:00
Alex Holliday 1deb22bf63 pagerduty 2026-03-04 17:17:52 +00:00
Alex Holliday af8b128ac2 notificaiton providers 2026-03-03 23:27:48 +00:00
Alex Holliday 00150ba6f4 checkService, bufferService 2026-03-03 23:19:54 +00:00
Alex Holliday 7f04dd5d9a checksService 2026-03-03 23:09:10 +00:00
Alex Holliday ab5389190d stautsService and settingsService 2026-03-03 23:01:35 +00:00
Alexander Holliday cba9f43032 Merge pull request #3362 from bluewave-labs/fix/types
Fix/types
2026-03-03 13:41:05 -08:00
Alex Holliday 1cd6e723cc netowrk-service 2026-03-03 21:37:08 +00:00
Alex Holliday f79a87f98a resolve remaining TS errors 2026-03-03 19:34:28 +00:00
Alex Holliday a0ea50d745 correct return types 2026-03-03 18:50:10 +00:00
Alex Holliday bfa11216f0 correct payload type for monitor response 2026-03-03 18:29:17 +00:00
Alex Holliday 07a2f36dd7 app error 2026-03-03 18:27:42 +00:00
Alex Holliday 8e44718787 logger, imageProcessing 2026-03-03 18:26:42 +00:00
Alex Holliday 83572aa12a eslint update 2026-03-03 17:43:57 +00:00
Alexander Holliday 332a21cd99 Merge pull request #3361 from bluewave-labs/fix/logs
replace log statement
2026-03-03 09:20:22 -08:00
Alex Holliday 128d1d12b0 replace log statement 2026-03-02 23:04:20 +00:00
Alexander Holliday f73b07cd3c Merge pull request #3353 from bluewave-labs/feat/swr-global
feat: add global swr config
2026-03-02 14:50:44 -08:00
Alex Holliday a7d14e0e43 add global swr config 2026-03-02 22:37:58 +00:00
Alexander Holliday 6622a990be Merge pull request #3352 from bluewave-labs/fix/env-var-validation
Fix/env var validation
2026-03-02 14:01:35 -08:00
Alex Holliday 3854931682 remove comment 2026-03-02 22:00:03 +00:00
Alex Holliday 09534757a2 validation 2026-03-02 21:52:22 +00:00
Alexander Holliday 7b2ce0e350 Merge pull request #3350 from bluewave-labs/fix/server-validation
fix: server validation
2026-03-02 13:40:56 -08:00
Alexander Holliday 19887332c6 Merge pull request #3349 from bluewave-labs/fix/remove-monitor-swagger-api
fix: removing GET /monitors from swagger docs
2026-03-02 13:35:57 -08:00
Alex Holliday a1858e8302 remove passthrough, remove redudnant refine 2026-03-02 21:35:08 +00:00
Alex Holliday 9fd006cfff remove joi 2026-03-02 19:42:47 +00:00
Alex Holliday 75a7e144cd remove comments 2026-03-02 19:26:20 +00:00
Alex Holliday 16cf23baf1 status page validation 2026-03-02 19:25:34 +00:00
Alex Holliday 8236b02034 remove comments 2026-03-02 19:06:32 +00:00
Alex Holliday 64242322a2 add try/catch block 2026-03-02 18:46:21 +00:00
Alex Holliday c1e03a45b0 settings 2026-03-02 18:03:10 +00:00
Alex Holliday f1466df56c update monitor validation 2026-03-02 17:54:52 +00:00
mannilakash 3654c58469 fix: removing GET /monitors from swagger docs 2026-03-02 12:48:04 -05:00
Alex Holliday b997c43bd5 check, monitor, maintenance 2026-03-02 17:46:33 +00:00
Alex Holliday a5d3c95145 validation refactoring 2026-03-02 17:29:48 +00:00
Gorkem Cetin 003106348a Change demo username in README
Updated demo username in README.
2026-03-02 02:22:46 -05:00
Alex Holliday c3a41272a3 validation refactoring 2026-03-01 17:35:00 +00:00
Alexander Holliday 0bff26fe47 Merge pull request #3346 from bluewave-labs/fix/hardcoded-dimensions
Fix/hardcoded dimensions
2026-03-01 07:19:44 -08:00
Alex Holliday 9169410c1a fallback and histogram 2026-02-28 22:03:30 +00:00
Alex Holliday 9174e66134 sidebar and starprompt 2026-02-28 21:48:42 +00:00
Alex Holliday 04c3c57a59 card-details 2026-02-28 21:27:33 +00:00
Alex Holliday f69b6bbf31 create status page 2026-02-28 21:04:46 +00:00
Alex Holliday 05aa1e7112 authfooter 2026-02-28 20:57:44 +00:00
Alexander Holliday 8aead0a00c Merge pull request #3332 from bluewave-labs/fix/pagespeed-sort
Pagespeed filter sorting fix
2026-02-28 12:51:49 -08:00
Alex Holliday db8ed2994a maintenance create 2026-02-28 20:42:10 +00:00
Alex Holliday 8571ccea78 settings page 2026-02-28 20:25:49 +00:00
Alex Holliday 4d273a6dce table and createMonitor 2026-02-28 16:30:04 +00:00
Alex Holliday 4aabc16096 theme constants 2026-02-28 16:16:14 +00:00
Alexander Holliday 94f9c3d118 Merge pull request #3326 from bluewave-labs/fix/uptime/row-ui
Fix: Uptime Table Body Placement
2026-02-28 08:04:50 -08:00
Alexander Holliday a719364e40 Merge pull request #3344 from bluewave-labs/fix/logging
fix/logging
2026-02-27 18:07:53 -08:00
Alex Holliday 308a439790 apply logger 2026-02-28 01:55:41 +00:00
Alex Holliday 42a001b24e logger 2026-02-28 01:04:35 +00:00
Alexander Holliday f48fc24b9e Merge pull request #3342 from bluewave-labs/fix/remove-on-row-click
remove on row click
2026-02-26 14:22:41 -08:00
Alex Holliday e3057ad039 remove unused import 2026-02-26 22:21:19 +00:00
Alex Holliday 32f41a3cc4 remove unused import 2026-02-26 22:11:32 +00:00
Alex Holliday 177d6f80d0 remove on row click 2026-02-26 22:08:14 +00:00
Alexander Holliday ecef55f1b3 Merge pull request #3341 from bluewave-labs/fix/global-thresholds
Fix/global thresholds
2026-02-26 11:36:21 -08:00
Alex Holliday dede689990 global threshold slider fix 2026-02-26 19:31:12 +00:00
Alex Holliday 85edea713a replace textfields with sliders 2026-02-26 19:07:43 +00:00
Alexander Holliday 7e3da7add7 Merge pull request #3304 from bluewave-labs/feat/grpc-monitoring
Feat: gRPC Monitoring
2026-02-26 09:34:04 -08:00
Alex Holliday dfeba1e4fb resolve conflicts 2026-02-26 17:32:11 +00:00
Alexander Holliday 0442c8dd0f Merge pull request #3336 from bluewave-labs/feat/globalping-map
Feat/globalping map
2026-02-25 14:00:03 -08:00
Alex Holliday 7960f1e82c popup close button 2026-02-25 21:58:48 +00:00
Alexander Holliday f7f1602423 Merge pull request #3335 from bluewave-labs/feat/globalping-map
feat: globalping map
2026-02-25 13:57:12 -08:00
Alex Holliday 78d1451621 hide attribution 2026-02-25 21:49:01 +00:00
Alex Holliday a9f005b2c4 move css styles 2026-02-25 21:44:09 +00:00
Alex Holliday b68214fc47 dark/light mode 2026-02-25 21:20:45 +00:00
Alex Holliday 64c0c93c3e chart 2026-02-25 21:14:17 +00:00
Alexander Holliday 40a1f0c6e0 Merge pull request #3334 from bluewave-labs/feat/global-ping-implementation
remove comment
2026-02-25 12:18:01 -08:00
Alex Holliday 1bb2dd323a remove comment 2026-02-25 20:17:34 +00:00
Alexander Holliday 17238c982e Merge pull request #3333 from bluewave-labs/feat/global-ping-implementation
feat: global ping implementation
2026-02-25 12:17:12 -08:00
Alex Holliday cc1d63f91a remove comment 2026-02-25 20:14:31 +00:00
Alex Holliday ba6e931c59 date range extraction, minor fixes 2026-02-25 20:13:07 +00:00
Alex Holliday 885acf6fe6 all continents in table 2026-02-25 19:34:24 +00:00
Alex Holliday 477c30286d show all checks 2026-02-25 18:43:12 +00:00
Alex Holliday 88e28b9819 cleanup on delete, cleanup orphans 2026-02-24 23:28:27 +00:00
Alex Holliday e7ba3cb5f9 checks route, checks table 2026-02-24 23:20:36 +00:00
Alex Holliday 651fbd7cbb add graph 2026-02-24 22:45:59 +00:00
Alex Holliday 64fdd04cc7 conitnent based query 2026-02-24 22:30:28 +00:00
Alex Holliday c3e4b5b95f ui initial commit 2026-02-24 22:16:02 +00:00
Alex Holliday 10313102ee use correct validation 2026-02-24 21:45:55 +00:00
Alexander Holliday 868cbae66f Merge pull request #3331 from bluewave-labs/fix/uptime-filter-status
Fix: Uptime Status Filter
2026-02-24 13:33:15 -08:00
Alex Holliday 2989968fa1 button string 2026-02-24 13:31:36 -08:00
Alex Holliday 89a7fdbe34 cleanup imports 2026-02-24 19:36:45 +00:00
Alex Holliday 432d1d8ccf remove circular dep 2026-02-24 19:19:32 +00:00
Alex Holliday 5a74411ea2 geo bufer 2026-02-24 18:44:47 +00:00
Alex Holliday 705ecf41c2 rename interface 2026-02-24 18:19:51 +00:00
Alex Holliday 1d4e9d6806 geochecks iniital commit 2026-02-24 18:10:20 +00:00
Alex Holliday 8e37ed7ba2 geo jobs scheduling 2026-02-24 17:55:06 +00:00
Alex Holliday b870f25d1a geo job template 2026-02-24 17:39:09 +00:00
Alex Holliday 7d3abb3f98 map fields 2026-02-24 17:18:22 +00:00
mannilakash 9baa6692dd fix: pagespeed filter sorting fix 2026-02-24 07:45:12 -05:00
Br0wnHammer dd7dfba1cf Fix: Uptime Status Filter 2026-02-24 15:05:27 +05:30
Alex Holliday 858aecce62 fe config box 2026-02-23 18:08:01 +00:00
Alex Holliday 42fdc47d86 initial commit 2026-02-23 17:48:53 +00:00
Br0wnHammer af8f913e62 Fix: Uptime Table Body Placement 2026-02-23 13:38:26 +05:30
Br0wnHammer ea086d6956 Fix: PR comments 2026-02-23 10:25:01 +05:30
Br0wnHammer 22b33d789c Fix: Add grpc in checkData 2026-02-23 10:25:00 +05:30
Br0wnHammer 42e7bc1dbf Feat: Add gRPC to filters 2026-02-23 10:25:00 +05:30
Br0wnHammer 13aa600852 Feat: Add gRPC to CreateMonitor UI 2026-02-23 10:25:00 +05:30
Br0wnHammer c06f81ef76 Feat: Add gRPC to client validation 2026-02-23 10:25:00 +05:30
Br0wnHammer c95db26437 Feat: Update backend Joi validation 2026-02-23 10:25:00 +05:30
Br0wnHammer 94a85f8bbb Feat: Impement requestGrpc in NetworkService 2026-02-23 10:25:00 +05:30
Br0wnHammer 05f8e6a07e Feat: Add GrpcStatus to network and Mongoose schema 2026-02-23 10:25:00 +05:30
Br0wnHammer dac78c7647 Feat: Add gRPC to monitortypes 2026-02-23 10:25:00 +05:30
Alexander Holliday 2d023de568 Merge pull request #3325 from bluewave-labs/feat/pagespeed-date-range
feat: pagespeed date range
2026-02-21 13:09:58 -08:00
Alex Holliday f2c88cfd23 fe 2026-02-21 21:05:51 +00:00
Alex Holliday 910c675a49 aggregate pagespeed 2026-02-21 20:52:47 +00:00
Alex Holliday 882e91f6a7 hook up header 2026-02-21 20:31:56 +00:00
Alexander Holliday 06be65ec52 Merge pull request #3324 from bluewave-labs/fix/import
fix: import
2026-02-21 08:20:57 -08:00
Alex Holliday ea54736a39 remove string 2026-02-21 16:19:43 +00:00
Alex Holliday da38f2ee76 createBulkMonitors -> CreateMonitors 2026-02-21 16:17:23 +00:00
Alex Holliday 83d56f1ff6 fe->be 2026-02-21 16:03:45 +00:00
Alex Holliday f94afb63c6 backend 2026-02-21 03:43:11 +00:00
Alex Holliday 673f47983a FE implementation 2026-02-21 03:14:11 +00:00
Alex Holliday b708e83cce add TODOs 2026-02-20 15:11:37 -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 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 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
336 changed files with 10440 additions and 10359 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 -1
View File
@@ -5,4 +5,6 @@
mongo
node_modules/
docs/architecture
docs/reviews
docs/reviews
docs/todo
docs/frontend
+8 -15
View File
@@ -42,15 +42,15 @@ Checkmate has been stress-tested with 1000+ active monitors without any particul
## Demo
You can see the latest build of [Checkmate](https://checkmate-demo.bluewavelabs.ca/) in action. The username is uptimedemo@demo.com and the password is Demouser1! (just a note that we update the demo server from time to time, so if it doesn't work for you, please ping us on the Discussions channel).
You can see the latest build of [Checkmate](https://checkmate-demo.bluewavelabs.ca/) in action. The username is demouser@demo.com and the password is Demouser1! (just a note that we update the demo server from time to time, so if it doesn't work for you, please ping us on the Discussions channel).
## User's guide
Usage instructions can be found [here](https://docs.checkmate.so/checkmate-2.1). It's still WIP and some of the information there might be outdated as we continuously add features weekly. Rest assured, we are doing our best! :)
Usage instructions can be found [here](https://checkmate.so/docs).
## Installation
See installation instructions in [Checkmate documentation portal](https://docs.checkmate.so/checkmate-2.1/users-guide/quickstart).
See installation instructions in [Checkmate documentation portal](https://checkmate.so/docs).
Alternatively, you can also use [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (South Africa) or [Pikapods](https://www.pikapods.com/) to quickly spin off a Checkmate instance. If you would like to monitor your server infrastructure, you'll need [Capture agent](https://github.com/bluewave-labs/capture). Capture repository also contains the installation instructions.
@@ -157,12 +157,11 @@ Here's how you can contribute:
0. Star this repo :)
1. Check [Contributor's guideline](https://github.com/bluewave-labs/Checkmate/blob/develop/CONTRIBUTING.md). First timers are encouraged to check `good-first-issue` tag.
2. Check [project structure](https://docs.checkmate.so/checkmate-2.1/developers-guide/general-project-structure) and [high level overview](https://bluewavelabs.gitbook.io/checkmate/developers-guide/high-level-overview).
3. Read a detailed structure of [Checkmate](https://deepwiki.com/bluewave-labs/Checkmate) if you would like to deep dive into the architecture.
4. Open an issue if you believe you've encountered a bug.
5. Check for good-first-issue's if you are a newcomer.
6. Make a pull request to add new features/make quality-of-life improvements/fix bugs.
7. Check out this interactive walkthrough of the `Checkmate` codebase on CodeCanvas [here](https://www.code-canvas.com/?session=unauthenticatedGithub&repo=Checkmate&owner=bluewave-labs&branch=develop&OnboardingTutorial=true). To refine existing dataflow simulation or create new ones, follow the quick tutorial [here](https://docs.code-canvas.com/updating-diagram).
2. Read a detailed structure of [Checkmate](https://deepwiki.com/bluewave-labs/Checkmate) if you would like to deep dive into the architecture.
3. Open an issue if you believe you've encountered a bug.
4. Check for good-first-issue's if you are a newcomer.
5. Make a pull request to add new features/make quality-of-life improvements/fix bugs.
6. Check out this interactive walkthrough of the `Checkmate` codebase on CodeCanvas [here](https://www.code-canvas.com/?session=unauthenticatedGithub&repo=Checkmate&owner=bluewave-labs&branch=develop&OnboardingTutorial=true). To refine existing dataflow simulation or create new ones, follow the quick tutorial [here](https://docs.code-canvas.com/updating-diagram).
<a href="https://github.com/bluewave-labs/checkmate/graphs/contributors">
<img src="https://contrib.rocks/image?repo=bluewave-labs/checkmate" />
@@ -175,9 +174,3 @@ Here's how you can contribute:
Thanks to [Gitbook](https://gitbook.io/) for giving us a free tier for their documentation platform, and [Poeditor](https://poeditor.com/) providing us a free account to use their i18n services. If you would like to sponsor Checkmate, please send an email to hello@bluewavelabs.ca
If you would like to sponsor a feature, [see this page](https://checkmate.so/sponsored-features).
Also check other developer and contributor-friendly projects of BlueWave:
- [VerifyWise](https://github.com/bluewave-labs/verifywise), the first open source AI governance platform.
- [DataRoom](https://github.com/bluewave-labs/bluewave-dataroom), an secure file sharing application, aka dataroom.
- [Guidefox](https://github.com/bluewave-labs/guidefox), an application that helps new users learn how to use your product via hints, tours, popups and banners.
+690 -104
View File
File diff suppressed because it is too large. Load diff
+6 -3
View File
@@ -13,7 +13,7 @@
"format-check": "prettier --check ."
},
"lint-staged": {
"**/*": "npm run format"
"**/*": "prettier --write"
},
"dependencies": {
"@emotion/react": "^11.13.3",
@@ -25,6 +25,7 @@
"@mui/x-charts": "7.29.1",
"@mui/x-date-pickers": "7.29.4",
"@reduxjs/toolkit": "2.7.0",
"@types/maplibre-gl": "^1.13.2",
"axios": "^1.7.4",
"dayjs": "1.11.13",
"flag-icons": "7.3.2",
@@ -33,6 +34,7 @@
"i18next": "25.4.2",
"joi": "17.13.3",
"lucide-react": "^0.562.0",
"maplibre-gl": "^5.19.0",
"mui-color-input": "^7.0.0",
"pretty-bytes": "^7.1.0",
"pretty-ms": "^9.3.0",
@@ -41,6 +43,7 @@
"react-hook-form": "^7.63.0",
"react-i18next": "^15.4.0",
"react-icons": "5.5.0",
"react-map-gl": "^8.1.0",
"react-redux": "9.2.0",
"react-router": "^6.23.0",
"react-router-dom": "^6.23.1",
@@ -49,7 +52,7 @@
"redux-persist": "6.0.0",
"swr": "^2.3.6",
"vite-plugin-svgr": "^4.2.0",
"zod": "^4.1.11"
"zod": "^4.3.6"
},
"devDependencies": {
"@types/node": "24.5.2",
@@ -63,6 +66,6 @@
"lint-staged": "^16.2.7",
"prettier": "^3.3.3",
"typescript": "5.9.3",
"vite": "6.3.6"
"vite": "6.4.1"
}
}
+38 -28
View File
@@ -3,43 +3,53 @@ 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 { SWRConfig } from "swr";
import { Routes } from "./Routes";
import AppLayout from "@/Components/v2/layout/AppLayout";
import AppLayout from "@/Components/layout/AppLayout";
import type { RootState } from "@/Types/state";
import { lightTheme, darkTheme } from "@/Utils/Theme/Theme";
const swrConfig = {
dedupingInterval: 5000,
revalidateOnFocus: false,
revalidateOnReconnect: true,
shouldRetryOnError: false,
errorRetryCount: 2,
errorRetryInterval: 1000,
focusThrottleInterval: 5000,
};
function App() {
const mode = useSelector((state: RootState) => state.ui.mode);
const theme = mode === "light" ? lightTheme : darkTheme;
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<GlobalStyles
styles={{
body: {
// @ts-ignore
backgroundColor: theme.palette.background.main,
},
}}
/>
<AppLayout>
<Routes />
</AppLayout>
<ToastContainer
newestOnTop={true}
theme={mode}
style={
{
"--toastify-color-progress-light": "#7C8BA1",
"--toastify-color-progress-dark": "#7C8BA1",
} as CSSProperties
}
/>
</ThemeProvider>
<SWRConfig value={swrConfig}>
<ThemeProvider theme={theme}>
<CssBaseline />
<GlobalStyles
styles={{
body: {
backgroundColor: theme.palette.background.default,
},
}}
/>
<AppLayout>
<Routes />
</AppLayout>
<ToastContainer
newestOnTop={true}
theme={mode}
style={
{
"--toastify-color-progress-light": "#7C8BA1",
"--toastify-color-progress-dark": "#7C8BA1",
} as CSSProperties
}
/>
</ThemeProvider>
</SWRConfig>
);
}
@@ -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";
@@ -32,7 +32,7 @@ export const HeatmapResponseTime = ({
if (!checks || checks.length === 0) return null;
const latestChecks = checks.slice(-25).reverse();
const latestChecks = checks.slice(-25);
let data: Array<CheckSnapshot | PlaceholderCheck>;
if (latestChecks.length !== 25) {
@@ -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";
@@ -1,7 +1,8 @@
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";
import { LAYOUT } from "@/Utils/Theme/constants";
import type { ResponsiveStyleValue } from "@mui/system";
import { useTheme } from "@mui/material/styles";
@@ -26,7 +27,7 @@ export const BaseChart = ({
return (
<BaseBox
sx={{
padding: padding ?? theme.spacing(8),
padding: padding ?? theme.spacing(LAYOUT.MD),
display: "flex",
flex: 1,
width: width,
@@ -34,13 +35,13 @@ export const BaseChart = ({
}}
>
<Stack
gap={theme.spacing(8)}
gap={theme.spacing(LAYOUT.MD)}
flex={1}
>
<Stack
direction="row"
alignItems={"center"}
gap={theme.spacing(4)}
gap={theme.spacing(LAYOUT.XS)}
>
{icon && (
<BaseBox
@@ -3,13 +3,14 @@ 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 { LAYOUT, SPACING } from "@/Utils/Theme/constants";
import {
ErrorFallback,
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";
@@ -91,7 +92,7 @@ export const BasePage = ({
return (
<Stack
spacing={theme.spacing(10)}
spacing={theme.spacing(LAYOUT.LG)}
{...props}
>
<Breadcrumb breadcrumbOverride={breadcrumbOverride} />
@@ -220,7 +221,7 @@ export const BaseAuthPage = ({
return (
<BasePage
breadcrumbOverride={[]}
gap={theme.spacing(8)}
gap={theme.spacing(LAYOUT.MD)}
alignItems={"center"}
justifyContent={"center"}
minHeight="100vh"
@@ -229,7 +230,7 @@ export const BaseAuthPage = ({
>
<HeaderAuthControls
hideLogo
py={theme.spacing(4)}
py={theme.spacing(LAYOUT.XS)}
position={"absolute"}
top={0}
left={0}
@@ -243,14 +244,14 @@ export const BaseAuthPage = ({
<Stack alignItems={"center"}>
<Typography
variant="h1"
mb={theme.spacing(2)}
mb={theme.spacing(SPACING.LG)}
>
{title}
</Typography>
<Typography variant="h2">{subtitle}</Typography>
</Stack>
<Stack
gap={theme.spacing(8)}
gap={theme.spacing(LAYOUT.MD)}
width={{
xs: "80%",
md: "25%",
@@ -2,6 +2,7 @@ import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { Check } from "lucide-react";
import { useTheme } from "@mui/material/styles";
import { LAYOUT } from "@/Utils/Theme/constants";
export const BulletPointCheck = ({
text,
@@ -21,7 +22,7 @@ export const BulletPointCheck = ({
return (
<Stack
direction="row"
gap={theme.spacing(6)}
gap={theme.spacing(LAYOUT.SM)}
alignItems="center"
>
<Check
@@ -1,7 +1,8 @@
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 { SPACING, LAYOUT } from "@/Utils/Theme/constants";
import { useNavigate } from "react-router";
import useMediaQuery from "@mui/material/useMediaQuery";
@@ -27,7 +28,7 @@ export const BaseFallback = ({ children, ...props }: BaseFallbackProps) => {
lg: "50%",
xl: "40%",
}}
padding={{ xs: theme.spacing(8), md: theme.spacing(16) }}
padding={{ xs: theme.spacing(LAYOUT.MD), md: theme.spacing(LAYOUT.XXL) }}
bgcolor={theme.palette.background.paper}
border={1}
borderColor={theme.palette.divider}
@@ -55,7 +56,7 @@ export const ErrorFallback = ({
<BaseFallback>
<Typography
variant="h1"
marginY={theme.spacing(4)}
marginY={theme.spacing(LAYOUT.XS)}
color={theme.palette.text.secondary}
>
{title}
@@ -81,20 +82,15 @@ export const EmptyFallback = ({
return (
<BaseFallback>
<Stack
gap={theme.spacing(10)}
gap={theme.spacing(LAYOUT.MD)}
zIndex={1}
alignItems="center"
>
<Typography
component="h1"
color={theme.palette.primary.contrastText}
>
{title}
</Typography>
<Typography component="h1">{title}</Typography>
<Stack
sx={{
flexWrap: "wrap",
gap: theme.spacing(2),
gap: theme.spacing(SPACING.MD),
maxWidth: "1100px",
width: "100%",
}}
@@ -139,7 +135,7 @@ export const EmptyMonitorFallback = ({
return (
<BaseFallback>
<Stack
gap={theme.spacing(10)}
gap={theme.spacing(LAYOUT.MD)}
zIndex={1}
alignItems="center"
>
@@ -152,7 +148,7 @@ export const EmptyMonitorFallback = ({
<Stack
sx={{
flexWrap: "wrap",
gap: theme.spacing(2),
gap: theme.spacing(SPACING.MD),
maxWidth: "1100px",
width: "100%",
}}
@@ -1,4 +1,4 @@
import { BaseChart } from "@/Components/v2/design-elements";
import { BaseChart } from "@/Components/design-elements";
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
@@ -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,7 +19,6 @@ export const MonitorStatus = ({ monitor }: { monitor: Monitor }) => {
<Typography
fontSize={typographyLevels.xl}
fontWeight={500}
color={theme.palette.primary.contrastText}
overflow={"hidden"}
textOverflow={"ellipsis"}
whiteSpace={"nowrap"}
@@ -3,6 +3,7 @@ import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import { useTheme } from "@mui/material/styles";
import useMediaQuery from "@mui/material/useMediaQuery";
import { LAYOUT } from "@/Utils/Theme/constants";
export const SplitBox = ({
left,
right,
@@ -18,10 +19,10 @@ export const SplitBox = ({
bgcolor={theme.palette.background.paper}
border={1}
borderColor={theme.palette.divider}
borderRadius={theme.spacing(2)}
borderRadius={theme.shape.borderRadius}
>
<Box
padding={theme.spacing(15)}
padding={theme.spacing(LAYOUT.XXL)}
borderRight={isSmall ? 0 : 1}
borderBottom={isSmall ? 1 : 0}
borderColor={theme.palette.divider}
@@ -37,7 +38,7 @@ export const SplitBox = ({
</Box>
<Box
flex={1}
padding={theme.spacing(15)}
padding={theme.spacing(LAYOUT.XXL)}
>
{right}
</Box>
@@ -60,7 +61,7 @@ export const ConfigBox = ({
return (
<SplitBox
left={
<Stack spacing={theme.spacing(4)}>
<Stack spacing={theme.spacing(LAYOUT.XS)}>
<Typography
textTransform={"capitalize"}
component="h2"
@@ -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,7 +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 { BaseBox } from "@/Components/design-elements";
import { useTranslation } from "react-i18next";
import type { SxProps } from "@mui/material";
@@ -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";
@@ -29,6 +29,7 @@ import type { TablePaginationOwnProps } from "@mui/material/TablePagination";
import { useTranslation } from "react-i18next";
import { useState, Fragment, useEffect } from "react";
import { useTheme } from "@mui/material/styles";
import { SPACING, LAYOUT } from "@/Utils/Theme/constants";
import type { SxProps, Theme } from "@mui/material/styles";
import useMediaQuery from "@mui/material/useMediaQuery";
@@ -90,20 +91,20 @@ export function DataTable<
// Return stack of cards for small screens
if (isSmall && cardsOnSmallScreens) {
return (
<Stack spacing={theme.spacing(4)}>
<Stack spacing={theme.spacing(LAYOUT.XS)}>
{data.map((row) => {
const key = row.id || row._id || Math.random();
keys.push(key);
return (
<Stack
onClick={() => (onRowClick ? onRowClick(row) : null)}
spacing={theme.spacing(4)}
spacing={theme.spacing(LAYOUT.XS)}
sx={{
borderStyle: "solid",
borderWidth: 1,
borderColor: theme.palette.divider,
borderRadius: theme.shape.borderRadius,
padding: theme.spacing(4),
padding: theme.spacing(LAYOUT.XS),
cursor: onRowClick ? "pointer" : "default",
}}
key={key}
@@ -155,7 +156,7 @@ export function DataTable<
<Table
sx={{
"&.MuiTable-root :is(.MuiTableHead-root, .MuiTableBody-root) :is(th, td)": {
paddingLeft: theme.spacing(8),
paddingLeft: theme.spacing(LAYOUT.MD),
},
"& .MuiTableCell-root": {
borderBottom: `1px solid ${theme.palette.divider}`,
@@ -168,13 +169,13 @@ export function DataTable<
color: theme.palette.text.secondary,
fontWeight: 500,
textTransform: "uppercase",
padding: `${theme.spacing(2)} ${theme.spacing(8)}`,
padding: `${theme.spacing(SPACING.LG)} ${theme.spacing(LAYOUT.MD)}`,
fontSize: theme.typography.fontSize,
},
"& :is(td)": {
backgroundColor: theme.palette.background.paper,
color: theme.palette.text.secondary,
padding: `${theme.spacing(7)} ${theme.spacing(8)}`,
padding: `${theme.spacing(LAYOUT.MD)} ${theme.spacing(LAYOUT.MD)}`,
fontSize: theme.typography.fontSize,
overflowX: "hidden",
},
@@ -514,7 +515,7 @@ export const Pagination = ({ ...props }: PaginationProps) => {
display: "grid",
gridTemplateColumns: "1fr 1fr",
gridAutoRows: "auto",
rowGap: theme.spacing(1),
rowGap: theme.spacing(SPACING.SM),
alignItems: "center",
justifyItems: "center",
paddingLeft: 0,
@@ -556,8 +557,8 @@ const EmptyView = ({ text, positive }: { text?: string; positive?: boolean }) =>
alignItems={"center"}
justifyContent={"center"}
sx={{
py: theme.spacing(12),
px: theme.spacing(4),
py: theme.spacing(LAYOUT.XL),
px: theme.spacing(LAYOUT.XS),
borderWidth: 1,
borderStyle: "solid",
borderColor: theme.palette.divider,
@@ -574,7 +575,7 @@ const EmptyView = ({ text, positive }: { text?: string; positive?: boolean }) =>
display: "flex",
alignItems: "center",
justifyContent: "center",
mb: theme.spacing(2),
mb: theme.spacing(SPACING.LG),
}}
>
<Icon
@@ -10,7 +10,7 @@ interface TextLinkProps extends StackProps {
text: string;
linkText: string;
href: string;
target?: string;
target?: "_self" | "_blank" | "_parent" | "_top";
}
export const TextLink = ({
@@ -34,6 +34,7 @@ export const TextLink = ({
to={href}
component={RouterLink}
target={target}
{...(target === "_blank" && { rel: "noopener noreferrer" })}
>
{linkText}
</Link>
@@ -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;
@@ -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";
@@ -1,7 +1,7 @@
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import MenuItem from "@mui/material/MenuItem";
import { Select } from "@/Components/v2/inputs";
import { Select } from "@/Components/inputs";
import "flag-icons/css/flag-icons.min.css";
import { useTranslation } from "react-i18next";
@@ -11,20 +11,26 @@ import type { ResponsiveStyleValue } from "@mui/system";
interface SliderInputProps extends SliderProps {
sx?: SliderProps["sx"];
showValue?: boolean;
formatDisplayValue?: (value: number) => string;
}
export const SliderInput = forwardRef<HTMLSpanElement, SliderInputProps>(
({ sx, showValue = false, ...props }, ref) => {
({ sx, showValue = false, formatDisplayValue, ...props }, ref) => {
const theme = useTheme();
const additionalSx = Array.isArray(sx) ? sx : sx ? [sx] : [];
const displayValue =
showValue && formatDisplayValue && typeof props.value === "number"
? formatDisplayValue(props.value)
: props.value;
return (
<Stack
gap={theme.spacing(8)}
direction={"row"}
alignItems={"center"}
>
{showValue && <Typography>{props.value}</Typography>}
{showValue && <Typography>{displayValue}</Typography>}
<Slider
{...props}
ref={ref}
@@ -63,12 +69,21 @@ interface SliderWithLabelProps extends SliderProps {
fieldLabel?: string;
required?: boolean;
showValue?: boolean;
formatDisplayValue?: (value: number) => string;
sliderMaxWidth?: ResponsiveStyleValue<number | string>;
}
export const SliderWithLabel = forwardRef<HTMLSpanElement, SliderWithLabelProps>(
(
{ fieldLabel, required, showValue = true, value, sliderMaxWidth = "100%", ...props },
{
fieldLabel,
required,
showValue = true,
formatDisplayValue,
value,
sliderMaxWidth = "100%",
...props
},
ref
) => {
const theme = useTheme();
@@ -82,6 +97,7 @@ export const SliderWithLabel = forwardRef<HTMLSpanElement, SliderWithLabelProps>
<SliderInput
{...props}
showValue={showValue}
formatDisplayValue={formatDisplayValue}
value={value}
ref={ref}
/>
@@ -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";
@@ -1,11 +1,7 @@
import Box from "@mui/material/Box";
import { useState, useEffect, useRef } from "react";
import { ThemeProvider, useTheme } from "@mui/material/styles";
import { useSelector } from "react-redux";
import BackgroundSVG from "@/assets/Images/background.svg";
import type { RootState } from "@/Types/state";
import { lightTheme, darkTheme } from "@/Utils/Theme/v2Theme";
import { OfflineBanner } from "@/Components/v2/design-elements";
import { useTheme } from "@mui/material/styles";
import { OfflineBanner } from "@/Components/design-elements";
import { setServerUnreachableCallback, get } from "@/Utils/ApiClient";
interface AppLayoutProps {
@@ -14,9 +10,6 @@ interface AppLayoutProps {
const AppLayout = ({ children }: AppLayoutProps) => {
const theme = useTheme();
const mode = useSelector((state: RootState) => state.ui.mode);
const v2theme = mode === "dark" ? darkTheme : lightTheme;
const [serverUnreachable, setServerUnreachable] = useState(false);
const retryIntervalRef = useRef<number | null>(null);
@@ -49,18 +42,10 @@ const AppLayout = ({ children }: AppLayoutProps) => {
<Box
sx={{
minHeight: "100vh",
// @ts-expect-error custom palette property
backgroundColor: theme.palette.primaryBackground.main,
backgroundImage: mode === "dark" ? `url("${BackgroundSVG}")` : "none",
backgroundSize: "100% 100%",
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
color: theme.palette.primary.contrastText,
backgroundColor: theme.palette.background.default,
}}
>
<ThemeProvider theme={v2theme}>
<OfflineBanner visible={serverUnreachable} />
</ThemeProvider>
<OfflineBanner visible={serverUnreachable} />
{children}
</Box>
);
@@ -1,26 +1,19 @@
import { Sidebar } from "@/Components/v2/sidebar";
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 { useSelector } from "react-redux";
import type { RootState } from "@/Types/state";
import { lightTheme, darkTheme } from "@/Utils/Theme/v2Theme";
import { ThemeProvider, useTheme } from "@mui/material";
import { useTheme } from "@mui/material";
const RootLayout = () => {
const mode = useSelector((state: RootState) => state.ui.mode);
const v2theme = mode === "dark" ? darkTheme : lightTheme;
const theme = useTheme();
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
const { collapsedWidth } = useSidebar();
return (
<Stack flexDirection="row">
<ThemeProvider theme={v2theme}>
<Sidebar />
</ThemeProvider>
<Sidebar />
<Stack
flex={1}
padding={6}
@@ -1,12 +1,22 @@
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";
import type { MonitorType } from "@/Types/Monitor";
import { Typography, useTheme } from "@mui/material";
import { useTranslation } from "react-i18next";
const types = ["http", "ping", "port", "docker", "game"];
const types = ["http", "ping", "port", "docker", "game", "grpc", "websocket"];
const typeDisplayNames: Record<string, string> = {
http: "HTTP",
ping: "Ping",
port: "Port",
docker: "Docker",
game: "Game",
grpc: "gRPC",
websocket: "WebSocket",
};
const statuses = ["up", "down"];
const states = ["active", "paused"];
@@ -30,6 +40,7 @@ export const ControlsFilter = ({
onClearFilters: () => void;
}) => {
const theme = useTheme();
const { t } = useTranslation();
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
const isFilterActive =
(selectedTypes?.length ?? 0) > 0 || selectedStatus !== "" || selectedState !== "";
@@ -50,7 +61,7 @@ export const ControlsFilter = ({
key={type}
value={type}
>
<Typography textTransform={"capitalize"}>{type}</Typography>
<Typography>{typeDisplayNames[type] ?? type}</Typography>
</MenuItem>
))}
</Select>
@@ -88,7 +99,7 @@ export const ControlsFilter = ({
variant="contained"
onClick={onClearFilters}
>
Clear Filters
{t("common.buttons.clearFilters")}
</Button>
)}
</Stack>
@@ -0,0 +1,203 @@
import { useRef, useEffect, useState } from "react";
import { Box, Typography, Stack, useTheme, GlobalStyles } from "@mui/material";
import Map, { Marker, Popup } from "react-map-gl/maplibre";
import type { MapRef } from "react-map-gl/maplibre";
import type { FlatGeoCheck } from "@/Types/GeoCheck";
import "maplibre-gl/dist/maplibre-gl.css";
interface GeoChecksMapProps {
geoChecks: FlatGeoCheck[];
}
export const GeoChecksMap = ({ geoChecks }: GeoChecksMapProps) => {
const mapRef = useRef<MapRef>(null);
const [selectedCheck, setSelectedCheck] = useState<FlatGeoCheck | null>(null);
const theme = useTheme();
const isDarkMode = theme.palette.mode === "dark";
const mapPopupStyles = (
<GlobalStyles
styles={{
".maplibregl-popup-content": {
background: "transparent !important",
padding: "0 !important",
boxShadow: "none !important",
},
".maplibregl-popup-tip": {
display: "none",
},
".maplibregl-ctrl-attrib": {
display: "none",
},
".maplibregl-ctrl-logo": {
display: "none",
},
".maplibregl-popup-close-button": {
color: theme.palette.text.primary,
background: "transparent",
fontSize: "20px",
padding: "4px",
"&:hover": {
backgroundColor: theme.palette.action.hover,
},
},
}}
/>
);
useEffect(() => {
if (geoChecks.length === 0 || !mapRef.current) return;
const bounds = geoChecks.reduce(
(acc, check) => {
return {
minLng: Math.min(acc.minLng, check.location.longitude),
maxLng: Math.max(acc.maxLng, check.location.longitude),
minLat: Math.min(acc.minLat, check.location.latitude),
maxLat: Math.max(acc.maxLat, check.location.latitude),
};
},
{
minLng: Infinity,
maxLng: -Infinity,
minLat: Infinity,
maxLat: -Infinity,
}
);
if (bounds.minLng !== Infinity) {
mapRef.current.fitBounds(
[
[bounds.minLng, bounds.minLat],
[bounds.maxLng, bounds.maxLat],
],
{ padding: 50, duration: 1000 }
);
}
}, [geoChecks]);
const getMarkerColor = (status: boolean): string => {
return status ? theme.palette.success.main : theme.palette.error.main;
};
const formatResponseTime = (timing: number): string => {
return `${timing.toFixed(0)}ms`;
};
return (
<>
{mapPopupStyles}
<Box
height={500}
width={"100%"}
borderRadius={theme.shape.borderRadius}
overflow={"hidden"}
>
<Map
ref={mapRef}
initialViewState={{
longitude: 0,
latitude: 20,
zoom: 1.5,
}}
style={{ height: "100%", width: "100%" }}
mapStyle={
isDarkMode
? "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json"
: "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json"
}
>
{geoChecks.map((check, index) => (
<Marker
key={`${check.id}-${index}`}
longitude={check.location.longitude}
latitude={check.location.latitude}
anchor="bottom"
onClick={(e: any) => {
e.originalEvent.stopPropagation();
setSelectedCheck(check);
}}
>
<div
style={{
width: "10px",
height: "10px",
borderRadius: "50%",
backgroundColor: getMarkerColor(check.status),
boxShadow: "0 2px 4px rgba(0,0,0,0.3)",
cursor: "pointer",
}}
/>
</Marker>
))}
{selectedCheck && (
<Popup
longitude={selectedCheck.location.longitude}
latitude={selectedCheck.location.latitude}
anchor="top"
onClose={() => setSelectedCheck(null)}
closeOnClick={false}
>
<Box
sx={{
minWidth: 200,
p: theme.spacing(4),
bgcolor: theme.palette.background.paper,
borderWidth: 1,
borderStyle: "solid",
borderColor: theme.palette.divider,
borderRadius: 1,
}}
>
<Typography
variant="subtitle1"
fontWeight="bold"
gutterBottom
>
{selectedCheck.location.city}, {selectedCheck.location.country}
</Typography>
<Stack spacing={0.5}>
<Typography variant="body2">
<Typography
component="span"
fontWeight="medium"
>
Status:
</Typography>{" "}
{selectedCheck.status ? "Up" : "Down"}
</Typography>
<Typography variant="body2">
<Typography
component="span"
fontWeight="medium"
>
Status Code:
</Typography>{" "}
{selectedCheck.statusCode}
</Typography>
<Typography variant="body2">
<Typography
component="span"
fontWeight="medium"
>
Response Time:
</Typography>{" "}
{formatResponseTime(selectedCheck.timings.total)}
</Typography>
<Typography
variant="caption"
color="text.secondary"
sx={{ mt: 0.5 }}
>
{new Date(selectedCheck.createdAt).toLocaleString()}
</Typography>
</Stack>
</Box>
</Popup>
)}
</Map>
</Box>
</>
);
};
@@ -0,0 +1,54 @@
import Stack from "@mui/material/Stack";
import { Tabs, Tab } from "@/Components/design-elements";
import { useTheme } from "@mui/material/styles";
import { useTranslation } from "react-i18next";
import type { GeoContinent } from "@/Types/GeoCheck";
interface HeaderGeoTabsProps {
geoCheckEnabled: boolean;
locations: GeoContinent[] | undefined;
selectedLocation: GeoContinent;
onLocationChange: (location: GeoContinent) => void;
}
export const HeaderGeoTabs = ({
geoCheckEnabled,
locations,
selectedLocation,
onLocationChange,
}: HeaderGeoTabsProps) => {
const { t } = useTranslation();
const theme = useTheme();
if (!geoCheckEnabled || !locations || locations.length === 0) {
return null;
}
const handleChange = (_event: React.SyntheticEvent, newValue: GeoContinent) => {
onLocationChange(newValue);
};
return (
<Stack
spacing={{ xs: theme.spacing(8), md: 0 }}
direction={{ xs: "column", md: "row" }}
alignItems={"center"}
justifyContent={"flex-start"}
>
<Tabs
value={selectedLocation}
onChange={handleChange}
>
{locations.map((location) => (
<Tab
key={location}
label={t(
`pages.createMonitor.form.geoChecks.option.locations.options.${location}`
)}
value={location}
/>
))}
</Tabs>
</Stack>
);
};
@@ -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";
@@ -4,7 +4,7 @@ import {
PausedStatusBox,
InitializingStatusBox,
BreachedStatusBox,
} from "@/Components/v2/design-elements";
} from "@/Components/design-elements";
import Stack from "@mui/material/Stack";
import type { MonitorsSummary } from "@/Types/Monitor";
@@ -1,5 +1,5 @@
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";
@@ -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,10 +9,10 @@ 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";
import type { PageSpeedGroupedCheck } from "@/Types/Check";
import type { Palette } from "@mui/material/styles";
type PaletteColorKey = Extract<
keyof Palette,
@@ -52,7 +52,7 @@ export const HistogramPageSpeedDetails = ({
checks,
range,
}: {
checks: CheckSnapshot[];
checks: PageSpeedGroupedCheck[];
range: string;
}) => {
const theme = useTheme();
@@ -73,7 +73,7 @@ export const HistogramPageSpeedDetails = ({
>
<AreaChart data={checks}>
<XAxis
dataKey={"createdAt"}
dataKey={"bucketDate"}
tick={(props) => (
<XTick
{...props}
@@ -2,8 +2,9 @@ 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 { SPACING, LAYOUT } from "@/Utils/Theme/constants";
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";
@@ -30,8 +31,8 @@ export const HistogramPageSpeedScoresTooltip = ({
border: 1,
borderColor: theme.palette.divider,
borderRadius: theme.shape.borderRadius,
py: theme.spacing(2),
px: theme.spacing(4),
py: theme.spacing(SPACING.LG),
px: theme.spacing(LAYOUT.XS),
}}
>
<Typography
@@ -54,12 +55,12 @@ export const HistogramPageSpeedScoresTooltip = ({
key={`${key}-tooltip`}
direction="row"
alignItems="center"
gap={theme.spacing(3)}
mt={theme.spacing(1)}
gap={theme.spacing(SPACING.XXL)}
mt={theme.spacing(SPACING.SM)}
>
<Box
width={theme.spacing(4)}
height={theme.spacing(4)}
width={theme.spacing(LAYOUT.XS)}
height={theme.spacing(LAYOUT.XS)}
sx={{ borderRadius: "50%", backgroundColor: dotColor }}
/>
<Typography
@@ -1,6 +1,6 @@
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/TimeUtils";
@@ -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";
@@ -1,6 +1,8 @@
export * from "./ControlsFilter";
export * from "./MonitorStatBoxes";
export * from "./HeaderMonitorControls";
export * from "./HeaderGeoTabs";
export * from "./GeoChecksMap";
export * from "./charts/HistogramStatus";
export * from "./charts/RadialAvgResponse";
export * from "./charts/HistogramDetails";
@@ -5,12 +5,13 @@ import IconButton from "@mui/material/IconButton";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import { useTheme } from "@mui/material";
import { SPACING, LAYOUT } from "@/Utils/Theme/constants";
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/v2/design-elements";
import { Avatar, Icon } from "@/Components/design-elements";
import { clearAuthState } from "@/Features/Auth/authSlice.js";
import type { RootState } from "@/Types/state.js";
@@ -64,9 +65,9 @@ export const AuthFooter = ({ collapsed, accountMenuItems }: AuthFooterProps) =>
});
const menuItemSx = {
gap: theme.spacing(2),
gap: theme.spacing(LAYOUT.MD),
borderRadius: theme.shape.borderRadius,
pl: theme.spacing(4),
pl: theme.spacing(LAYOUT.XS),
"& svg": { stroke: theme.palette.text.secondary },
};
@@ -74,9 +75,9 @@ export const AuthFooter = ({ collapsed, accountMenuItems }: AuthFooterProps) =>
<Stack
direction="row"
alignItems="center"
py={theme.spacing(4)}
px={theme.spacing(8)}
gap={theme.spacing(4)}
py={theme.spacing(LAYOUT.XS)}
px={theme.spacing(LAYOUT.MD)}
gap={theme.spacing(LAYOUT.MD)}
>
<Avatar
small
@@ -88,7 +89,7 @@ export const AuthFooter = ({ collapsed, accountMenuItems }: AuthFooterProps) =>
<Stack
direction="row"
alignItems="center"
gap={theme.spacing(2)}
gap={theme.spacing(SPACING.MD)}
minWidth={0}
flex={1}
>
@@ -132,10 +133,15 @@ export const AuthFooter = ({ collapsed, accountMenuItems }: AuthFooterProps) =>
disableScrollLock
anchorOrigin={{ vertical: "top", horizontal: "right" }}
slotProps={{
paper: { sx: { mt: theme.spacing(-4), ml: collapsed ? theme.spacing(2) : 0 } },
paper: {
sx: {
mt: theme.spacing(-LAYOUT.XS),
ml: collapsed ? theme.spacing(SPACING.LG) : 0,
},
},
}}
MenuListProps={{ sx: { p: 2, "& li": { m: 0 } } }}
sx={{ ml: theme.spacing(4) }}
sx={{ ml: theme.spacing(LAYOUT.XS) }}
>
{collapsed && (
<Box
@@ -1,4 +1,4 @@
import { Icon } from "@/Components/v2/design-elements";
import { Icon } from "@/Components/design-elements";
import {
Globe,
@@ -1,6 +1,7 @@
import { Typography, IconButton, Stack, Box } from "@mui/material";
import { Icon } from "@/Components/v2/design-elements";
import { Icon } from "@/Components/design-elements";
import { X } from "lucide-react";
import { SPACING, LAYOUT } from "@/Utils/Theme/constants";
import { useTheme } from "@mui/material";
import { useSelector, useDispatch } from "react-redux";
@@ -34,25 +35,22 @@ export const StarPrompt = ({
return (
<Stack
direction="column"
sx={{
width: "100%",
padding: `${theme.spacing(6)} ${theme.spacing(6)}`,
borderTop: `1px solid ${theme.palette.divider}`,
borderBottom: `1px solid ${theme.palette.divider}`,
borderRadius: 0,
gap: theme.spacing(1.5),
}}
gap={theme.spacing(LAYOUT.MD)}
padding={theme.spacing(LAYOUT.SM)}
width={"100%"}
borderTop={`1px solid ${theme.palette.divider}`}
borderBottom={`1px solid ${theme.palette.divider}`}
>
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
width="100%"
pl={theme.spacing(4)}
pl={theme.spacing(LAYOUT.XS)}
>
<Typography
variant="subtitle2"
mt={theme.spacing(3)}
mt={theme.spacing(SPACING.XXL)}
>
{t("components.sidebar.starPrompt.title")}
</Typography>
@@ -78,7 +76,7 @@ export const StarPrompt = ({
<Typography
sx={{
mb: 1,
px: theme.spacing(4),
px: theme.spacing(LAYOUT.XS),
}}
>
{t("components.sidebar.starPrompt.description")}
@@ -96,7 +94,7 @@ export const StarPrompt = ({
"&:hover": {
opacity: 0.8,
},
pl: theme.spacing(4),
pl: theme.spacing(LAYOUT.XS),
filter: mode === "dark" ? "invert(1)" : "none",
}}
/>
@@ -3,12 +3,13 @@ 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 { LAYOUT } from "@/Utils/Theme/constants";
import { useSidebar } from "@/Hooks/useSidebar.js";
import { Logo } from "@/Components/v2/sidebar/Logo";
import { getMenu, getBottomMenu, getAccountMenu } from "@/Components/v2/sidebar/Menu";
import { NavItem } from "@/Components/v2/sidebar/NavItem";
import { StarPrompt } from "@/Components/v2/sidebar/StarPrompt";
import { AuthFooter } from "@/Components/v2/sidebar/Authfooter";
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";
@@ -65,9 +66,9 @@ export const Sidebar = () => {
left={0}
minHeight={"100vh"}
maxHeight={"100vh"}
paddingTop={theme.spacing(6)}
paddingBottom={theme.spacing(6)}
gap={theme.spacing(6)}
paddingTop={theme.spacing(LAYOUT.SM)}
paddingBottom={theme.spacing(LAYOUT.SM)}
gap={theme.spacing(LAYOUT.SM)}
borderRight={`1px solid ${theme.palette.divider}`}
width={width}
sx={{
@@ -81,13 +82,13 @@ export const Sidebar = () => {
component="nav"
disablePadding
sx={{
px: theme.spacing(6),
px: theme.spacing(LAYOUT.SM),
flex: 1,
}}
>
<Logo
pt={theme.spacing(8)}
pb={theme.spacing(10)}
pt={theme.spacing(LAYOUT.MD)}
pb={theme.spacing(LAYOUT.MD)}
/>
{menu.map((item) => {
const selected = location.pathname.startsWith(`/${item.path}`);
@@ -105,7 +106,7 @@ export const Sidebar = () => {
<List
component="nav"
disablePadding
sx={{ px: theme.spacing(6) }}
sx={{ px: theme.spacing(LAYOUT.SM) }}
>
{bottomMenu.map((item) => {
const selected = location.pathname.startsWith(`/${item.path}`);
@@ -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;
@@ -1,21 +0,0 @@
.MuiBreadcrumbs-root {
min-height: 34px;
}
.MuiBreadcrumbs-root svg {
width: 16px;
min-height: 16px;
}
.MuiBreadcrumbs-root .MuiBreadcrumbs-li a {
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,39 +0,0 @@
import { Stack, styled } from "@mui/material";
const ConfigBox = styled(Stack)(({ theme }) => ({
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
backgroundColor: theme.palette.primary.main,
border: 1,
borderStyle: "solid",
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.spacing(2),
"& > *": {
paddingTop: theme.spacing(15),
paddingBottom: theme.spacing(15),
},
"& > div:first-of-type": {
flex: 0.7,
borderRight: 1,
borderRightStyle: "solid",
borderRightColor: theme.palette.primary.lowContrast,
paddingRight: theme.spacing(15),
paddingLeft: theme.spacing(15),
backgroundColor: theme.palette.tertiary.background,
"& :is(h1, h2):first-of-type": {
fontWeight: 600,
marginBottom: theme.spacing(4),
},
},
"& > div:last-of-type": {
flex: 1,
paddingRight: theme.spacing(20),
paddingLeft: theme.spacing(20),
},
"& h1, & h2": {
color: theme.palette.primary.contrastTextSecondary,
},
}));
export default ConfigBox;
@@ -1,73 +0,0 @@
import { useId } from "react";
import PropTypes from "prop-types";
import { Modal, Stack, Typography } from "@mui/material";
const GenericDialog = ({ title, description, open, onClose, theme, children, width }) => {
const titleId = useId();
const descriptionId = useId();
const ariaDescribedBy = description?.length > 0 ? descriptionId : "";
return (
<Modal
aria-labelledby={titleId}
aria-describedby={ariaDescribedBy}
open={open}
onClose={onClose}
onClick={(e) => e.stopPropagation()}
>
<Stack
gap={theme.spacing(2)}
width={width}
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
minWidth: 400,
bgcolor: theme.palette.primary.main,
border: 1,
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
boxShadow: 24,
p: theme.spacing(15),
"&:focus": {
outline: "none",
},
}}
>
<Typography
id={titleId}
component="h2"
fontSize={16}
color={theme.palette.primary.contrastText}
fontWeight={600}
marginBottom={theme.spacing(4)}
>
{title}
</Typography>
{description && (
<Typography
id={descriptionId}
color={theme.palette.primary.contrastTextTertiary}
marginBottom={theme.spacing(4)}
>
{description}
</Typography>
)}
{children}
</Stack>
</Modal>
);
};
GenericDialog.propTypes = {
title: PropTypes.string.isRequired,
description: PropTypes.string,
open: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired,
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node])
.isRequired,
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]),
};
export { GenericDialog };
-63
View File
@@ -1,63 +0,0 @@
import PropTypes from "prop-types";
import { Button, Stack } from "@mui/material";
import { GenericDialog } from "./genericDialog.jsx";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
const Dialog = ({
title,
description,
open,
onCancel,
confirmationButtonLabel,
onConfirm,
isLoading,
}) => {
const theme = useTheme();
const { t } = useTranslation();
return (
<GenericDialog
title={title}
description={description}
open={open}
onClose={onCancel}
theme={theme}
>
<Stack
direction="row"
gap={theme.spacing(4)}
mt={theme.spacing(12)}
justifyContent="flex-end"
>
<Button
variant="contained"
color="secondary"
onClick={onCancel}
>
{t("cancel", "Cancel")}
</Button>
<Button
variant="contained"
color="error"
loading={isLoading}
onClick={onConfirm}
>
{confirmationButtonLabel}
</Button>
</Stack>
</GenericDialog>
);
};
Dialog.propTypes = {
title: PropTypes.string.isRequired,
description: PropTypes.string,
open: PropTypes.bool.isRequired,
onCancel: PropTypes.func.isRequired,
confirmationButtonLabel: PropTypes.string.isRequired,
onConfirm: PropTypes.func.isRequired,
isLoading: PropTypes.bool,
};
export default Dialog;
-120
View File
@@ -1,120 +0,0 @@
/**
* Maps legacy icon names to Lucide icon names
* Use: import { iconMap } from './iconMap'; LucideIcons[iconMap['monitors']]
*/
export const iconMap = {
// Navigation/Sidebar icons
monitors: "Globe",
incidents: "AlertTriangle",
integrations: "Link",
"page-speed": "Gauge",
settings: "Settings",
notifications: "Bell",
maintenance: "Wrench",
"status-pages": "Wifi",
docs: "FileText",
discussions: "MessageCircle",
support: "HelpCircle",
changeLog: "Code",
logs: "Database",
// User icons
user: "User",
"user-two": "Users",
"user-edit": "UserPen",
groups: "Users",
lock: "Lock",
key: "Key",
logout: "LogOut",
// Action icons
edit: "Pencil",
"trash-bin": "Trash2",
search: "Search",
"close-icon": "X",
check: "Check",
"check-icon": "CheckCircle",
"check-outlined": "CheckCircle",
// Arrow icons
"left-arrow": "ChevronLeft",
"right-arrow": "ChevronRight",
"up-arrow": "ChevronUp",
"down-arrow": "ChevronDown",
"left-arrow-double": "ChevronsLeft",
"right-arrow-double": "ChevronsRight",
"left-arrow-long": "ArrowLeft",
"top-right-arrow": "ArrowUpRight",
"open-in-new-page": "ExternalLink",
"selector-vertical": "ChevronsUpDown",
// Status/Alert icons
"alert-icon": "Bell",
"warning-icon": "AlertCircle",
"history-icon": "History",
// Data/Chart icons
dashboard: "LayoutGrid",
"monitor-graph-line": "TrendingUp",
"response-time-icon": "TrendingUp",
"uptime-icon": "Activity",
"speedometer-icon": "Gauge",
"spedometer-icon": "Gauge",
"ruler-icon": "Ruler",
"performance-report": "Layers",
"average-response-icon": "BarChart3",
"distributed-uptime": "Network",
// Misc icons
calendar: "Calendar",
"calendar-check": "CalendarCheck",
"clock-snooze": "Clock",
mail: "Mail",
email: "Mail",
folder: "Folder",
"dots-vertical": "MoreVertical",
"pause-icon": "PauseCircle",
"resume-icon": "PlayCircle",
"checkbox-filled": "CheckSquare",
"checkbox-outline": "Square",
"radio-checked": "Circle",
"cpu-chip": "Cpu",
certificate: "Award",
"interval-check": "RefreshCw",
};
/**
* MUI icon to Lucide mapping
*/
export const muiToLucideMap = {
AddCircleOutline: "PlusCircle",
ArrowDownwardRounded: "ArrowDown",
ArrowUpwardRounded: "ArrowUp",
ArrowDropDown: "ChevronDown",
ArrowOutward: "ExternalLink",
CheckCircle: "CheckCircle2",
Clear: "X",
Close: "X",
CloseRounded: "X",
CloudUpload: "Upload",
Delete: "Trash2",
DeleteOutlineRounded: "Trash2",
Email: "Mail",
ErrorOutline: "AlertTriangle",
ErrorOutlineOutlined: "AlertTriangle",
Image: "ImageIcon",
InfoOutlined: "Info",
KeyboardArrowDown: "ChevronDown",
Menu: "Menu",
PauseCircleOutline: "PauseCircle",
PauseOutlined: "Pause",
PlayArrowOutlined: "Play",
PlayCircleOutlineRounded: "PlayCircle",
RadioButtonChecked: "Circle",
ReorderRounded: "GripVertical",
SettingsOutlined: "Settings",
Share: "Share2",
Visibility: "Eye",
VisibilityOff: "EyeOff",
WarningAmberOutlined: "AlertTriangle",
};
-198
View File
@@ -1,198 +0,0 @@
import { useTheme } from "@mui/material";
import PropTypes from "prop-types";
import {
Activity,
AlertCircle,
AlertTriangle,
ArrowDown,
ArrowLeft,
ArrowUp,
ArrowUpRight,
Bell,
Calendar,
Check,
CheckCircle,
CheckCircle2,
CheckSquare,
ChevronDown,
ChevronLeft,
ChevronRight,
ChevronsLeft,
ChevronsRight,
ChevronsUpDown,
Circle,
CircleDot,
Clock,
Code,
Cpu,
Database,
ExternalLink,
Eye,
EyeOff,
FileText,
Gauge,
Globe,
GripVertical,
HelpCircle,
History,
Image as ImageIcon,
Info,
Key,
Layers,
Link,
Lock,
LogOut,
Mail,
Menu,
MessageCircle,
MoreVertical,
Pause,
PauseCircle,
Play,
PlayCircle,
PlusCircle,
RefreshCw,
Ruler,
Search,
Settings,
Square,
Trash2,
TrendingUp,
Upload,
User,
Users,
Wifi,
Wrench,
X,
} from "lucide-react";
/**
* Map of icon names to Lucide icon components.
* Only icons explicitly imported here will be included in the bundle (tree-shaking).
* To add a new icon: import it above and add it to this object.
*/
const iconComponents = {
Activity,
AlertCircle,
AlertTriangle,
ArrowDown,
ArrowLeft,
ArrowUp,
ArrowUpRight,
Bell,
Calendar,
Check,
CheckCircle,
CheckCircle2,
CheckSquare,
ChevronDown,
ChevronLeft,
ChevronRight,
ChevronsLeft,
ChevronsRight,
ChevronsUpDown,
Circle,
CircleDot,
Clock,
Code,
Cpu,
Database,
ExternalLink,
Eye,
EyeOff,
FileText,
Gauge,
Globe,
GripVertical,
HelpCircle,
History,
Image: ImageIcon,
Info,
Key,
Layers,
Link,
Lock,
LogOut,
Mail,
Menu,
MessageCircle,
MoreVertical,
Pause,
PauseCircle,
Play,
PlayCircle,
PlusCircle,
RefreshCw,
Ruler,
Search,
Settings,
Square,
Trash2,
TrendingUp,
Upload,
User,
Users,
Wifi,
Wrench,
X,
};
/**
* Theme-aware icon component wrapping Lucide icons
*
* @param {string} name - Lucide icon name (e.g., "Bell", "Check", "ArrowLeft")
* @param {number} size - Icon size in pixels (default: 20)
* @param {string} color - Direct color value OR theme path like "primary.contrastText"
* @param {number} strokeWidth - Stroke width (default: 1.5)
* @param {string} fill - Fill color (default: "none")
*/
const Icon = ({ name, size = 20, color, strokeWidth = 1.5, fill = "none", ...props }) => {
const theme = useTheme();
// Resolve color from theme path or use direct value
const resolveColor = (colorValue) => {
if (!colorValue) {
return theme.palette.primary.contrastTextTertiary; // Default icon color
}
// If it's a theme path like "primary.contrastText"
if (typeof colorValue === "string" && colorValue.includes(".")) {
const parts = colorValue.split(".");
let resolved = theme.palette;
for (const part of parts) {
resolved = resolved?.[part];
}
return resolved || colorValue;
}
// Direct color value
return colorValue;
};
const LucideIcon = iconComponents[name];
if (!LucideIcon) {
console.warn(`Icon "${name}" not found in Icon component`);
return null;
}
return (
<LucideIcon
size={size}
color={resolveColor(color)}
strokeWidth={strokeWidth}
fill={fill}
{...props}
/>
);
};
Icon.propTypes = {
name: PropTypes.string.isRequired,
size: PropTypes.number,
color: PropTypes.string,
strokeWidth: PropTypes.number,
fill: PropTypes.string,
};
export default Icon;
@@ -1,116 +0,0 @@
import PropTypes from "prop-types";
import { FormControlLabel, Checkbox as MuiCheckbox } from "@mui/material";
import { useTheme } from "@emotion/react";
import Icon from "../../Icon";
/**
* Checkbox Component
*
* A customized checkbox component using Material-UI that supports custom sizing,
* disabled states, and custom icons.
*
* @component
* @param {Object} props - Component properties
* @param {string} props.id - Unique identifier for the checkbox input
* @param {string} [props.name] - Optional name attribute for the checkbox
* @param {(string|React.ReactNode)} props.label - Label text or node for the checkbox
* @param {('small'|'medium'|'large')} [props.size='medium'] - Size of the checkbox icon
* @param {boolean} props.isChecked - Current checked state of the checkbox
* @param {string} [props.value] - Optional value associated with the checkbox
* @param {Function} [props.onChange] - Callback function triggered when checkbox state changes
* @param {boolean} [props.isDisabled] - Determines if the checkbox is disabled
*
* @returns {React.ReactElement} Rendered Checkbox component
*
* @example
* // Basic usage
* <Checkbox
* id="terms-checkbox"
* label="I agree to terms"
* isChecked={agreed}
* onChange={handleAgree}
* />
*
* @example
* // With custom size and disabled state
* <Checkbox
* id="advanced-checkbox"
* label="Advanced Option"
* size="large"
* isChecked={isAdvanced}
* isDisabled={!canModify}
* onChange={handleAdvancedToggle}
* />
*/
const Checkbox = ({
id,
name,
label,
size = "medium",
isChecked,
value,
onChange,
isDisabled,
}) => {
/* TODO move sizes to theme */
const sizes = { small: "14px", medium: "16px", large: "18px" };
const theme = useTheme();
return (
<FormControlLabel
className="checkbox-wrapper"
control={
<MuiCheckbox
checked={isDisabled ? false : isChecked}
name={name}
value={value}
onChange={onChange}
icon={<Icon name="Square" />}
checkedIcon={<Icon name="CheckSquare" />}
inputProps={{
"aria-label": "controlled checkbox",
id: id,
}}
sx={{
"&:hover": { backgroundColor: "transparent" },
"& svg": { width: sizes[size], height: sizes[size] },
}}
/>
}
label={label}
disabled={isDisabled}
sx={{
borderRadius: theme.shape.borderRadius,
p: theme.spacing(2.5),
"& .MuiButtonBase-root": {
width: theme.spacing(10),
p: 0,
mr: theme.spacing(6),
},
"&:not(:has(.Mui-disabled)):hover": {
backgroundColor: theme.palette.tertiary.main,
},
"& span.MuiTypography-root": {
fontSize: 13,
color: theme.palette.primary.contrastTextTertiary,
},
".MuiFormControlLabel-label.Mui-disabled": {
color: theme.palette.primary.contrastTextTertiary,
opacity: 0.25,
},
}}
/>
);
};
Checkbox.propTypes = {
id: PropTypes.string,
name: PropTypes.string,
label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired,
size: PropTypes.oneOf(["small", "medium", "large"]),
isChecked: PropTypes.bool.isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
onChange: PropTypes.func,
isDisabled: PropTypes.bool,
};
export default Checkbox;
@@ -1,63 +0,0 @@
import PropTypes from "prop-types";
import { Stack, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import { MuiColorInput } from "mui-color-input";
/**
*
* @param {*} id The ID of the component
* @param {*} value The color value of the component
* @param {*} error The error of the component
* @param {*} onChange The Change handler function
* @param {*} onBlur The Blur handler function
* @returns The ColorPicker component
* Example usage:
* <ColorPicker
* id="color"
* value={form.color}
* error={errors["color"]}
* onChange={handleColorChange}
* onBlur={handleBlur}
* >
* </ColorPicker>
*/
const ColorPicker = ({ id, name, value, error, onChange, onBlur }) => {
const theme = useTheme();
return (
<Stack gap={theme.spacing(4)}>
<MuiColorInput
format="hex"
name={name}
type="color-picker"
value={value}
id={id}
onChange={(color) => onChange({ target: { name, value: color } })}
onBlur={onBlur}
/>
{error && (
<Typography
component="span"
className="input-error"
color={theme.palette.error.main}
mt={theme.spacing(2)}
sx={{
opacity: 0.8,
}}
>
{error}
</Typography>
)}
</Stack>
);
};
ColorPicker.propTypes = {
id: PropTypes.string.isRequired,
value: PropTypes.string,
error: PropTypes.string,
onChange: PropTypes.func.isRequired,
onBlur: PropTypes.func,
name: PropTypes.string,
};
export default ColorPicker;
@@ -1,51 +0,0 @@
import { Stack, Typography } from "@mui/material";
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
const DEFAULT_GAP = 6;
const FieldWrapper = ({
label,
children,
gap,
labelMb,
labelFontWeight = 500,
labelVariant = "h3",
labelSx = {},
sx = {},
}) => {
const theme = useTheme();
return (
<Stack
gap={gap ?? theme.spacing(DEFAULT_GAP)}
sx={sx}
>
{label && (
<Typography
component={labelVariant}
color={theme.palette.primary.contrastTextSecondary}
fontWeight={labelFontWeight}
sx={{
...(labelMb !== undefined && { mb: theme.spacing(labelMb) }),
...labelSx,
}}
>
{label}
</Typography>
)}
{children}
</Stack>
);
};
FieldWrapper.propTypes = {
label: PropTypes.node,
children: PropTypes.node.isRequired,
gap: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]),
labelMb: PropTypes.number,
labelFontWeight: PropTypes.number,
labelVariant: PropTypes.string,
labelSx: PropTypes.object,
sx: PropTypes.object,
};
export default FieldWrapper;
@@ -1,249 +0,0 @@
// Components
import { Box, Stack, Typography } from "@mui/material";
import Icon from "../../Icon";
import Image from "../../Image/index.jsx";
import TextField from "@mui/material/TextField";
import IconButton from "@mui/material/IconButton";
import ProgressUpload from "../../ProgressBars/index.jsx";
// Utils
import PropTypes from "prop-types";
import { useCallback, useState, useRef, useEffect } from "react";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
/**
* ImageUpload component allows users to upload images with drag-and-drop functionality.
* It supports file size and format validation.
*
* @component
* @param {Object} props - Component props
* @param {boolean} [props.previewIsRound=false] - Determines if the image preview should be round
* @param {string} [props.src] - Source URL of the image to display
* @param {function} props.onChange - Callback function to handle file change, takes a file as an argument
* @param {number} [props.maxSize=3145728] - Maximum file size allowed in bytes (default is 3MB)
* @param {Array<string>} [props.accept=['jpg', 'jpeg', 'png']] - Array of accepted file formats
* @param {Object} [props.errors] - Object containing error messages
* @returns {JSX.Element} The rendered component
*/
const ImageUpload = ({
previewIsRound = false,
src,
onChange,
maxSize = 3 * 1024 * 1024,
accept = ["jpg", "jpeg", "png"],
error,
}) => {
const theme = useTheme();
const { t } = useTranslation();
const [uploadComplete, setUploadComplete] = useState(false);
const [completedFile, setCompletedFile] = useState(null);
const [file, setFile] = useState(null);
const [progress, setProgress] = useState({ value: 0, isLoading: false });
const intervalRef = useRef(null);
const [localError, setLocalError] = useState(null);
const [isDragging, setIsDragging] = useState(false);
const roundStyle = previewIsRound ? { borderRadius: "50%" } : {};
const handleImageChange = useCallback(
(file) => {
if (!file) return;
const isValidType = accept.some((type) => file.type.includes(type));
const isValidSize = file.size <= maxSize;
if (!isValidType) {
setLocalError(t("invalidFileFormat"));
return;
}
if (!isValidSize) {
setLocalError(t("invalidFileSize"));
return;
}
setLocalError(null);
const previewFile = {
src: URL.createObjectURL(file),
name: file.name,
file,
};
setFile(previewFile);
setProgress({ value: 0, isLoading: true });
intervalRef.current = setInterval(() => {
setProgress((prev) => {
const buffer = 12;
if (prev.value + buffer >= 100) {
clearInterval(intervalRef.current);
setUploadComplete(true);
setCompletedFile(previewFile);
return { value: 100, isLoading: false };
}
return { value: prev.value + buffer, isLoading: true };
});
}, 120);
},
[maxSize, accept]
);
useEffect(() => {
if (uploadComplete && completedFile) {
onChange?.(completedFile);
setUploadComplete(false);
setCompletedFile(null);
}
}, [uploadComplete, completedFile, onChange]);
return (
<>
{src ? (
<Stack
direction="row"
justifyContent="center"
>
<Image
alt="Uploaded preview"
src={src}
width="250px"
height="250px"
sx={{ ...roundStyle }}
/>
</Stack>
) : (
<>
<Box
className="image-field-wrapper"
mt={theme.spacing(8)}
onDragEnter={() => setIsDragging(true)}
onDragLeave={() => setIsDragging(false)}
onDrop={() => setIsDragging(false)}
sx={{
position: "relative",
height: "fit-content",
border: "dashed",
borderRadius: theme.shape.borderRadius,
borderColor: isDragging
? theme.palette.primary.main
: theme.palette.primary.lowContrast,
backgroundColor: isDragging ? "hsl(215, 87%, 51%, 0.05)" : "transparent",
borderWidth: "2px",
transition: "0.2s",
"&:hover": {
borderColor: theme.palette.primary.main,
backgroundColor: "hsl(215, 87%, 51%, 0.05)",
},
}}
>
<TextField
type="file"
onChange={(e) => handleImageChange(e?.target?.files?.[0])}
sx={{
width: "100%",
"& .MuiInputBase-input[type='file']": {
opacity: 0,
cursor: "pointer",
maxWidth: "500px",
minHeight: "175px",
zIndex: 1,
},
"& fieldset": {
padding: 0,
border: "none",
},
}}
/>
<Stack
alignItems="center"
gap="4px"
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
zIndex: 0,
width: "100%",
}}
>
<IconButton
sx={{
pointerEvents: "none",
borderRadius: theme.shape.borderRadius,
border: `solid ${theme.shape.borderThick}px ${theme.palette.primary.lowContrast}`,
boxShadow: theme.shape.boxShadow,
}}
>
<Icon
name="Upload"
size={24}
/>
</IconButton>
<Typography
component="h2"
color={theme.palette.primary.contrastTextTertiary}
>
<Typography
component="span"
fontSize="inherit"
color="info"
fontWeight={500}
>
{t("ClickUpload")}
</Typography>{" "}
or {t("DragandDrop")}
</Typography>
<Typography
component="p"
color={theme.palette.primary.contrastTextTertiary}
sx={{ opacity: 0.6 }}
>
({t("MaxSize")}: {Math.round(maxSize / 1024 / 1024)}MB)
</Typography>
</Stack>
</Box>
{(localError || progress.isLoading || progress.value !== 0) && (
<ProgressUpload
icon={
<Icon
name="Image"
size={20}
/>
}
label={file?.name || "Upload failed"}
size={file?.size}
progress={progress.value}
onClick={() => {
clearInterval(intervalRef.current);
setFile(null);
setProgress({ value: 0, isLoading: false });
setLocalError(null);
onChange(undefined);
}}
error={localError || error}
/>
)}
<Typography
component="p"
color={theme.palette.primary.contrastTextTertiary}
sx={{ opacity: 0.6 }}
>
{t("SupportedFormats")}: {accept.join(", ").toUpperCase()}
</Typography>
</>
)}
</>
);
};
ImageUpload.propTypes = {
previewIsRound: PropTypes.bool,
src: PropTypes.string,
onChange: PropTypes.func,
maxSize: PropTypes.number,
accept: PropTypes.array,
error: PropTypes.string,
};
export default ImageUpload;
Whitespace-only changes.
@@ -1,117 +0,0 @@
import PropTypes from "prop-types";
import { FormControlLabel, Radio as MUIRadio, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import Icon from "../../Icon";
import "./index.css";
/**
* Radio component.
*
* @component
* @example
* // Usage:
* <Radio
* title="Radio Button Title"
* desc="Radio Button Description"
* size="small"
* />
*
* @param {Object} props - The component
* @param {string} id - The id of the radio button.
* @param {string} title - The title of the radio button.
* @param {string} [desc] - The description of the radio button.
* @param {string} [size="small"] - The size of the radio button.
* @returns {JSX.Element} - The rendered Radio component.
*/
const Radio = ({
name,
checked,
value,
id,
size,
title,
desc,
onChange,
labelSpacing,
}) => {
const theme = useTheme();
return (
<FormControlLabel
className="custom-radio-button"
name={name}
checked={checked}
value={value}
control={
<MUIRadio
id={id}
size={size}
checkedIcon={
<Icon
name="CircleDot"
size={16}
/>
}
sx={{
color: "transparent",
width: 16,
height: 16,
boxShadow: `inset 0 0 0 1px ${theme.palette.secondary.main}`,
"&:not(.Mui-checked)": {
boxShadow: `inset 0 0 0 1px ${theme.palette.primary.contrastText}70`, // Use theme text color for the outline
},
mt: theme.spacing(0.5),
}}
/>
}
onChange={onChange}
label={
<>
<Typography
component="p"
mb={
labelSpacing !== undefined ? theme.spacing(labelSpacing) : theme.spacing(2)
}
>
{title}
</Typography>
<Typography
component="h6"
mt={theme.spacing(1)}
color={theme.palette.primary.contrastTextSecondary}
>
{desc}
</Typography>
</>
}
labelPlacement="end"
sx={{
alignItems: "flex-start",
p: theme.spacing(2.5),
m: theme.spacing(-2.5),
borderRadius: theme.shape.borderRadius,
"&:hover": {
backgroundColor: theme.palette.tertiary.main,
},
"& .MuiButtonBase-root": {
p: 0,
mr: theme.spacing(6),
},
}}
/>
);
};
Radio.propTypes = {
title: PropTypes.string,
desc: PropTypes.string,
size: PropTypes.string,
name: PropTypes.string,
checked: PropTypes.bool,
value: PropTypes.string,
id: PropTypes.string,
onChange: PropTypes.func,
};
export default Radio;
@@ -1,315 +0,0 @@
import PropTypes from "prop-types";
import {
Box,
ListItem,
Autocomplete,
TextField,
Typography,
Checkbox,
} from "@mui/material";
import { useTheme } from "@emotion/react";
import Icon from "../../Icon";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import FieldWrapper from "../FieldWrapper/index.jsx";
/**
* Search component using Material UI's Autocomplete.
*
* @param {Object} props
* @param {string} props.id - Unique identifier for the Autocomplete component
* @param {Array<Object>} props.options - Options to display in the Autocomplete dropdown
* @param {string} props.filteredBy - Key to access the option label from the options
* @param {string} props.value - Current input value for the Autocomplete
* @param {Function} props.handleChange - Function to call when the input changes
* @param {Function} Prop.onBlur - Function to call when the input is blured
* @param {Object} props.sx - Additional styles to apply to the component
* @param {string} props.unit - Label to identify type of options
* @returns {JSX.Element} The rendered Search component
*/
const SearchAdornment = () => {
const theme = useTheme();
return (
<Box height={16}>
<Icon
name="Search"
size={16}
color="primary.contrastTextTertiary"
strokeWidth={1.2}
/>
</Box>
);
};
//TODO keep search state inside of component.
const Search = ({
label,
id,
options,
filteredBy,
secondaryLabel,
value,
inputValue,
handleInputChange,
handleChange,
sx,
multiple = false,
isAdorned = true,
error,
disabled,
startAdornment,
endAdornment,
onBlur,
//FieldWrapper's props
gap,
labelMb,
labelFontWeight,
labelVariant,
labelSx = {},
unit = "option",
maxWidth = "100%",
}) => {
const theme = useTheme();
const { t } = useTranslation();
const [selectAll, setSelectAll] = React.useState(false);
const [open, setOpen] = React.useState(false);
const enhancedOptions = React.useMemo(() => {
return multiple && isAdorned
? [
{
[filteredBy]: t("selectAll"),
isSelectAll: true,
_id: "select_all",
id: "select_all",
},
...options,
]
: options;
}, [multiple, isAdorned, options, filteredBy]);
const isOptionSelected = (option) => {
if (!multiple && !isAdorned) return false;
if (Array.isArray(value)) {
return value.some((item) => (item._id ?? item.id) === (option._id ?? option.id));
}
return false;
};
const handleSelectAll = (isSelectAll) => {
const newValue = isSelectAll ? [...options] : [];
handleChange(newValue);
setSelectAll(isSelectAll);
};
useEffect(() => {
const allSelected =
Array.isArray(value) && Array.isArray(options) && value.length === options.length;
if (selectAll !== allSelected) setSelectAll(allSelected);
}, [value, options]);
return (
<Autocomplete
onBlur={onBlur}
multiple={multiple}
id={id}
value={value}
open={open}
onOpen={() => setOpen(true)}
onClose={(event, reason) => {
if (reason === "blur" || reason === "escape") {
setOpen(false);
}
}}
inputValue={inputValue}
onInputChange={(_, newValue) => {
handleInputChange(newValue);
}}
onChange={(_, newValue) => {
if (multiple && isAdorned) {
const hasSelectAllSelected =
Array.isArray(newValue) && newValue.some((item) => item.isSelectAll);
if (hasSelectAllSelected) {
handleSelectAll(!selectAll);
} else {
handleChange(newValue);
setSelectAll(Array.isArray(newValue) && newValue.length === options.length);
}
} else {
handleChange(newValue);
setOpen(false);
}
}}
fullWidth
freeSolo
disabled={disabled}
disableClearable
options={enhancedOptions}
getOptionLabel={(option) => option[filteredBy]}
isOptionEqualToValue={(option, value) =>
(option._id ?? option.id) === (value?._id ?? value?.id)
} // Compare by unique identifier
renderInput={(params) => (
<FieldWrapper
label={label}
labelMb={labelMb}
labelVariant={labelVariant}
labelFontWeight={labelFontWeight}
labelSx={labelSx}
gap={gap}
sx={{
...sx,
}}
>
<TextField
{...params}
error={Boolean(error)}
placeholder="Type to search"
slotProps={{
input: {
...params.InputProps,
...(isAdorned && { startAdornment: <SearchAdornment /> }),
...(startAdornment && { startAdornment: startAdornment }),
...(endAdornment && { endAdornment: endAdornment }),
},
}}
sx={{}}
/>
{error && (
<Typography
component="span"
className="input-error"
color={theme.palette.error.main}
mt={theme.spacing(2)}
sx={{
opacity: 0.8,
}}
>
{error}
</Typography>
)}
</FieldWrapper>
)}
filterOptions={(options, { inputValue }) => {
if (inputValue.trim() === "" && multiple && isAdorned) {
return enhancedOptions;
}
const filtered = options.filter((option) =>
option[filteredBy].toLowerCase().includes(inputValue.toLowerCase())
);
if (filtered.length === 0) {
return [
{
[filteredBy]: t("general.noOptionsFound", { unit: unit }),
noOptions: true,
},
];
}
return filtered;
}}
getOptionKey={(option) => {
return option._id ?? option.id;
}}
renderOption={(props, option) => {
const { key, ...optionProps } = props;
const hasSecondaryLabel = secondaryLabel && option[secondaryLabel] !== undefined;
const port = option["port"];
const selected = isOptionSelected(option);
return (
<ListItem
key={key}
{...optionProps}
sx={
option.noOptions
? {
pointerEvents: "none",
backgroundColor: theme.palette.primary.main,
}
: option.isSelectAll
? {
fontWeight: "bold",
backgroundColor: theme.palette.primary.light,
"&:hover": {
backgroundColor: theme.palette.primary.light,
},
}
: {}
}
>
{multiple && isAdorned && !option.noOptions && (
<Checkbox
checked={option.isSelectAll ? selectAll : selected}
sx={{
color: theme.palette.primary.contrastTextSecondary,
"&.Mui-checked": {
color: theme.palette.secondary.main,
},
padding: 0,
}}
/>
)}
{option[filteredBy] +
(hasSecondaryLabel
? ` (${option[secondaryLabel]}${port ? `: ${port}` : ""})`
: "")}
</ListItem>
);
}}
slotProps={{
popper: {
keepMounted: true,
sx: {
"& ul": { p: 2, backgroundColor: theme.palette.primary.main },
"& li.MuiAutocomplete-option": {
color: theme.palette.primary.contrastTextSecondary,
px: 4,
borderRadius: theme.shape.borderRadius,
},
"& .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true'], & .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true'].Mui-focused, & .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true']:hover":
{
backgroundColor: theme.palette.primary.lowContrast,
color: "red",
},
"& li.MuiAutocomplete-option:hover:not([aria-selected='true'])": {
color: theme.palette.secondary.contrastText,
backgroundColor: theme.palette.secondary.main,
},
"& .MuiAutocomplete-noOptions": {
px: theme.spacing(6),
py: theme.spacing(5),
},
},
},
}}
sx={{
/* height: 34,*/
"&.MuiAutocomplete-root .MuiAutocomplete-input": {
padding: `0 ${theme.spacing(5)}`,
},
...sx,
}}
/>
);
};
Search.propTypes = {
label: PropTypes.string,
id: PropTypes.string,
multiple: PropTypes.bool,
options: PropTypes.array.isRequired,
filteredBy: PropTypes.string.isRequired,
secondaryLabel: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
inputValue: PropTypes.string.isRequired,
handleInputChange: PropTypes.func.isRequired,
handleChange: PropTypes.func,
isAdorned: PropTypes.bool,
sx: PropTypes.object,
error: PropTypes.string,
disabled: PropTypes.bool,
startAdornment: PropTypes.object,
endAdornment: PropTypes.object,
onBlur: PropTypes.func,
unit: PropTypes.string,
};
export default Search;
@@ -1,7 +0,0 @@
.select-wrapper .select-component > .MuiSelect-select {
padding: 0 10px;
min-height: 34px;
display: flex;
align-items: center;
line-height: 1;
}
@@ -1,213 +0,0 @@
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { MenuItem, Select as MuiSelect, Stack, Typography } from "@mui/material";
import Icon from "../../Icon";
import FieldWrapper from "../FieldWrapper/index.jsx";
import "./index.css";
/**
* @component
* @param {object} props
* @param {string} props.id - The ID attribute for the select element.
* @param {string} props.placeholder - The label of the select element.
* @param {string} props.placeholder - The placeholder text when no option is selected.
* @param {boolean} props.isHidden - Whether the placeholder should be hidden.
* @param {(string | number | boolean)} props.value - The currently selected value.
* @param {object[]} props.items - The array of items to populate in the select dropdown.
* @param {(string | number | boolean)} props.items._id - The unique identifier of each item.
* @param {string} props.items.name - The display name of each item.
* @param {function} props.onChange - The function to handle onChange event.
* @param {object} props.sx - The custom styles object for MUI Select component.
* @param {number} props.maxWidth - Maximum width in pixels for the select component. Enables responsive text truncation.
* @returns {JSX.Element}
*
* @example
* const frequencies = [
* { _id: 1, name: "1 minute" },
* { _id: 2, name: "2 minutes" },
* { _id: 3, name: "3 minutes" },
* ];
*
* <Select
* id="frequency-id"
* name="my-name"
* label="Check frequency"
* placeholder="Select frequency"
* value={value}
* onChange={handleChange}
* items={frequencies}
* />
*/
const Select = ({
id,
label,
placeholder,
isHidden,
value,
items,
onChange,
onBlur,
sx,
error = false,
name = "",
labelControlSpacing = 6,
maxWidth,
//FieldWrapper's props
labelMb,
labelFontWeight,
labelVariant,
labelSx = {},
fieldWrapperSx = {},
}) => {
const theme = useTheme();
const getItemValue = (item) => item?._id ?? item?.id;
const itemStyles = {
color: theme.palette.primary.contrastTextTertiary,
borderRadius: theme.shape.borderRadius,
margin: theme.spacing(2),
};
const responsiveMaxWidth = {
xs: `${maxWidth * 0.5}px`,
sm: `${maxWidth * 0.75}px`,
md: `${maxWidth * 0.9}px`,
lg: `${maxWidth}px`,
};
return (
<FieldWrapper
label={label}
labelMb={labelMb}
labelVariant={labelVariant}
labelFontWeight={labelFontWeight}
labelSx={labelSx}
gap={labelControlSpacing}
sx={{
...fieldWrapperSx,
}}
>
<MuiSelect
className="select-component"
value={value}
onChange={onChange}
onBlur={onBlur}
displayEmpty
error={error}
name={name}
inputProps={{ id: id }}
IconComponent={(props) => (
<Icon
name="ChevronDown"
size={20}
{...props}
/>
)}
MenuProps={{ disableScrollLock: true }}
sx={{
fontSize: 13,
minWidth: "125px",
...(maxWidth && { maxWidth: responsiveMaxWidth }),
"& fieldset": {
borderRadius: theme.shape.borderRadius,
borderColor: theme.palette.primary.lowContrast,
},
"&:not(.Mui-focused):hover fieldset": {
borderColor: theme.palette.primary.lowContrast,
},
"& svg path": {
fill: theme.palette.primary.contrastTextTertiary,
},
"& .MuiSelect-select": {
padding: "0",
minHeight: "34px",
display: "flex",
alignItems: "center",
lineHeight: 1,
},
...sx,
}}
renderValue={(selected) => {
const selectedItem = items.find((item) => getItemValue(item) === selected);
const displayName = selectedItem ? selectedItem.name : placeholder;
return (
<Typography
sx={{
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
title={displayName}
>
{displayName}
</Typography>
);
}}
>
{placeholder && (
<MenuItem
className="select-placeholder"
value="0"
sx={{
display: isHidden ? "none" : "flex",
visibility: isHidden ? "none" : "visible",
...itemStyles,
}}
>
{placeholder}
</MenuItem>
)}
{items
.map((item) => {
const itemValue = getItemValue(item);
if (itemValue === undefined || itemValue === null) {
return null;
}
return (
<MenuItem
value={itemValue}
key={`${id}-${itemValue}`}
sx={{
...itemStyles,
}}
>
{item.name}
</MenuItem>
);
})
.filter(Boolean)}
</MuiSelect>
</FieldWrapper>
);
};
Select.propTypes = {
id: PropTypes.string,
name: PropTypes.string,
label: PropTypes.string,
placeholder: PropTypes.string,
isHidden: PropTypes.bool,
error: PropTypes.bool,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool])
.isRequired,
items: PropTypes.arrayOf(
PropTypes.shape({
_id: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]),
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]),
name: PropTypes.string.isRequired,
})
).isRequired,
onChange: PropTypes.func.isRequired,
onBlur: PropTypes.func,
sx: PropTypes.object,
labelControlSpacing: PropTypes.number,
/**
* Maximum width in pixels. Used to control text truncation and element width.
* Responsive breakpoints will be calculated as percentages of this value.
*/
maxWidth: PropTypes.number,
};
export default Select;
@@ -1,74 +0,0 @@
import { Stack, Typography, InputAdornment, IconButton } from "@mui/material";
import { useTheme } from "@mui/material/styles";
import PropTypes from "prop-types";
import Icon from "../../../Icon";
export const HttpAdornment = ({ https }) => {
const theme = useTheme();
return (
<Stack
direction="row"
alignItems="center"
height="100%"
sx={{
borderRight: `solid 1px ${theme.palette.primary.lowContrast}`,
backgroundColor: theme.palette.tertiary.main,
pl: theme.spacing(6),
}}
>
<Typography
component="h5"
color={theme.palette.primary.contrastTextSecondary}
sx={{ lineHeight: 1, opacity: 0.8 }}
>
{https ? "https" : "http"}
</Typography>
</Stack>
);
};
HttpAdornment.propTypes = {
https: PropTypes.bool.isRequired,
prefix: PropTypes.string,
};
export const PasswordEndAdornment = ({ fieldType, setFieldType }) => {
const theme = useTheme();
return (
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={() => setFieldType(fieldType === "password" ? "text" : "password")}
sx={{
color: theme.palette.primary.lowContrast,
padding: theme.spacing(1),
"&:focus-visible": {
outline: `2px solid ${theme.palette.primary.main}`,
outlineOffset: `2px`,
},
"& .MuiTouchRipple-root": {
pointerEvents: "none",
display: "none",
},
}}
>
{fieldType === "password" ? (
<Icon
name="EyeOff"
size={20}
/>
) : (
<Icon
name="Eye"
size={20}
/>
)}
</IconButton>
</InputAdornment>
);
};
PasswordEndAdornment.propTypes = {
fieldType: PropTypes.string,
setFieldType: PropTypes.func,
};
Loaded 100 of 336 files, more files were not shown because too many files have changed in this diff. Show more