Compare commits

..
Author SHA1 Message Date
Alexander Holliday 1244ff6d93 Merge pull request #3212 from bluewave-labs/develop
develop -> master
2026-01-27 13:58:48 -08:00
Alexander Holliday 53f420cf56 Merge pull request #3211 from bluewave-labs/fix/string-cast
cast to string
2026-01-27 13:58:11 -08:00
Alex Holliday 589825c1c5 format 2026-01-27 21:57:55 +00:00
Alex Holliday 6efc05879a cast to string 2026-01-27 21:54:20 +00:00
Alexander Holliday c0ea804c98 Merge pull request #3209 from bluewave-labs/develop
develop -> master
2026-01-27 13:40:25 -08:00
Alexander Holliday 20dccb140e Merge pull request #3208 from bluewave-labs/fix/docker
docker fix
2026-01-27 13:39:44 -08:00
Alex Holliday 8dfedb8a52 docker fix 2026-01-27 21:26:27 +00:00
Alexander Holliday c21cf29db6 Merge pull request #3207 from bluewave-labs/develop
develop -> master
2026-01-27 13:20:18 -08:00
Alexander Holliday d48cdaca9c Merge pull request #3205 from bluewave-labs/fix/types
fix types
2026-01-27 13:19:13 -08:00
Alex Holliday 36a53ee80b fix types 2026-01-27 21:17:57 +00:00
Alexander Holliday 3d0da70ce6 Merge pull request #3204 from bluewave-labs/develop
develop -> master
2026-01-27 13:09:47 -08:00
Alexander Holliday 4400ca703d Merge pull request #3202 from bluewave-labs/fix/dockerfile
remove console.log, fix docker file
2026-01-27 13:01:52 -08:00
Alex Holliday c1d0cb568d remove console.log, fix docker file 2026-01-27 21:00:28 +00:00
Alexander Holliday 81e73e5adf Merge pull request #3200 from bluewave-labs/fix/checks
fix/checks
2026-01-27 12:52:20 -08:00
Alex Holliday 7a84d06a79 query tweak 2026-01-27 20:49:44 +00:00
Alex Holliday 63d2bb97ee add dateRange to checks query 2026-01-27 20:46:28 +00:00
Alex Holliday 0af7cfe714 generate checks 2026-01-27 20:04:47 +00:00
Alex Holliday d190f5ab02 types 2026-01-27 19:55:52 +00:00
Alexander Holliday 8cfe1d3c1a Merge pull request #3199 from bluewave-labs/feat/recent-checks
feat: recent checks
2026-01-27 11:54:39 -08:00
Alex Holliday 1bf4c75418 format 2026-01-27 19:52:44 +00:00
Alex Holliday 7771a0ce35 resolve errors 2026-01-27 19:50:32 +00:00
Alex Holliday fcd9e78367 infra migration 2026-01-27 19:39:17 +00:00
Alex Holliday 6f9fefddd2 recent checks 2026-01-27 19:24:02 +00:00
Alex Holliday 5731283c46 recent checks 2026-01-27 19:23:34 +00:00
Alex Holliday 420ff292d9 toEntity 2026-01-27 18:58:08 +00:00
Alex Holliday 290ee7d2df proper parsing 2026-01-27 18:50:08 +00:00
Alex Holliday 50c644231e initial commit 2026-01-27 18:26:31 +00:00
Alexander Holliday 5729b0a263 Merge pull request #3197 from bluewave-labs/fix/checks
date limit
2026-01-26 20:07:32 -08:00
Alex Holliday 4f3f5408ef date limit 2026-01-27 04:06:52 +00:00
Alexander Holliday de7038d6c3 Merge pull request #3195 from bluewave-labs/fix/checks
format
2026-01-26 19:40:26 -08:00
Alex Holliday 4119189c71 format 2026-01-27 03:39:55 +00:00
Alexander Holliday fd66affea8 Merge pull request #3193 from ajhollid/fix/dockerfile
fix: dist dir
2026-01-26 15:58:07 -08:00
Alex Holliday 839dc201df dist 2026-01-26 15:57:23 -08:00
Alexander Holliday bbb0716251 Merge pull request #3191 from bluewave-labs/fix/pagespeed-tooltip
fix tooltip styling
2026-01-26 15:46:50 -08:00
Alex Holliday bbdfc00eb9 format 2026-01-26 23:45:28 +00:00
Alexander Holliday ba498534e6 Merge pull request #3190 from bluewave-labs/fix/migration
fix: migration
2026-01-26 15:44:50 -08:00
Alex Holliday f51bf36ddd fix tooltip styling 2026-01-26 23:44:40 +00:00
Alex Holliday 716479c5bb format 2026-01-26 23:21:49 +00:00
Alex Holliday f9c85e2d76 indexes 2026-01-26 23:20:18 +00:00
Alex Holliday e26f3facf1 use creatdAt 2026-01-26 23:15:59 +00:00
Alex Holliday fd5f493345 improve migration 2026-01-26 23:02:30 +00:00
Alexander Holliday e5bb736530 Merge pull request #3189 from bluewave-labs/feat/pagespeed-details
feat: v2 pagespeed details
2026-01-26 14:34:56 -08:00
Alex Holliday d71d16ecd0 refresh interval 2026-01-26 22:32:08 +00:00
Alex Holliday 389716fa77 pretty-ms 2026-01-26 22:29:28 +00:00
Alex Holliday 1bcb2ddf91 pagespeed v2 refactor 2026-01-26 22:28:02 +00:00
Alex Holliday b61077bd3f remove unsued strings, initial commit 2026-01-26 21:14:04 +00:00
Alexander Holliday 1cffd51bd3 Merge pull request #3188 from bluewave-labs/feat/v2-pagespeed
feat/v2 pagespeed
2026-01-26 12:50:30 -08:00
Alex Holliday 451e352458 types 2026-01-26 20:47:29 +00:00
Alex Holliday a07e41d8f1 format 2026-01-26 20:47:24 +00:00
Alex Holliday 90e9b41973 pagespeed key alert 2026-01-26 20:47:10 +00:00
Alex Holliday 1819df5117 priority fallback 2026-01-26 20:27:11 +00:00
Alex Holliday 6380dcc6ba summary 2026-01-26 20:05:11 +00:00
Alex Holliday 6cb45ea6de delete pagepseed 2026-01-26 19:36:29 +00:00
Alex Holliday 67f74dd7de fix: prevent horizontal scrollbar in table by adding overflowX hidden and making histogram responsive 2026-01-26 18:28:55 +00:00
Alex Holliday 8f58ceafab fix: add overflowX hidden to table cells to prevent horizontal scrollbar 2026-01-26 18:27:12 +00:00
Alex Holliday c7aa45cd77 initial commit 2026-01-26 18:24:58 +00:00
Alexander Holliday d0727f91dd Merge pull request #3186 from bluewave-labs/feat/v2-uptime-details
feat/v2 uptime details
2026-01-26 09:16:12 -08:00
Alex Holliday 967b2b200b build errors 2026-01-26 17:15:08 +00:00
Alex Holliday d71c7c6ee1 format 2026-01-26 17:11:09 +00:00
Alexander Holliday 6daa79f0ed Merge pull request #3187 from bluewave-labs/maintenance-canlendar-dark-theme
fix maintenance datepicker theme color
2026-01-26 09:07:50 -08:00
karenvicent 93a5eff562 fix maintenance datepicker theme color 2026-01-26 11:40:44 -05:00
Alex Holliday 11c95f885d bucket date 2026-01-26 03:19:33 +00:00
Alex Holliday 174665e70a remove unused 2026-01-26 02:55:52 +00:00
Alex Holliday 64de01437e remove toast styling 2026-01-26 02:48:56 +00:00
Alex Holliday a64885e609 checks table 2026-01-26 02:37:59 +00:00
Alex Holliday e354fa7ebf response time chart 2026-01-26 02:06:37 +00:00
Alex Holliday 3208cc9e2c strings and icons 2026-01-25 16:48:56 +00:00
Alex Holliday 79dfa886f1 upchart 2026-01-25 16:31:54 +00:00
Alex Holliday 8af8efbe3b toggle-button-group 2026-01-25 16:09:48 +00:00
Alex Holliday 71bd1fa28f time range header 2026-01-25 15:56:15 +00:00
Alex Holliday a2569c50c7 stat boxes 2026-01-25 04:09:40 +00:00
Alex Holliday f7ae11997d monitor controls header 2026-01-25 03:20:58 +00:00
Alexander Holliday 0b7a41dc78 Merge pull request #3184 from bluewave-labs/fix/password-tooltip-theme-contrast
Fix password tooltip theme contrast issues
2026-01-24 18:12:24 -08:00
Alex Holliday d42d5f2325 initial commit 2026-01-25 02:09:33 +00:00
gorkem-bwl 369d16c7c3 fix: resolve password tooltip theme contrast and translation issues
- Fix tooltip background using non-existent tertiary.background
  property by switching to tertiary.main
- Fix Check component text color inheritance for noHighlightText
  by adding color: inherit to inner Typography
- Add translation wrapper for validation error messages in PasswordPanel
- Add translation wrapper for validation error messages in ProfilePanel
2026-01-24 19:52:06 -05:00
Alexander Holliday 6e8172c126 Merge pull request #3183 from bluewave-labs/feat/v2/uptime
feat: v2-uptime
2026-01-24 12:43:09 -08:00
Alex Holliday 794018a6ab simplify breadcrumbs 2026-01-24 20:41:21 +00:00
Alex Holliday 8d6bc7bc0b udpate API client 2026-01-24 20:26:05 +00:00
Alex Holliday 30631d6ff8 fix setRowsPerPage 2026-01-24 16:41:40 +00:00
Alex Holliday 56c5ad419a format settings 2026-01-24 16:20:09 +00:00
Alex Holliday b852293447 header create 2026-01-24 16:19:43 +00:00
Alex Holliday d877ecf061 Merge branch 'feat/v2/uptime' of https://github.com/bluewave-labs/Checkmate into feat/v2/uptime 2026-01-24 16:19:15 +00:00
Alexander Holliday 73e90bfab6 Merge pull request #3182 from bluewave-labs/fix/monitor-utils-naming-conflict
fix: resolve monitorUtils naming conflict on case-insensitive filesystems
2026-01-24 08:10:20 -08:00
gorkem-bwl a55163de56 fix: resolve monitorUtils naming conflict on case-insensitive filesystems
## Changes
- Rename monitorUtils.js to monitorUtilsLegacy.js to avoid conflict with MonitorUtils.ts
- Update imports in IncidentActionsMenu, JobTable, FailedJobTable, and PageSpeed/Create
2026-01-24 11:03:47 -05:00
Alex Holliday f5c6abad1b header create 2026-01-24 16:01:32 +00:00
Alex Holliday 3dbbe035a8 stacking fields 2026-01-24 01:08:44 +00:00
Alex Holliday 9904626526 chart type option 2026-01-24 00:51:13 +00:00
Alex Holliday 61f3aa1d1c reenable sorting 2026-01-24 00:29:19 +00:00
Alex Holliday a0a8ceb0db uptime 2026-01-23 23:54:58 +00:00
Alex Holliday c2dece862c controls 2026-01-23 21:30:34 +00:00
Alex Holliday 69018a4944 components 2026-01-23 12:33:40 -08:00
Alex Holliday 363d89e4f4 initial commit 2026-01-23 19:58:37 +00:00
Alexander Holliday f8bb01190e Merge pull request #3179 from bluewave-labs/feat/swr-hook
add SWR hook and API client
2026-01-23 10:18:09 -08:00
Alex Holliday 00cf0b6521 fix get return type 2026-01-23 18:14:16 +00:00
Alex Holliday a11d551a99 add SWR hook and API client 2026-01-23 17:38:30 +00:00
Alexander Holliday b35be1ecfc Merge pull request #3178 from bluewave-labs/fix/incident-details
remove extra network request
2026-01-22 14:08:09 -08:00
Alexander Holliday 9d64de57b3 Merge pull request #3177 from bluewave-labs/fix/datepicker-dark-mode-contrast
fix: DatePicker calendar popup contrast in dark mode
2026-01-22 14:06:26 -08:00
Alex Holliday de6c4d9c3e remove extra network request 2026-01-22 22:05:18 +00:00
gorkem-bwl e0e79fb0da fix: DatePicker calendar popup contrast in dark mode
## Changes
- Add global theme overrides for MUI DatePicker components (MuiPickersPopper,
  MuiDateCalendar, MuiPickersCalendarHeader, MuiDayCalendar, MuiPickersDay)
- Remove inline popper styling from CreateMaintenance component as it's now
  handled globally
- Calendar popup now properly adapts to both light and dark themes using
  theme palette tokens
2026-01-22 17:05:06 -05:00
Alexander Holliday 3339b6b67f Merge pull request #3176 from bluewave-labs/fix/purge-js
remove final js
2026-01-22 13:51:59 -08:00
Alex Holliday b511cde849 remove final js 2026-01-22 21:49:49 +00:00
Alexander Holliday ce322a8e28 Merge pull request #3175 from bluewave-labs/fix/incident-details
fix: purge incident module
2026-01-22 13:30:42 -08:00
Alex Holliday d25ca473de purge incident module 2026-01-22 21:28:28 +00:00
Alexander Holliday 5292ca8988 Merge pull request #3174 from bluewave-labs/feat/status-page-service-and-repo
feat/status page service and repo
2026-01-22 13:04:30 -08:00
Alex Holliday c98cba2eef shutdown -> ts 2026-01-22 21:02:34 +00:00
Alex Holliday 6b89db131d remove statuspagemodule 2026-01-22 21:01:45 +00:00
Alex Holliday 995ae5252d format 2026-01-22 20:57:45 +00:00
Alex Holliday e6c268f14e status page service/repo refactor 2026-01-22 20:57:20 +00:00
Alexander Holliday 5aef405414 Merge pull request #3173 from bluewave-labs/fix-uptime-monitor-redirection-incident
Fix uptime monitor redirection incident
2026-01-22 12:30:05 -08:00
karenvicent a5112a7955 Merge branch 'develop' into fix-uptime-monitor-redirection-incident 2026-01-22 15:09:29 -05:00
karenvicent 5404b65ce2 Fixed the navigation from an uptime monitor into Incidents 2026-01-22 15:08:24 -05:00
Alexander Holliday 34dfe33c29 Merge pull request #3170 from bluewave-labs/fix/light-dark-mode-theme
fix: sidebar and body background not adapting to light mode
2026-01-22 11:58:13 -08:00
Alexander Holliday 1e24c8841e Merge pull request #3172 from bluewave-labs/fix/fe/incident-detail-resolved
Fix: Incident Detail UI Layout
2026-01-22 11:52:46 -08:00
mannilakash 0c04c06781 fix: sidebar border color changes based on theme (primary->lowContrast color) 2026-01-22 14:48:11 -05:00
Br0wnHammer 7441169a0e Fix: Incident Detail UI Layout 2026-01-23 00:47:57 +05:30
Alexander Holliday 3520e5adf4 Merge pull request #3171 from bluewave-labs/fix/fe/incident-page-ui
Fix: Incidents Page Detail
2026-01-22 11:04:42 -08:00
Br0wnHammer 227e0128f9 Fix: Incidents Page Detail 2026-01-23 00:31:24 +05:30
Alexander Holliday 2359255ef7 Merge pull request #3169 from bluewave-labs/feat/maintenance-window-repository
feat/maintenance window repository
2026-01-22 10:14:22 -08:00
Alex Holliday b074f2401f format 2026-01-22 18:12:56 +00:00
Alex Holliday eee3caf021 remove old db module 2026-01-22 18:11:54 +00:00
Alex Holliday f9848e4e1e update fe 2026-01-22 17:53:54 +00:00
Alex Holliday 8bd608bee0 implement remaining methods 2026-01-22 17:44:03 +00:00
gorkem-bwl ea589d6be4 fix: sidebar and body background not adapting to light mode
## Changes

### Sidebar
- Remove hardcoded `backgroundColor: "#000000"`
- Use `theme.palette.background.main` for dynamic theming
- Update border color to use `theme.palette.border.light`

### Body Background
- Remove hardcoded `background-color: #000000` from index.css
- Add GlobalStyles in App.jsx to apply theme background to body element

## Benefits
- Sidebar now properly displays in light mode
- Body background adapts correctly to theme changes
- Consistent theming across light and dark modes
2026-01-22 12:27:18 -05:00
Alexander Holliday 5a0078cf92 Merge pull request #3167 from bluewave-labs/incidents-feat-modal
Incidents action button and detail modal
2026-01-22 09:06:53 -08:00
karenvicent f4a5bd65ad Merge branch 'develop' into incidents-feat-modal 2026-01-22 11:11:02 -05:00
karenvicent 4766594413 fix function duplicated and log 2026-01-22 10:58:47 -05:00
Alex Holliday 9962866ba5 refactor 2026-01-21 23:26:13 +00:00
Alex Holliday 82cc289f53 mw type 2026-01-21 14:26:17 -08:00
Alex Holliday 117c22fae4 initial commit 2026-01-21 14:22:53 -08:00
Alexander Holliday e0cabdb4fc Merge pull request #3168 from bluewave-labs/fix/notifications-service
fix: refactor notifications service
2026-01-21 13:56:43 -08:00
Alex Holliday 155a2577d0 remove unused dep 2026-01-21 13:55:22 -08:00
Alex Holliday d627a25b40 refactor notifications service 2026-01-21 13:51:04 -08:00
karenvicent b8a5af9c10 delete detail incident page 2026-01-21 16:12:42 -05:00
Alexander Holliday 5b43d9d173 Merge pull request #3166 from ajhollid/fix/db-modules
fix/db modules
2026-01-21 13:03:37 -08:00
Alex Holliday 801b716a03 parse settings 2026-01-21 13:01:43 -08:00
Alex Holliday fdc3983328 remove settingsModule 2026-01-21 13:01:31 -08:00
karenvicent 0cf85f8b3b add traslation 2026-01-21 15:50:46 -05:00
karenvicent 0ea4a5024a Merge branch 'develop' into incidents-feat-modal 2026-01-21 15:35:22 -05:00
karenvicent 09513696ee detail modal fixes and filters options 2026-01-21 15:33:00 -05:00
Alexander Holliday b0da1f4259 Merge pull request #3165 from ajhollid/fix/team-ts
fix/team ts
2026-01-21 11:57:21 -08:00
Alex Holliday 5b96075c03 build errs 2026-01-21 11:56:05 -08:00
Alex Holliday 36e02e0a9a team -> ts 2026-01-21 11:46:49 -08:00
Alex Holliday 9fa0113f2b remove database.js 2026-01-21 11:21:00 -08:00
Alex Holliday 371e8cc154 purge translationService 2026-01-21 11:20:41 -08:00
Alexander Holliday 7b4286646b Merge pull request #3163 from ajhollid/fix/app
fix: app -> ts
2026-01-21 11:20:03 -08:00
Alex Holliday 2a8d2cb06a app -> ts 2026-01-21 11:18:13 -08:00
Alexander Holliday 147eff3ede Merge pull request #3162 from ajhollid/fix/routes
fix/routes
2026-01-21 10:37:21 -08:00
Alex Holliday 81c7c992e2 routes -> ts 2026-01-21 10:36:03 -08:00
Alex Holliday 35488fed3e Merge branch 'develop' into fix/routes 2026-01-21 10:18:55 -08:00
Alexander Holliday 760abe69ed Merge pull request #3161 from bluewave-labs/fix/cleanup
Fix/cleanup
2026-01-21 10:18:44 -08:00
Alex Holliday d242382abf format 2026-01-21 10:16:17 -08:00
Alex Holliday ef61a63da0 parse query 2026-01-21 10:15:13 -08:00
Alex Holliday 3fa370765e purge papaparse 2026-01-21 09:52:31 -08:00
Alexander Holliday 3b5e793f8f Merge pull request #3156 from bluewave-labs/fix/monitor/pause-button
Fix: Configure Pause Button
2026-01-21 09:20:53 -08:00
Br0wnHammer b34dbec3cf Fix: updateTrigger val 2026-01-21 18:47:38 +05:30
karenvicent 90eee474d2 Merge branch 'develop' into incidents-feat-modal 2026-01-20 21:03:07 -05:00
karenvicent 63416ceb4b fix import routes 2026-01-20 20:58:35 -05:00
karenvicent eb8c9bea76 Merge branch 'develop' into incidents-feat-modal 2026-01-20 20:46:02 -05:00
Alex Holliday 63b4779961 monitorSerivce return types 2026-01-21 00:01:38 +00:00
Alexander Holliday d2040495d6 Merge pull request #3160 from bluewave-labs/fix/remove-service-registry
fix/remove service registry
2026-01-20 15:10:20 -08:00
Alex Holliday f436e15569 fix dep 2026-01-20 23:10:08 +00:00
Alex Holliday 952c70fca4 remove v1 dir 2026-01-20 23:07:31 +00:00
Alex Holliday 4957209307 purge string service 2026-01-20 23:06:23 +00:00
Alex Holliday cc9850c975 logger->ts 2026-01-20 22:56:14 +00:00
Alex Holliday c6ac48b4ff rateLimiter -> ts, remove resonse handler 2026-01-20 22:50:34 +00:00
karenvicent 82584d85c7 detail modal 2026-01-20 17:49:54 -05:00
Alex Holliday ffd67150eb sanitization ts 2026-01-20 22:47:06 +00:00
Alex Holliday 44079f2eb8 remove service regsitry, remove unused middleware 2026-01-20 22:40:06 +00:00
Alexander Holliday f03ded4109 Merge pull request #3159 from bluewave-labs/feat/email-service-ts
feat: email-service js->ts
2026-01-20 14:08:32 -08:00
Alex Holliday fa520e5f88 js->ts 2026-01-20 22:06:55 +00:00
Alexander Holliday e9fbb472f4 Merge pull request #3158 from bluewave-labs/feat/diagnostic-service-ts
feat: diagnostic-service js->ts
2026-01-20 13:55:13 -08:00
Alex Holliday de236f03eb js->ts 2026-01-20 21:53:19 +00:00
Alexander Holliday 4f043fe1d4 Merge pull request #3157 from bluewave-labs/feat-checks-repository
Feat checks repository
2026-01-20 13:36:09 -08:00
Alex Holliday 102bd0a5f8 format 2026-01-20 21:34:40 +00:00
Alex Holliday 118372630e return types 2026-01-20 21:31:53 +00:00
Alex Holliday 27dea6a08e implement remaining check methods 2026-01-20 21:18:26 +00:00
Br0wnHammer dd259fea89 Fix: One source of truth 2026-01-21 02:02:16 +05:30
Alex Holliday 9af25a19e5 findByMonitorId 2026-01-20 20:14:23 +00:00
Br0wnHammer 6101fe1bfa Fix: Configure Pause Button 2026-01-21 01:42:00 +05:30
Alexander Holliday 795146c79b Merge pull request #3155 from bluewave-labs/fix/notifications
fix: replaced monitor with updatedMonitor
2026-01-20 11:51:29 -08:00
Alex Holliday a69091bb28 replaced monitor with updatedMonitor 2026-01-20 19:49:40 +00:00
Alexander Holliday 6160806963 Merge pull request #3154 from bluewave-labs/fix/checks
fix/checks
2026-01-20 11:47:45 -08:00
Alex Holliday c1b2bda623 _id -> id 2026-01-20 19:41:57 +00:00
Alex Holliday c0e4893f96 remove ack service and db methods 2026-01-20 19:26:46 +00:00
Alex Holliday 476ff39a17 remove check ack routes and controller functions 2026-01-20 19:25:00 +00:00
Alexander Holliday f467ef1411 Merge pull request #3153 from bluewave-labs/fix/incidents
fix/incidents
2026-01-20 11:24:21 -08:00
Alex Holliday 36b02e5e66 Merge branch 'fix/incidents' into fix/checks 2026-01-20 19:22:18 +00:00
Alex Holliday f6fd8729c3 remove dependency 2026-01-20 19:22:04 +00:00
Alex Holliday cdfc87da09 remove ack 2026-01-20 19:21:03 +00:00
Alex Holliday 427e5a18ea incidents -> checks 2026-01-20 19:12:05 +00:00
Alex Holliday f696b4ef9b Merge branch 'develop' into fix/incidents 2026-01-20 19:08:03 +00:00
Alex Holliday 69d050ff61 remove incidents from buffer 2026-01-20 19:04:30 +00:00
Alex Holliday 7afdcb8ebc fix query param 2026-01-20 19:01:50 +00:00
Alex Holliday 3cf7243c93 summary 2026-01-20 18:56:06 +00:00
Alexander Holliday 4ed0033a41 Merge pull request #3136 from bluewave-labs/fix/various-ui-ux-fixes
fix: various UI/UX improvements for infrastructure pages
2026-01-20 09:18:30 -08:00
Alexander Holliday bafd82a691 Merge pull request #3147 from bluewave-labs/fix/sidebar-scroll-gap
Fix sidebar gap when scrolling on pages with tall content
2026-01-20 09:17:24 -08:00
Alex Holliday adb2a383cf refactor 2026-01-20 00:02:27 +00:00
Alex Holliday e91a54b75c initial commit 2026-01-19 23:41:24 +00:00
Alexander Holliday 33dfedb4d3 Merge pull request #3152 from bluewave-labs/feat/notifications-refactor
feat/notifications refactor
2026-01-19 14:20:36 -08:00
Alex Holliday dde54a4e45 remove old service 2026-01-19 22:18:17 +00:00
Alex Holliday 5f17520ba0 logging 2026-01-19 22:16:33 +00:00
Alex Holliday bebb5d6942 inject logger 2026-01-19 21:58:48 +00:00
Alex Holliday 7c9410f9d7 test alerts 2026-01-19 21:39:02 +00:00
Alexander Holliday 348ca0dd02 Merge pull request #3151 from bluewave-labs/docs/update-openapi-specs
docs: update OpenAPI specification to match current API
2026-01-19 12:19:16 -08:00
Alex Holliday 1d36f40caf slack 2026-01-19 20:12:54 +00:00
Alex Holliday 9006707e57 discord 2026-01-19 20:03:20 +00:00
Alex Holliday be82a7ee2d discord 2026-01-19 19:58:15 +00:00
Alex Holliday fd33b903ed email provider 2026-01-19 19:30:46 +00:00
gorkem-bwl ab25531758 docs: fix OpenAPI spec accuracy and warnings
## Changes

### Route Corrections
- Remove non-existent `/auth/refresh` endpoint
- Add missing `/auth/users/{userId}/password` PUT endpoint
- Fix `/notifications` GET -> `/notifications/team` GET (correct path)

### OpenAPI 3.1.0 Compliance
- Fix `nullable` warnings by using `type: ["string", "null"]` syntax
- Updated Incident schema nullable fields (endTime, message, statusCode, etc.)
- Updated QueueJob schema nullable fields (processedOn, finishedOn)
- Updated Check schema nullable field (ackAt)

### Verified Against Codebase
- Audited all 76 API endpoints across 12 route files
- Confirmed DELETE /checks/team and DELETE /checks/{monitorId} exist
- All routes now match actual codebase implementation

## Validation
- Passes `swagger-cli validate`
- Only remaining warnings are cosmetic `example` deprecations
2026-01-19 14:13:18 -05:00
Alex Holliday f7f764fd68 providers 2026-01-19 18:58:17 +00:00
gorkem-bwl 3b0326457f docs: update OpenAPI specification to match current API
## Changes

### New Endpoints Added
- `/incidents/team` - Get incidents by team with filtering
- `/incidents/team/summary` - Get incident summary statistics
- `/incidents/{incidentId}` - Get incident by ID
- `/incidents/{incidentId}/resolve` - Manually resolve incident
- `/queue/jobs` (GET) - List queue jobs (admin only)
- `/queue/jobs` (POST) - Add job to queue (admin only)
- `/queue/metrics` - Get queue metrics (admin only)
- `/monitors/team/groups` - Get monitor groups by team
- `/monitors/pagespeed/details/{monitorId}` - Get PageSpeed details
- `/checks/{action}` - Acknowledge all checks for all monitors
- `/checks/{action}/{monitorId}` - Acknowledge checks for specific monitor

### Endpoints Fixed
- `/monitors/export` changed to `/monitors/export/json` (matches actual route)
- Removed non-existent `/monitors/stats/{monitorId}` endpoint
- Removed non-existent `/monitors/team/summary` endpoint
- Split optional path parameter into two valid endpoints for checks

### New Schemas Added
- `Incident` - Complete incident model
- `IncidentSummary` - Incident statistics summary
- `QueueJob` - Queue job information
- `QueueMetrics` - Queue metrics data
- `Check` - Complete check result model
- `CheckTimings` - HTTP timing information
- `CheckCpuInfo` - CPU metrics for infrastructure checks
- `CheckMemoryInfo` - Memory metrics for infrastructure checks
- `CheckDiskInfo` - Disk metrics for infrastructure checks
- `CheckHostInfo` - Host information for infrastructure checks
- `CheckErrorInfo` - Error information from checks
- `CheckCaptureInfo` - Capture agent information
- `CheckNetworkInfo` - Network interface metrics
- `CheckAudits` - PageSpeed audit results
- `LighthouseAudit` - Individual Lighthouse audit result

### Responses Added
- `BadRequest` - 400 error response

### Other Fixes
- Simplified server variables to fix OpenAPI 3.1.0 validation
- Added request body to `/checks/check/{checkId}` endpoint
- Added "incidents" tag to tags array

## Benefits
- OpenAPI specification now accurately reflects the actual API
- All endpoints have proper request/response schemas
- Validation passes with swagger-cli
- Better API documentation for consumers
2026-01-19 13:49:08 -05:00
Alex Holliday 7d6bc4b2dc notificaitons provider 2026-01-19 00:48:20 +00:00
Alex Holliday c2487f0134 alerts 2026-01-18 23:54:42 +00:00
Alex Holliday a86588f080 refactoring notifications 2026-01-18 23:38:40 +00:00
Alex Holliday 4e9839e2e5 notificationsService 2026-01-18 22:52:59 +00:00
gorkem-bwl 487f6b222d refactor: extract sidebar width logic into useSidebar hook
Create shared useSidebar hook to centralize sidebar width and transition
logic, addressing code review feedback about duplicated logic between
Sidebar and HomeLayout components.

Changes:
- Add useSidebar hook with collapsed state, width, and transition values
- Update Sidebar to use the hook instead of inline selectors and values
- Update HomeLayout to use the hook for spacer element styling

This ensures sidebar width logic is defined in one place and stays in sync.
2026-01-18 14:11:05 -05:00
Alex Holliday 391f42ee53 add webhook provider and utils 2026-01-18 16:59:38 +00:00
Alex Holliday cff5508614 notification provider 2026-01-18 16:34:20 +00:00
Alex Holliday a0ebf7d224 initial commit 2026-01-18 06:02:07 +00:00
Alexander Holliday ec9c79fc94 Merge pull request #3150 from bluewave-labs/feat/invite-repo
feat: invite-service to TS
2026-01-17 21:43:11 -08:00
Alex Holliday 3e8d4cb49b convert to ts 2026-01-18 05:38:50 +00:00
Alexander Holliday 288ac08cd6 Merge pull request #3149 from bluewave-labs/feat/user-service-ts
feat/user service ts
2026-01-17 11:09:29 -08:00
Alex Holliday ba35230919 remove db references from userService 2026-01-17 19:08:12 +00:00
Alex Holliday 048e53ea15 setting service 2026-01-17 18:29:42 +00:00
Alex Holliday e498bd60b1 team panel fix 2026-01-17 05:41:52 +00:00
gorkem-bwl a4cf53ee15 Set html/body background to black and disable overscroll bounce
- Added background-color: #000000 to prevent white flash on overscroll
- Added overscroll-behavior: none to disable elastic bounce effect
2026-01-16 22:59:25 -05:00
gorkem-bwl 3a77c6d7fa Fix sidebar gap when scrolling on pages with tall content
The sidebar was showing a visible gap at the bottom when scrolling on
pages with tall tables (e.g., /checks). This occurred because the
parent container's overflow-x: hidden was breaking sticky positioning.

Changes:
- Changed sidebar from position: sticky to position: fixed
- Added a spacer Box element to maintain layout spacing
- Moved border and background styles to the sidebar component
- Cleaned up unused CSS selectors
2026-01-16 22:26:43 -05:00
Alex Holliday b47f3710fe refactor user-service 2026-01-16 22:41:12 +00:00
Alex Holliday 9c92542653 recovery 2026-01-16 22:03:35 +00:00
Alexander Holliday 173a4e17d2 Merge pull request #3138 from bluewave-labs/fix/password-special-chars
fix: remove <> from allowed password characters and display allowed special chars
2026-01-16 13:17:12 -08:00
Alexander Holliday 70b197675e Merge pull request #3146 from bluewave-labs/fix/pagespeed-snapshot
remove pagespeed from snapshot array, full check list required
2026-01-16 13:16:37 -08:00
Alex Holliday 11dcccc085 remove pagespeed from snapshot array, full check list required 2026-01-16 21:16:05 +00:00
Alexander Holliday e3e08da259 Merge pull request #3145 from bluewave-labs/fix/pagespeed-stats
fix: stats fixes
2026-01-16 13:06:49 -08:00
Alex Holliday 6447c315ce stats fixes 2026-01-16 21:04:41 +00:00
Alex Holliday cb5b8e0673 update user refactor 2026-01-16 20:29:54 +00:00
Gorkem Cetin (BWL) f1a6afb860 Merge pull request #3133 from bluewave-labs/fix/ui-improvements-logs-incidents
Improve UI consistency for Logs and Incidents pages
2026-01-16 14:47:21 -05:00
gorkem-bwl 5c52a208b9 Fix build errors and formatting
- Replace CheckIcon SVG import with Lucide Icon component in
  LatestIncidentsPanel/index.jsx
- Format IncidentItem.jsx and StatisticsPanel.jsx with Prettier
2026-01-16 14:44:28 -05:00
gorkem-bwl 4fdc0bde5c Merge develop into fix/ui-improvements-logs-incidents
Resolve conflicts in IncidentItem.jsx and StatisticsPanel.jsx by
keeping the UI improvements from this branch while adopting the
new Lucide Icon component from develop (replacing old SVG imports).
2026-01-16 14:42:29 -05:00
Alexander Holliday 3d4ebe9588 Merge pull request #3143 from bluewave-labs/fix/responsive-layout-overflow
Fix horizontal overflow causing white space on narrow screens
2026-01-16 11:18:02 -08:00
Alex Holliday 88bb8ef2d3 Merge branch 'fix/responsive-layout-overflow' into feat/user-service-ts 2026-01-16 19:15:24 +00:00
Alex Holliday 64dc381e0f separate user from token 2026-01-16 19:14:03 +00:00
Alex Holliday af82dec2ad separate user from token 2026-01-16 19:12:21 +00:00
gorkem-bwl 62ea7b0e5b Fix horizontal overflow causing white space on narrow screens
Add overflow-x: hidden and width: 100% to .home-layout to prevent
horizontal scrollbar. Add min-width: 0 to .home-content-wrapper to
allow flexbox child to shrink below content size at narrow widths.
2026-01-16 14:09:37 -05:00
Alexander Holliday 8747c38f40 Merge pull request #3142 from bluewave-labs/fix/infra-config
infra fix
2026-01-16 10:38:57 -08:00
Alex Holliday d633fc5ccb infra fix 2026-01-16 18:37:14 +00:00
Alexander Holliday 0d7a35367f Merge pull request #3141 from bluewave-labs/feat/job-queue-ts
feat/job queue ts
2026-01-16 10:25:40 -08:00
Alex Holliday af141ec772 build fix 2026-01-16 18:25:28 +00:00
Alex Holliday 5784fec3d1 format 2026-01-16 18:17:55 +00:00
gorkem-bwl 7436c2b5df fix: remove unnecessary dateRange param now that backend defaults it 2026-01-16 13:12:00 -05:00
gorkem-bwl 2055943876 fix: make dateRange optional for hardware details endpoint
The Joi validation marks dateRange as optional, but the controller was using
requireString() which throws a 400 error when dateRange is missing.

Changed to optionalString() with "recent" as the default value, matching
the behavior of other similar endpoints and the Joi validation schema.
2026-01-16 13:11:41 -05:00
Alex Holliday 95cd33ac06 queue and buffer refactor 2026-01-16 18:11:04 +00:00
Gorkem Cetin (BWL) 2a591ee503 Merge pull request #3139 from bluewave-labs/feat/lucide-icon-migration
Migrate icons from custom SVGs and MUI to Lucide React
2026-01-16 12:50:21 -05:00
gorkem-bwl f5aa24d77e fix: rename Image import to ImageIcon to avoid conflicts 2026-01-16 07:34:03 -05:00
gorkem-bwl 7874439e4c fix: address PR review feedback for icon migration
- Use AlertCircle for error and AlertTriangle for warning icons
- Use Icon color prop instead of CSS fill for Lucide stroke-based icons
- Replace wildcard import with explicit named imports for tree-shaking
- Replace sx prop with style prop on Icon components
- Add Trash2 to Icon component imports
2026-01-16 07:31:23 -05:00
gorkem-bwl 7140d436a5 style: apply Prettier formatting 2026-01-15 23:39:26 -05:00
gorkem-bwl ba0c89fc6c feat: migrate icons from custom SVGs and MUI to Lucide React
Replace custom SVG icons and @mui/icons-material with lucide-react for consistent,
lightweight icon management across the application.

## Changes
- Add lucide-react package and remove @mui/icons-material
- Create theme-aware Icon wrapper component with color resolution
- Create iconMap.js for legacy icon name mappings
- Migrate 64 components to use new Icon component
- Remove 74 unused SVG files from assets/icons/

## Benefits
- Consistent icon API across the application
- Smaller bundle size with tree-shakeable imports
- Theme-aware color support via MUI theme paths
- Simpler maintenance with single icon library
2026-01-15 23:37:50 -05:00
gorkem-bwl d91e87f484 fix: remove <> from allowed password characters and display allowed special chars
## Changes
- Remove < and > from password regex pattern in joi.js (these chars are
  stripped by DOMPurify sanitization which caused confusing validation errors)
- Update locale strings to display the list of allowed special characters
  in password tooltip, error messages, and PasswordPanel requirements

## Benefits
- Users now see exactly which special characters are allowed when setting passwords
- Eliminates confusing "password cannot be empty" errors when using < or >
- Regex now accurately reflects the characters that actually work

Fixes #3010
2026-01-15 22:50:29 -05:00
gorkem-bwl bf15018173 fix: various UI/UX improvements for infrastructure pages
## Changes
- Fix infrastructure monitor configure page loading error by adding required dateRange parameter
- Fix URL validation error when editing existing infrastructure monitors
- Fix monitor update using correct 'id' property instead of '_id'
- Fix gauge box labels: "Device" (capitalized) and "Mounted on" instead of "mountpoint"
- Show full device path instead of truncated ".../" prefix
- Implement CSS Grid layout for gauge boxes ensuring consistent widths across all rows
- Gauge boxes expand to fill space only when 4+ boxes exist

## Files Modified
- client/src/Pages/Infrastructure/Create/index.jsx
- client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx
- client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx
- client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx
- client/src/Pages/Infrastructure/Details/Components/BaseContainer/index.jsx
- client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx
- client/src/locales/en.json
2026-01-15 22:04:56 -05:00
Alexander Holliday b5caabed20 Merge pull request #3135 from bluewave-labs/fix/pagespeed-monitor-delete
Fix pagespeed monitor deletion failing with undefined ID
2026-01-15 18:29:34 -08:00
gorkem-bwl 8814596d6e Fix pagespeed monitor deletion failing with undefined ID
The delete handler was using monitor._id from state, which was undefined
because the fetched monitor object didn't have _id populated. Changed to
use monitorId directly from URL params, which is already available and
used by other handlers in the same component (e.g., handlePause).

Fixes "Failed to delete monitor" error when removing pagespeed monitors.
2026-01-15 21:27:37 -05:00
gorkem-bwl c60d07c3a0 Improve Latest Incidents panel layout
- Simplify incident rows to single line with grid layout
- Remove redundant "Monitor:" and "Status:" labels
- Align columns vertically (monitor name, status badge, duration)
- Center-align status badge column
- Match icon styling with General Statistics (18px, strokeWidth 1.5)
- Remove pointer cursor (click not implemented)
- Add dividers and minHeight to Statistics panel for alignment
2026-01-15 21:19:34 -05:00
gorkem-bwl 54ecf02d9a Fix Prettier formatting in StatisticsPanel 2026-01-15 20:48:15 -05:00
gorkem-bwl 1ab31d19f3 Improve UI consistency for Logs and Incidents pages
## Changes

### Logs page
- Convert server logs from monospace text to DataTable with pagination
- Add table columns: Timestamp, Level, Service, Method, Message
- Add colored level badges (info=green, warn=yellow, error=red, debug=blue)
- Add pagination with configurable rows per page
- Align diagnostics gauge cards with 16px gap and responsive widths

### Incidents page
- Redesign summary cards with consistent styling
- Remove shadows and use 1px border matching other sections
- Reduce icon sizes to 18-24px (sidebar-style)
- Reduce font sizes (13px titles, 18-32px values)
- Add titles to all three summary cards
- Fix "Most Affected Monitor" showing "Unknown Monitor" when no incidents
- Show "N/A" for avg resolution time when no incidents
- Remove cluttered "Resolutions" progress bar section
- Remove horizontal dividers between statistics rows
- Standardize card padding and gap to 16px

### Uptime page
- Fix oversized monitor names in table (removed h6 variant)
2026-01-15 20:44:51 -05:00
Alexander Holliday 2f2ce34373 Merge pull request #3132 from bluewave-labs/fix/theme-simplification-and-bug-fixes
Simplify theme and fix Redux state bugs
2026-01-15 16:51:05 -08:00
Alex Holliday 0f764ca084 typing 2026-01-15 23:45:40 +00:00
gorkem-bwl 8b2ee8d937 Simplify theme and fix Redux state bugs
## Changes

- Remove unused semantic color groups (successSecondary, map, chatbot)
- Remove unused properties from primary and tertiary groups
- Inline accent.darker usage in globalTheme.js
- Fix greeting component crash with persisted Redux state
- Fix MonitorGrid key prop warning

## Details

Theme simplification:
- Reduced constants.js from 355 to 291 lines
- Removed 3 unused semantic color groups
- Removed 6 unused properties (contrastBorder, contrastBorderDisabled, etc.)
- Inlined darken() call for accent hover states

Bug fixes:
- Added null-safe selectors in greeting.jsx for state.ui.greeting
- Added initialization check in setGreeting reducer for older persisted state
- Fixed key prop in MonitorGrid to handle both _id and id
- Removed unused console.log and Grid2 import
2026-01-15 18:06:40 -05:00
Alex Holliday ed80837810 related serverices to TS 2026-01-15 22:33:25 +00:00
Alexander Holliday d82877b28d Merge pull request #3121 from web-engineer/fix/3120-data-leak
3120 remove URL's from data when they are supposed to be disabled
2026-01-15 14:24:47 -08:00
Alex Holliday cf67c82f5e convert job queue and job queue helper 2026-01-15 22:06:46 +00:00
Alex Holliday 5a7b51bfec convert super simple queue to ts 2026-01-15 21:49:48 +00:00
Alexander Holliday 206dcae196 Merge pull request #3131 from bluewave-labs/fix/remove-monitor-db-module
fix: remove db module
2026-01-15 13:05:12 -08:00
Alex Holliday 56bfbe2c77 format 2026-01-15 21:05:00 +00:00
Alex Holliday 8aac3086b8 build error 2026-01-15 21:03:35 +00:00
Alex Holliday 9255500bfc remove db module 2026-01-15 20:55:01 +00:00
Alexander Holliday e99b87fc1e Merge pull request #3130 from bluewave-labs/fix/monitor-service
Fix/monitor service
2026-01-15 11:40:33 -08:00
Alex Holliday ec953ff262 format 2026-01-15 19:40:22 +00:00
Alex Holliday 68559741ba remove mongo middleware 2026-01-15 19:36:41 +00:00
Alex Holliday 74a7f0a701 remove verify team access 2026-01-15 18:33:03 +00:00
Alex Holliday 75bcf9df58 deleteById 2026-01-15 18:24:58 +00:00
Alex Holliday 6b5fd8d17f update, pause 2026-01-15 18:21:36 +00:00
Alexander Holliday a1069f487b Merge pull request #3128 from bluewave-labs/new-uptime-time-range
New uptime time range
2026-01-15 09:28:30 -08:00
karenvicent 86a2171988 Merge branch 'develop' into incidents-feat-modal 2026-01-15 11:39:32 -05:00
Craig Lawson 420164d616 conditional simplification as reqeusted 2026-01-15 12:52:11 +00:00
Craig Lawson f0a6fc4fdf Merge upstream/develop into fix/3120-data-leak 2026-01-15 11:06:27 +00:00
karenvicent e306499bce Merge branch 'develop' into new-uptime-time-range 2026-01-14 22:50:58 -05:00
karenvicent 1472a09718 add new uptime intervals 2026-01-14 22:43:25 -05:00
karenvicent 1c01dcaf31 create incidents action menu and detail page 2026-01-14 22:12:26 -05:00
Alex Holliday e7e4ce7f57 pagination fix 2026-01-14 15:57:17 -08:00
Alex Holliday 93c24d2f5c pagination and filter fix 2026-01-14 15:44:52 -08:00
Alex Holliday cdbaad5bd9 initial 2026-01-14 23:34:05 +00:00
Alexander Holliday b1b27be731 Merge pull request #3127 from bluewave-labs/feat/monitor-service-ts
Feat/monitor service ts
2026-01-14 14:49:00 -08:00
Alexander Holliday 01f005ca41 Merge branch 'develop' into feat/monitor-service-ts 2026-01-14 14:47:25 -08:00
Alex Holliday 1326a6222b format 2026-01-14 22:45:47 +00:00
Alex Holliday aea0b34533 format 2026-01-14 22:45:39 +00:00
Alex Holliday d4af86f26c pagespeed details 2026-01-14 22:44:23 +00:00
Alex Holliday 940247b7bf teamid 2026-01-14 21:47:37 +00:00
Alex Holliday 2b0c523047 unify requests 2026-01-14 20:11:05 +00:00
Alex Holliday 4ad09fad4f snapshots 2026-01-14 20:06:37 +00:00
Alex Holliday 665eb9a67f hardware details 2026-01-14 19:28:17 +00:00
Alex Holliday d1faeab042 fix params 2026-01-14 00:04:22 +00:00
Alex Holliday 76fa99f7bd getUptimeDetails 2026-01-13 23:59:09 +00:00
Alex Holliday 77764c51ee addDemo 2026-01-13 23:22:34 +00:00
Alex Holliday 8a986d2e58 add indexes 2026-01-13 22:56:51 +00:00
Alexander Holliday e57b161be9 Merge pull request #3126 from bluewave-labs/fix/checks-index
add index and cutof
2026-01-13 14:46:24 -08:00
Alex Holliday 8de06df8c4 add index and cutof 2026-01-13 14:45:54 -08:00
Alexander Holliday 8e359cf145 Merge pull request #3125 from bluewave-labs/fix/check-repository
fix: check repo fix
2026-01-13 13:46:27 -08:00
Alex Holliday 52e598a66e Merge branch 'fix/check-repository' into feat/monitor-service-ts 2026-01-13 21:46:17 +00:00
Alex Holliday 0a60cc0363 check repo fix 2026-01-13 21:44:16 +00:00
Alex Holliday 75788f7cb5 Merge branch 'develop' into feat/monitor-service-ts 2026-01-13 20:22:34 +00:00
Alexander Holliday b58a7ad053 Merge pull request #3123 from bluewave-labs/feat/time-series
Feat/time series
2026-01-13 12:21:18 -08:00
Alex Holliday f763126b38 format' 2026-01-13 20:20:50 +00:00
Alex Holliday e79bf28bdc server format 2026-01-13 20:20:29 +00:00
Alex Holliday d0b3bc60df create and update 2026-01-13 20:20:00 +00:00
Alex Holliday 117a2e00c4 status page fix 2026-01-13 19:01:56 +00:00
Alex Holliday f8e88623a8 hardware details pipeline refactor 2026-01-13 18:54:28 +00:00
Alex Holliday 705475e971 _id -> id 2026-01-13 18:37:22 +00:00
Alex Holliday 2b0a51520a infra 2026-01-13 17:50:31 +00:00
Alexander Holliday 55145a38ff Merge pull request #3105 from CodeShellDev/patch-1
bump mongo image version for arm arch to `.30`
2026-01-12 22:10:10 -08:00
Alex Holliday 25d1039376 update pagespeed 2026-01-12 23:57:39 +00:00
Alex Holliday 7a6b0d5da4 refactoring 2026-01-12 22:58:46 +00:00
Alex Holliday e090d3cfd3 -> timeseries 2026-01-12 19:32:23 +00:00
Craig Lawson 1c1ac25857 fix saveSettings is currently being called even when validation fails. 2026-01-12 08:56:24 +00:00
Craig Lawson 3ce40b1ab8 Fix handleSave function syntax errors 2026-01-11 22:48:17 +00:00
Craig Lawson 1494df9eb3 missing brace 2026-01-11 22:39:49 +00:00
Craig Lawson a70959e4a6 Nitpick fix - ref -
client/src/Pages/v1/Settings/index.jsx (1)
171-175: LGTM!

The Redux dispatch provides immediate UI feedback while the actual persistence happens on Save. This is the correct pattern for a server-persisted setting.

Minor note: The ?? value fallback is unreachable for showURL since newValue is always set to a boolean at line 96. Consider simplifying to dispatch(setShowURL(newValue)) for clarity, though the current code is functionally correct.
2026-01-11 22:36:05 +00:00
Craig Lawson b6f48a910f Revert caching changes - restore working implementation
- Remove settingsModule caching that caused inconsistent state
- Restore direct AppSettings query in statusPageModule
- Keep immediate Redux dispatch for UI feedback
- All functionality verified working"
2026-01-11 22:08:15 +00:00
Craig Lawson e4d101237e bug fix - settings handlechange 2026-01-11 21:51:59 +00:00
Craig Lawson 7556086f71 code review updates
Summary of what we fixed based on AI review feedback:

Formatting - Ran Prettier to fix code formatting
Redux state rollback - Moved Redux dispatch to after successful save in settingsHooks.js
Performance - Added caching layer in settingsModule (1-minute TTL) to prevent DB query on every status page request
Architecture - Inject settingsModule instead of raw AppSettings model for better encapsulation
2026-01-11 20:51:25 +00:00
Craig Lawson 7b619e6b31 Remove debug logging and add feature documentation
- Remove all console.log statements added during development
- Clean up code for production readiness
2026-01-11 18:56:16 +00:00
Craig Lawson 76ab2695db missing setting 2026-01-11 18:23:58 +00:00
Craig Lawson 418068e702 loggin settings 2026-01-11 18:18:38 +00:00
Craig Lawson 12b28ffb3f type issue 2026-01-10 22:30:03 +00:00
Craig Lawson 526fa24eab validation bug 2026-01-10 22:23:31 +00:00
Craig Lawson a13f6a34c4 missing value 2026-01-10 22:02:36 +00:00
Craig Lawson f659876845 select bug 2026-01-10 21:58:13 +00:00
Craig Lawson 643a743d70 more logging 2026-01-10 21:49:10 +00:00
Craig Lawson ab4cc80628 3120 persist admin value 2026-01-10 21:29:15 +00:00
Craig Lawson fb3795bd70 3120 remove URL's from data when they are supposed to be disabled 2026-01-10 12:15:29 +00:00
Alexander Holliday 5c14c68899 Merge pull request #3119 from bluewave-labs/fix/controllerUtils
js -> ts
2026-01-09 15:59:18 -08:00
Alexander Holliday 5a878e89d0 Merge pull request #3118 from bluewave-labs/fix/check-controller
js->ts
2026-01-09 15:58:45 -08:00
Alex Holliday bdb07220ed js -> ts 2026-01-09 15:58:31 -08:00
Alexander Holliday 641d5e6dda Merge pull request #3117 from bluewave-labs/fix/diagnostic-controller
js->ts
2026-01-09 15:54:02 -08:00
Alex Holliday b24339f232 js->ts 2026-01-09 15:53:53 -08:00
Alexander Holliday ff324fe6e7 Merge pull request #3116 from bluewave-labs/fix/incidentController
js->ts
2026-01-09 15:46:39 -08:00
Alex Holliday fee9b8a0de js->ts 2026-01-09 15:46:30 -08:00
Alexander Holliday ad9488f54e Merge pull request #3115 from bluewave-labs/fix/invite-controller
js -> ts
2026-01-09 15:44:23 -08:00
Alex Holliday ed77fc3a9d js->ts 2026-01-09 15:44:11 -08:00
Alexander Holliday babd8e672f Merge pull request #3114 from bluewave-labs/fix/log-controller
js->ts
2026-01-09 15:39:35 -08:00
Alex Holliday 8ceac493ef js -> ts 2026-01-09 15:39:30 -08:00
Alexander Holliday fee808789f Merge pull request #3113 from bluewave-labs/fix/mw-controller
js->ts
2026-01-09 15:35:53 -08:00
Alex Holliday dd03c554c9 js->ts 2026-01-09 15:35:48 -08:00
Alex Holliday 3ab4fcb277 js->ts 2026-01-09 15:32:53 -08:00
Alexander Holliday 11e3cac440 Merge pull request #3112 from bluewave-labs/fix/status-page-controller
js->ts
2026-01-09 15:26:37 -08:00
Alex Holliday 1d7f3b15a4 js->ts 2026-01-09 15:21:56 -08:00
Alexander Holliday b7ad3c036b Merge pull request #3111 from bluewave-labs/fix/settings-controller
js->ts
2026-01-09 15:20:30 -08:00
Alex Holliday 7b2ff593a4 js->ts 2026-01-09 15:16:17 -08:00
Alexander Holliday 916f3c0921 Merge pull request #3110 from bluewave-labs/feat/queue-controller
Feat/queue controller
2026-01-09 15:13:31 -08:00
Alex Holliday 846f1dfa6c remove extra param 2026-01-09 15:07:38 -08:00
Alex Holliday 3f631ff382 js -> ts 2026-01-09 15:07:02 -08:00
Alexander Holliday 43a370e162 Merge pull request #3109 from bluewave-labs/feat/notification-controller
js -> ts
2026-01-09 15:05:13 -08:00
Alex Holliday 64320c7aca js -> ts 2026-01-09 15:02:19 -08:00
Alexander Holliday 61cedfb2b7 Merge pull request #3108 from bluewave-labs/feat/monitor-controller
monitor controller -> ts
2026-01-09 14:51:17 -08:00
Alex Holliday a9ce980d03 monitor controller -> ts 2026-01-09 14:49:44 -08:00
Alexander Holliday 8409f08a2d Merge pull request #3107 from bluewave-labs/feat/be/ts
ts config
2026-01-09 13:38:55 -08:00
Alex Holliday ef2cccf16e ts config 2026-01-09 13:36:58 -08:00
CodeShell 0285302ecc bump mongo image version for arm arch to .30 2026-01-09 10:36:46 +00:00
Alexander Holliday 668ea7418d Merge pull request #3088 from bluewave-labs/fix-traslator-error
fix translator error and add format
2025-12-22 09:41:58 -08:00
karenvicent b12bdfcc22 fix traslator error and add format 2025-12-22 12:36:29 -05:00
Alexander Holliday 65de627870 Merge pull request #3079 from Antoplt/feat/choose-disks-monitor
feature requested #2840: improve disk device and mountpoint display with hover tooltips
2025-12-18 10:53:24 -08:00
Alexander Holliday 03d1dcaefb Merge branch 'develop' into feat/choose-disks-monitor 2025-12-18 10:53:01 -08:00
Alexander Holliday 14768bde28 Merge pull request #3086 from bluewave-labs/fix-incidents-duplication-front-end
Fix incidents duplication front end
2025-12-18 10:51:52 -08:00
karenvicent 9555ff43c7 add llama suggestions and fix genericFallback error 2025-12-18 12:47:01 -05:00
karenvicent 75acb1ade6 Merge branch 'develop' into fix-incidents-duplication-front-end 2025-12-17 18:56:25 -05:00
karenvicent fd12ca6e21 incidents feature fron-end 2025-12-17 18:51:34 -05:00
ppmar ff2f84d46b resolve disk selection persistence and validation issues 2025-12-17 23:50:40 +01:00
Alexander Holliday 1ffce112d1 Merge pull request #3077 from web-engineer/feat/helm-tls-letsencrypt-support
feat(helm): add TLS/HTTPS support with cert-manager integration
2025-12-16 15:25:05 -08:00
Alexander Holliday 382df34f06 Merge pull request #3085 from Preetham001-SR/fix-hardcoded-messages
fix: replace hardcoded response messages with localized strings
2025-12-16 15:22:19 -08:00
Preetham001-SR 124f300362 fix: replace hardcoded response messages with localized strings
- Replace hardcoded 'OK' messages in monitorController with proper localized strings
- Add new string constants to en.json for consistent messaging
- Follow existing pattern used by other controller methods
- Resolves TODO comment in getMonitorsAndSummaryByTeamId method

Fixes: Hardcoded response messages inconsistent with i18n architecture
2025-12-14 23:45:32 +05:30
Alexander Holliday f782899768 Merge pull request #3080 from bluewave-labs/feat/remove-v2
Remove business V2 logic
2025-12-05 09:05:17 -08:00
Sajanpreet Singh cdb83bb47d Fix build checks 2025-12-04 10:42:18 -06:00
Sajanpreet Singh 72bcc0d020 Remove business V2 logic 2025-12-04 10:31:45 -06:00
Craig Lawson ac901b46c3 chore(helm): default TLS secretName to <release>-* and remove duplicate prechecks file; update docs with protocol flags 2025-12-03 21:43:21 +00:00
Craig Lawson c1b3d325f6 feat(helm): enforce TLS + protocol validation and improve docs\n\n- Add prechecks.yaml to validate TLS issuer annotations and secretName\n- Require https when TLS enabled; prevent client https + server http mixed content\n- Validate client.protocol in addition to server.protocol\n- Update INSTALLATION.md to set protocol: https when enabling TLS\n- Clarify values.yaml comments for TLS usage 2025-12-03 21:30:35 +00:00
Craig Lawson 7cad2fec27 chore(helm): comment out secretName in TLS config, add usage note for cert-manager 2025-11-30 16:46:06 +00:00
Adevixxx 7edc2a71d8 Ajout de l'interface pour selectionner des disques et modifications du coté serveur pour la sauvegarde 2025-11-30 11:48:54 +01:00
Antonin 913f7a9e42 Merge branch 'develop' into feat/choose-disks-monitor 2025-11-30 11:35:34 +01:00
Craig Lawson dd98757b2a feat(helm): add TLS/HTTPS support with cert-manager integration
- Add TLS configuration to ingress templates (client & server)
- Add tls.enabled and tls.secretName to values.yaml
- Include comprehensive cert-manager documentation in INSTALLATION.md
- Add example annotations for Let's Encrypt cluster issuer
- Support both values.yaml and --set flag configuration methods
- Backward compatible (TLS disabled by default)

Enables automatic HTTPS certificate provisioning when cert-manager
is installed in the cluster with appropriate ClusterIssuer configured.
2025-11-29 17:46:29 +00:00
Alexander Holliday 1e3ee23120 Merge pull request #3071 from mospell/feat/better_infrastructure_notifications
Feat: Add link to Infrastructure Details for Notifications
2025-11-28 11:52:04 -08:00
Alexander Holliday 74d8e0a892 Merge pull request #3074 from SuryaGanesh284/develop
Add quick setup checklist to CONTRIBUTING.md
2025-11-28 11:51:06 -08:00
Antonin 50af063f64 Merge branch 'develop' into feat/choose-disks-monitor 2025-11-26 11:58:35 +01:00
Antonin 80a66d74ca Add metric/value three & four to propTypes 2025-11-26 11:56:46 +01:00
SuryaGanesh284 6c2908681a Add quick setup checklist to CONTRIBUTING.md
Added a quick setup checklist for contributors.
2025-11-25 22:12:16 +05:30
mospell 90593c0d7c added 'go-to' links to the remaining Discord notifications 2025-11-23 17:18:09 +01:00
mospell 8fb958e772 Feat: Add link to infrastructure details for Notifications 2025-11-23 16:39:09 +01:00
Alexander Holliday fc39ab3fb9 Merge pull request #3069 from bluewave-labs/feat/JSON-export
implement JSON export
2025-11-21 10:45:32 -08:00
Alex Holliday 58e7ef2527 implement JSON export 2025-11-21 10:43:29 -08:00
Alexander Holliday fec32380ae Merge pull request #3067 from bluewave-labs/fix-incidents-creation
Incident Management System - Backend Implementation
2025-11-19 09:35:15 -08:00
karenvicent 3b8d87bd17 Merge branch 'develop' into fix-incidents-creation 2025-11-19 10:06:35 -05:00
karenvicent 131ced10fc Incidents management implementation 2025-11-18 16:01:47 -05:00
antoplt 886752effd Add metricFour and valueFour props 2025-11-18 15:29:25 +01:00
antoplt a5d54a637e Fix function naming error 2025-11-18 14:44:45 +01:00
antoplt 1b4edf16fb Add mountpoint as fourth metric 2025-11-18 14:38:03 +01:00
antoplt 4042d34585 Add mountpoint field to mongoose diskSchema 2025-11-18 14:25:02 +01:00
antoplt 135c3ff454 Merge branch 'develop' into feat/choose-disks-monitor 2025-11-18 13:55:39 +01:00
antoplt f8d5791fab Add device name formatting 2025-11-18 13:13:35 +01:00
antoplt ffa4b1695b Add device field into moogose diskSchema 2025-11-18 13:12:34 +01:00
antoplt 98214ff578 Add disk device as third metric value 2025-11-18 12:06:22 +01:00
karenvicent eb65c41d15 Merge branch 'develop' into fix-incidents-creation 2025-11-06 17:07:24 -05:00
karenvicent 0c3fdbc539 feat: add incident model, module and service with StatusService integration
- Create Incident model with resolution tracking fields
- Implement IncidentModule for data access
- Implement IncidentService with business logic
- Integrate with StatusService to auto-create/resolve incidents on status change
2025-11-06 17:06:33 -05:00
Alexander Holliday feb061240b Merge pull request #3042 from bluewave-labs/develop
develp->master
2025-10-27 12:09:00 -07:00
Alexander Holliday a924227805 Merge pull request #2964 from bluewave-labs/develop
develop -> master
2025-09-18 13:40:45 -07:00
Alexander Holliday 2144d3f8c0 Merge pull request #2962 from bluewave-labs/develop
develop -> master
2025-09-18 12:04:51 -07:00
Alexander Holliday fcd6786149 Merge pull request #2936 from bluewave-labs/develop
develop -> master
2025-09-08 20:43:21 -07:00
Alexander Holliday ea968ebf3c Merge pull request #2933 from bluewave-labs/develop
develop -> master
2025-09-08 20:33:35 -07:00
Alexander Holliday 8a7b852974 Merge pull request #2929 from bluewave-labs/develop
develop -> master
2025-09-08 07:24:16 -07:00
Alexander Holliday c8d3e3b70c Merge pull request #2921 from bluewave-labs/develop
develop -> master
2025-09-04 15:46:30 -07:00
Alexander Holliday 7a90a6f9f4 Merge pull request #2915 from bluewave-labs/develop
develop->master
2025-09-01 10:33:09 -07:00
738 changed files with 28369 additions and 24901 deletions

No files matched your search

+3 -1
View File
@@ -1,3 +1,5 @@
.idea
.vscode
.VSCodeCounter
.VSCodeCounter
*.sh
mongo
-993
View File
@@ -1,993 +0,0 @@
# Checkmate Codebase Architecture - Docker Monitoring Integration Guide
## 1. PROJECT STRUCTURE
### Directory Layout
```
Checkmate/
├── server/ # Backend Node.js/Express
│ ├── src/
│ │ ├── db/v1|v2/ # Database (MongoDB)
│ │ ├── service/v1|v2/ # Business logic
│ │ ├── routes/v1|v2/ # API routes
│ │ ├── controllers/v1|v2/ # HTTP request handlers
│ │ ├── middleware/v1/ # Express middleware
│ │ ├── config/ # Service initialization
│ │ ├── validation/ # Input validation
│ │ └── utils/ # Helper functions
│ └── package.json # Dependencies (Node >=20)
├── client/ # Frontend React + TypeScript
│ ├── src/
│ │ ├── Components/v1|v2/ # UI Components
│ │ ├── Pages/v1|v2/ # Page layouts
│ │ ├── Hooks/ # React hooks (data fetching)
│ │ ├── Features/ # Redux slices
│ │ └── Utils/ # Utilities & API client
│ └── package.json # React, Vite, MUI
└── docker/ # Deployment configs
```
### Technology Stack
**Backend:**
- Express 4.19 (HTTP server)
- MongoDB 8.3 (database)
- Mongoose (ODM)
- BullMQ 5.41 (job queue) - V2 only
- super-simple-scheduler 1.4 (job scheduling) - V1
- Dockerode 4.0.6 (Docker API client)
- JWT authentication
- Winston (logging)
**Frontend:**
- React 18.3 + TypeScript
- Vite (build tool)
- MUI v6 (Material-UI)
- Redux Toolkit (state management)
- Axios (HTTP client)
- React Router (navigation)
- i18next (internationalization)
**Architectures:**
- V1: Simpler, SuperSimpleQueue-based job scheduling
- V2: TypeScript-based, BullMQ, more structured (in development)
---
## 2. MONITOR TYPES & IMPLEMENTATION
### Supported Monitor Types
Defined in `/server/src/db/v1/models/Monitor.js` line 46:
```javascript
type: {
enum: ["http", "ping", "pagespeed", "hardware", "docker", "port", "game"],
required: true
}
```
### How Monitor Types Are Handled
#### Request Dispatch (NetworkService)
**Location:** `/server/src/service/v1/infrastructure/networkService.js` (580 lines)
Entry point: `requestStatus(monitor)` - Routes to type-specific handlers:
```javascript
async requestStatus(monitor) {
switch(monitor.type) {
case "ping": return await this.requestPing(monitor);
case "http": return await this.requestHttp(monitor);
case "pagespeed": return await this.requestPageSpeed(monitor);
case "hardware": return await this.requestHardware(monitor);
case "docker": return await this.requestDocker(monitor);
case "port": return await this.requestPort(monitor);
case "game": return await this.requestGame(monitor);
}
}
```
#### DOCKER IMPLEMENTATION (Lines 289-383)
```javascript
async requestDocker(monitor) {
// Uses Dockerode to connect to /var/run/docker.sock
const docker = new this.Docker({
socketPath: "/var/run/docker.sock",
handleError: true,
});
// Container matching (priority-based):
// 1. Exact full ID match (64-char)
// 2. Exact name match (case-insensitive)
// 3. Partial ID match (backwards compatibility)
// Returns: { status: bool, code: int, message: string, responseTime: ms }
}
```
**Docker Monitor Storage:**
- `monitor.url`: Container ID or name (e.g., "my-app" or "a1b2c3d4...")
- `monitor.port`: Not used for Docker
- Returns: Only status and response time (no detailed metrics)
#### HARDWARE IMPLEMENTATION (Lines 279-287)
```javascript
async requestHardware(monitor) {
return await this.requestHttp(monitor); // Delegates to HTTP!
}
```
Hardware monitors send HTTP requests to Capture API endpoint with secret authentication.
### Hardware/Docker Check Data Structure
**Location:** `/server/src/db/v1/models/Check.js` (176 lines)
Check schema includes:
```javascript
// Common fields
monitorId, teamId, type, status, responseTime, message
// Hardware/Docker specific (capture data):
cpu: {
physical_core, logical_core, frequency, temperature[],
free_percent, usage_percent
},
memory: {
total_bytes, available_bytes, used_bytes, usage_percent
},
disk: [{
read_speed_bytes, write_speed_bytes,
total_bytes, free_bytes, usage_percent
}],
host: { os, platform, kernel_version },
errors: [{ metric[], err }],
capture: { version, mode },
net: [{ name, bytes_sent, bytes_recv, ... }]
```
**TTL:** Auto-deletes after 30 days
**Indexes:** monitorId, type, status, updatedAt (optimized for queries)
---
## 3. DATABASE SCHEMA
### Core Models
#### Monitor Model
**File:** `/server/src/db/v1/models/Monitor.js` (208 lines)
```javascript
{
userId: ObjectId (immutable, required),
teamId: ObjectId (immutable, required),
name: String (required),
description: String,
type: String (enum: http|ping|pagespeed|hardware|docker|port|game),
url: String (required) // container name/ID for docker
port: Number,
interval: Number (ms, default: 60000),
isActive: Boolean (default: true),
status: Boolean, // Current up/down
statusWindow: [Boolean], // Last N checks
statusWindowSize: Number (default: 5),
statusWindowThreshold: Number (default: 60),
// Hardware/Docker thresholds
thresholds: {
usage_cpu: Number (0-1),
usage_memory: Number (0-1),
usage_disk: Number (0-1),
usage_temperature: Number
},
// Notification thresholds
alertThreshold: Number (default: 5),
cpuAlertThreshold, memoryAlertThreshold, diskAlertThreshold, tempAlertThreshold,
// Relationships
notifications: [ObjectId], // refs to Notification model
// Advanced
secret: String, // For Capture API auth
ignoreTlsErrors: Boolean,
jsonPath: String,
expectedValue: String,
matchMethod: String (equal|include|regex),
gameId: String,
group: String (max 50 chars),
// Computed
uptimePercentage: Number,
timestamps: true // createdAt, updatedAt
}
// Indexes
MonitorSchema.index({ teamId: 1, type: 1 });
// Pre-hooks
pre("findOneAndDelete"): Deletes checks, stats, removes from status pages
pre("deleteMany"): Cascade deletes
pre("save"): Syncs alertThresholds
pre("findOneAndUpdate"): Updates thresholds
```
#### Check Model
**File:** `/server/src/db/v1/models/Check.js`
```javascript
{
monitorId: ObjectId (ref: Monitor, immutable, indexed),
teamId: ObjectId (ref: Team, immutable, indexed),
type: String (enum: http|ping|pagespeed|hardware|docker|port|game, indexed),
status: Boolean (indexed),
responseTime: Number,
timings: Object,
statusCode: Number,
message: String,
// Acknowledgment
ack: Boolean (default: false),
ackAt: Date,
// Hardware fields (see above)
cpu, memory, disk[], host, errors[], capture, net[]
// PageSpeed only
accessibility, bestPractices, seo, performance, audits,
// Expiry
expiry: Date (TTL index: 30 days),
timestamps: true
}
// Indexes
CheckSchema.index({ updatedAt: 1 });
CheckSchema.index({ monitorId: 1, updatedAt: 1 });
CheckSchema.index({ monitorId: 1, updatedAt: -1 });
CheckSchema.index({ teamId: 1, updatedAt: -1 });
```
#### MonitorStats Model
**File:** `/server/src/db/v1/models/MonitorStats.js`
```javascript
{
monitorId: ObjectId (ref: Monitor, immutable, indexed),
avgResponseTime: Number,
totalChecks: Number,
totalUpChecks: Number,
totalDownChecks: Number,
uptimePercentage: Number,
lastCheckTimestamp: Number,
lastResponseTime: Number,
timeOfLastFailure: Number,
timestamps: true
}
```
### Database Access Layer
**Location:** `/server/src/db/v1/modules/`
- `MonitorModule.js` (593 lines) - Monitor CRUD + aggregations
- `CheckModule.js` - Check recording
- `MonitorModuleQueries.js` - MongoDB aggregation pipelines
**Key Methods:**
```javascript
// Creation
Monitor.createMonitor({ body, teamId, userId })
Monitor.createBulkMonitors(monitors)
// Retrieval
Monitor.getMonitorById(id)
Monitor.getMonitorsByTeamId()
Monitor.getMonitorStatsById()
Monitor.getHardwareDetailsById()
// Updates
Monitor.updateMonitor(id, updateData)
// Deletions
Monitor.deleteMonitor(id)
```
---
## 4. API PATTERNS
### REST Endpoint Structure
**Base:** `/api/v1/`
#### Monitor Routes
**File:** `/server/src/routes/v1/monitorRoute.js`
```
GET /monitors getAllMonitors
POST /monitors createMonitor
DELETE /monitors deleteAllMonitors (superadmin)
GET /monitors/:monitorId getMonitorById
PUT /monitors/:monitorId editMonitor (admin)
DELETE /monitors/:monitorId deleteMonitor (admin)
GET /monitors/team getMonitorsByTeamId
GET /monitors/team/with-checks getMonitorsWithChecksByTeamId
GET /monitors/team/summary getMonitorsSummaryByTeamId
GET /monitors/team/groups getGroupsByTeamId
GET /monitors/stats/:monitorId getMonitorStatsById
GET /monitors/uptime/details/:monitorId getUptimeDetailsById
GET /monitors/hardware/details/:monitorId getHardwareDetailsById
POST /monitors/pause/:monitorId pauseMonitor (admin)
POST /monitors/demo addDemoMonitors
POST /monitors/bulk createBulkMonitors (CSV upload)
POST /monitors/test-email sendTestEmail
GET /monitors/games getAllGames
GET /monitors/certificate/:monitorId fetchCertificate
```
#### Check Routes
**File:** `/server/src/routes/v1/checkRoute.js`
```
GET /checks/:monitorId getChecksByMonitorId
GET /checks/team/:teamId getChecksByTeamId
GET /checks/:monitorId/:checkId getCheckById
PUT /checks/:checkId updateCheck (ack)
```
### Request/Response Pattern
**Create Monitor Request:**
```javascript
POST /api/v1/monitors
Authorization: Bearer <JWT>
Content-Type: application/json
{
name: "My Docker Container",
description: "Monitors app container",
type: "docker",
url: "my-app-container", // Container name
interval: 60000, // 1 minute
isActive: true,
secret: "api-key-for-capture", // If using hardware monitoring
notifications: ["notif-id-1"],
thresholds: {
usage_cpu: 0.8, // 80%
usage_memory: 0.9, // 90%
usage_disk: 0.95
},
alertThreshold: 5, // Consecutive failures
statusWindowSize: 5, // Last 5 checks
statusWindowThreshold: 60 // % threshold
}
Response 200:
{
data: {
_id: "monitor-id",
name: "My Docker Container",
type: "docker",
status: true,
interval: 60000,
...
},
msg: "Monitor created successfully"
}
```
**Edit Monitor Request:**
```javascript
PUT /api/v1/monitors/:monitorId
{
name: "Updated Name",
interval: 120000,
thresholds: { usage_cpu: 0.85 },
// Partial updates allowed
}
```
### Authentication & Authorization
**JWT Middleware:** `/server/src/middleware/v1/verifyJWT.js`
- Header: `Authorization: Bearer <token>`
- Validates signature using `jwtSecret` from AppSettings
- Extracts user info: `req.user = { userId, teamId, role, ... }`
**Role-Based Access:**
```javascript
// isAllowed middleware checks req.user.role
router.post("/monitors", isAllowed(["admin", "superadmin"]), createMonitor);
router.delete("/monitors/:monitorId", isAllowed(["admin", "superadmin"]), deleteMonitor);
```
### Validation
**File:** `/server/src/validation/joi.js`
```javascript
const createMonitorBodyValidation = joi.object({
name: joi.string().required(),
description: joi.string().required(),
type: joi.string().required(),
url: joi.string().required(),
interval: joi.number(),
port: joi.number(),
thresholds: joi.object().keys({
usage_cpu: joi.number(),
usage_memory: joi.number(),
usage_disk: joi.number(),
usage_temperature: joi.number(),
}),
notifications: joi.array().items(joi.string()),
secret: joi.string(),
// ... more fields
});
```
---
## 5. POLLING/JOBS IMPLEMENTATION
### Job Queue Architecture
#### V1: SuperSimpleScheduler (Production)
**Files:**
- `/server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js`
- `/server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js`
**Job Lifecycle:**
1. **Initialization** (on server startup):
```javascript
// Load all monitors from DB
const monitors = await db.monitorModule.getAllMonitors();
for (const monitor of monitors) {
// Add job with random offset (0-100ms)
this.addJob(monitor._id, monitor);
}
// Register job template
scheduler.addTemplate("monitor-job", this.helper.getMonitorJob());
```
2. **Job Template** (executed per interval):
```javascript
getMonitorJob = () => {
return async (monitor) => {
// Check maintenance window
if (await this.isInMaintenanceWindow(monitorId, teamId)) {
return; // Skip
}
// Execute monitor check
const networkResponse = await this.networkService.requestStatus(monitor);
// Update monitor status
const { monitor: updated, statusChanged } =
await this.statusService.updateStatus(networkResponse);
// Send notifications if status changed
if (statusChanged) {
await this.notificationService.handleNotifications({
...networkResponse,
monitor: updated,
prevStatus,
statusChanged
});
}
};
};
```
3. **Job Control:**
```javascript
// Add job
addJob(monitorId, monitor) {
scheduler.addJob({
id: monitorId.toString(),
template: "monitor-job",
repeat: monitor.interval, // Milliseconds
active: monitor.isActive,
data: monitor.toObject()
});
}
// Pause/Resume
pauseJob(monitor) { scheduler.pauseJob(monitor._id); }
resumeJob(monitor) { scheduler.resumeJob(monitor._id); }
// Update interval
updateJob(monitor) {
scheduler.updateJob(monitor._id, {
repeat: monitor.interval,
data: monitor.toObject()
});
}
// Delete
deleteJob(monitor) { scheduler.removeJob(monitor._id); }
```
#### V2: BullMQ (TypeScript, Planned)
**Files:**
- `/server/src/service/v2/infrastructure/JobQueue.ts`
- `/server/src/service/v2/infrastructure/JobGenerator.ts`
**Key Differences:**
- Uses Redis backing store
- TypeScript interfaces (type safety)
- More robust queue management
- Cleanup jobs for orphaned checks
```typescript
// Cleanup job runs every 24 hours
scheduler.addJob({
id: "cleanup-orphaned-checks",
template: "cleanup-job",
repeat: 24 * 60 * 60 * 1000,
active: true
});
```
### Monitor Execution Flow
```
Monitor Created/Updated
↓
MonitorService.createMonitor()
↓
jobQueue.addJob(monitor)
↓
Job scheduled at interval
↓
[Job Execution]
├─ Check maintenance window
├─ NetworkService.requestStatus(monitor)
│ └─ Routes to type handler (requestDocker, requestHttp, etc)
├─ Create Check record (database)
├─ StatusService.updateMonitorStatus()
│ └─ Updates Monitor.status field
├─ MonitorStatsService.updateStats()
├─ If status changed → NotificationService.handleNotifications()
└─ Job completes / reschedules for next interval
```
### Status Update Logic
**Location:** `/server/src/service/v1/infrastructure/statusService.js`
```javascript
async updateStatus(networkResponse) {
// networkResponse = { monitorId, type, status, code, message, responseTime }
// 1. Create Check record
const check = await Check.create({
monitorId, teamId, type, status,
responseTime, statusCode: code, message
});
// 2. Get previous status
const prevStatus = monitor.status;
// 3. Apply statusWindow logic (sliding window of checks)
monitor.statusWindow.push(status);
if (monitor.statusWindow.length > monitor.statusWindowSize) {
monitor.statusWindow.shift();
}
// 4. Determine if DOWN (majority of window is false)
const downCount = monitor.statusWindow.filter(s => !s).length;
const isDown = (downCount / monitor.statusWindowSize) >
(monitor.statusWindowThreshold / 100);
monitor.status = isDown ? false : true;
// 5. Return status change info
return {
monitor: await monitor.save(),
statusChanged: prevStatus !== monitor.status,
prevStatus
};
}
```
---
## 6. FRONTEND COMPONENTS
### Monitor Management Pages
#### Infrastructure Monitors (Hardware/Docker)
**Location:** `/client/src/Pages/v1/Infrastructure/`
```
Infrastructure/
├── Create/
│ ├── index.jsx (Main form - 10K lines)
│ ├── Components/
│ │ ├── MonitorStatusHeader.jsx
│ │ ├── MonitorActionButtons.jsx
│ │ └── CustomAlertsSection.jsx
│ └── hooks/
│ ├── useInfrastructureMonitorForm.jsx
│ ├── useValidateInfrastructureForm.jsx
│ └── useInfrastructureSubmit.jsx
├── Details/
├── Monitors/
│ └── Components/
│ └── MonitorsTable.jsx
```
#### Form Structure (useInfrastructureMonitorForm hook)
```javascript
const [infrastructureMonitor, setInfrastructureMonitor] = useState({
url: "", // Container name/IP
name: "", // Display name
notifications: [], // Notification IDs
interval: 0.25, // Minutes (0.25 = 15s, 1 = 60s)
// Threshold toggles
cpu: false,
usage_cpu: "", // Percentage
memory: false,
usage_memory: "",
disk: false,
usage_disk: "",
temperature: false,
usage_temperature: "",
secret: "", // Capture API secret
statusWindowSize: 5,
statusWindowThreshold: 60
});
```
**Frequency Options:**
```javascript
[
{ _id: 0.25, name: "15 Seconds" },
{ _id: 0.5, name: "30 Seconds" },
{ _id: 1, name: "1 Minute" },
{ _id: 2, name: "2 Minutes" },
{ _id: 5, name: "5 Minutes" },
{ _id: 10, name: "10 Minutes" }
]
```
#### Form Submission (useInfrastructureSubmit hook)
```javascript
// Converts frontend form → backend model
const finalForm = {
url: `http${https ? "s" : ""}://` + infrastructureMonitor.url,
name: infrastructureMonitor.name || infrastructureMonitor.url,
interval: infrastructureMonitor.interval * 60000, // Convert to ms
type: "hardware", // Always "hardware" for infrastructure monitors
// Thresholds (convert % to decimal 0-1)
thresholds: {
...(cpu ? { usage_cpu: usage_cpu / 100 } : {}),
...(memory ? { usage_memory: usage_memory / 100 } : {}),
...(disk ? { usage_disk: usage_disk / 100 } : {}),
...(temperature ? { usage_temperature: usage_temperature / 100 } : {})
},
notifications: infrastructureMonitor.notifications
};
// API call
isCreate ?
await createMonitor({ monitor: finalForm, redirect: "/infrastructure" }) :
await updateMonitor({ monitor: finalForm, redirect: "/infrastructure" });
```
### Monitor Display Components
#### Monitor Status Component
**Location:** `/client/src/Components/v2/Monitors/MonitorStatus.tsx`
Displays:
- Status indicator (up/down)
- Response time
- Last check time
- Incident badges
#### Monitoring Hooks
**Location:** `/client/src/Hooks/v1/monitorHooks.js`
```javascript
// Fetch monitors
useFetchMonitorsByTeamId({ types, limit, page, ... })
useFetchMonitorsWithSummary({ types, monitorUpdateTrigger })
useFetchMonitorsWithChecks({ types, limit, ... })
// Fetch individual monitor
useFetchHardwareMonitorById({ monitorId, updateTrigger })
useFetchStatsByMonitorId({ monitorId, dateRange, ... })
// Manage monitors
useCreateMonitor() // POST /monitors
useUpdateMonitor() // PUT /monitors/:id
useDeleteMonitor() // DELETE /monitors/:id
usePauseMonitor() // POST /monitors/pause/:id
```
### Network Service (API Client)
**Location:** `/client/src/Utils/NetworkService.js` (1157 lines)
```javascript
// Constructor
constructor(store, dispatch, navigate) {
// Axios instance with:
// - Bearer token injection
// - Language header
// - 401 redirect to login
// - Network error handling
}
// Monitor endpoints
async createMonitor(config) {
return this.axiosInstance.post(`/monitors`, config.monitor);
}
async updateMonitor(config) {
return this.axiosInstance.put(`/monitors/${config.monitorId}`, config.monitor);
}
async getMonitorsByTeamId(config) {
// Query params: limit, types[], page, rowsPerPage, filter, field, order
return this.axiosInstance.get(`/monitors/team?...`);
}
async getHardwareDetailsByMonitorId(config) {
return this.axiosInstance.get(`/monitors/hardware/details/${config.monitorId}`);
}
async getStatsByMonitorId(config) {
// Query params: sortOrder, limit, dateRange, numToDisplay, normalize
return this.axiosInstance.get(`/monitors/stats/${config.monitorId}?...`);
}
```
---
## 7. AUTHENTICATION & API KEYS
### User Authentication (JWT)
**Registration/Login:**
```javascript
// POST /auth/register
{
firstName: "John",
lastName: "Doe",
email: "john@example.com",
password: "SecurePass123!"
}
Response:
{
user: { _id, email, role, teamId, ... },
token: "eyJhbGciOiJIUzI1NiIs..." // JWT
}
```
**Token Storage:** Redux store (`Features/Auth/authSlice`)
**Token Injection:** Automatic by NetworkService
```javascript
config.headers.Authorization = `Bearer ${authToken}`;
```
### Monitor-Specific Authentication
#### For Hardware/Docker Monitors via Capture API
Monitors can have a `secret` field for authenticating requests to the Capture API:
```javascript
{
type: "hardware",
url: "http://192.168.1.100:3000", // Capture endpoint
secret: "api-key-12345", // Passed to Capture API
thresholds: { ... }
}
```
**Implementation:** The secret is stored in the Monitor model and could be:
1. Sent as HTTP header: `Authorization: Bearer <secret>`
2. Sent as query param: `?secret=<secret>`
3. Sent in request body for POST requests
---
## 8. CONFIGURATION & USER SETTINGS
### Global Settings
**Model:** `/server/src/db/v1/models/AppSettings.js`
```javascript
{
jwtSecret: String, // For signing JWTs
sslCertificate: String, // SSL cert content
smtpConfig: Object, // Email settings
globalThresholds: { // Default thresholds
cpu: Number,
memory: Number,
disk: Number,
temperature: Number
},
siteTitle: String,
defaultLanguage: String,
maintenanceMode: Boolean
}
```
**Fetching Settings:**
```javascript
// Frontend
useFetchGlobalSettings()
→ NetworkService.getGlobalSettings()
→ GET /settings
// Backend
SettingsService.loadSettings()
→ Loads from AppSettings collection
```
### Monitor Configuration UI Flow
1. **Create Page:** `/infrastructure/create`
- Fetch global settings for default thresholds
- Form includes:
- Monitor name
- URL/Container name
- Interval (dropdown)
- CPU/Memory/Disk/Temp thresholds (checkboxes + inputs)
- Notification channels (multi-select)
- Secret (if using Capture API)
2. **Edit Page:** `/infrastructure/configure/:monitorId`
- Load existing monitor data
- Update form fields
- Save changes with PUT request
3. **Details Page:** `/infrastructure/:monitorId`
- Display monitor stats
- Show uptime graph
- List recent checks
- Status history
### Team-Based Isolation
**Access Control:**
```javascript
// All queries filtered by teamId
await Monitor.find({ teamId: req.user.teamId });
// verifyTeamAccess middleware
await verifyTeamAccess(req.user.teamId, req.params.monitorId);
```
---
## KEY PATTERNS FOR DOCKER MONITORING INTEGRATION
### 1. Adding Docker Monitor Type
Already supported! Located in:
- `Monitor.schema` type enum ✓
- `NetworkService.requestDocker()` ✓
- `Check.schema` with type ✓
### 2. Docker-Specific Data Collection
Currently: Only status + response time
**To Add Detailed Metrics:**
- Extend `Check.schema` with Docker stats fields
- Modify `NetworkService.requestDocker()` to call `container.stats()`
- Stream stats to `Check.docker` field
### 3. Capture API Integration (Hardware Monitoring)
**Flow:**
```
Monitor with type="hardware" and url="http://capture-endpoint"
↓
NetworkService.requestHardware()
↓ (delegates to)
NetworkService.requestHttp()
↓
Expects JSON response: { cpu, memory, disk, net, ... }
↓
Check record stores full payload in hardware fields
```
### 4. Database Queries
**Get hardware checks:**
```javascript
Check.find({
monitorId: id,
type: "hardware",
createdAt: { $gte: startDate, $lte: endDate }
})
.sort({ createdAt: -1 })
.limit(100)
```
### 5. UI Display
- Stats dashboard: `/infrastructure/:monitorId`
- Uptime details: `/monitor/uptime/details/:monitorId`
- Hardware details: `/monitor/hardware/details/:monitorId`
---
## SERVICE ARCHITECTURE
### Service Registry Pattern
**Location:** `/server/src/service/v1/system/serviceRegistry.js`
```javascript
// Singleton pattern
ServiceRegistry.set(serviceName, serviceInstance);
const service = ServiceRegistry.get(serviceName);
```
### Service Dependencies
**Initialization:** `/server/src/config/services.js`
```javascript
// Infrastructure services
new NetworkService({
axios, got, https, jmespath, GameDig, ping,
logger, http, Docker, // ← Docker client injected
net, stringService, settingsService
})
// Business services
new MonitorService({
db, settingsService, jobQueue,
stringService, emailService, logger
})
```
---
## KEY FILES SUMMARY
| Component | File | Lines | Purpose |
|-----------|------|-------|---------|
| Monitor Model | `server/src/db/v1/models/Monitor.js` | 209 | Schema, hooks |
| Check Model | `server/src/db/v1/models/Check.js` | 177 | Check records, TTL |
| NetworkService | `server/src/service/v1/infrastructure/networkService.js` | 580 | Type handlers |
| MonitorService | `server/src/service/v1/business/monitorService.js` | 277 | CRUD, validation |
| MonitorModule | `server/src/db/v1/modules/monitorModule.js` | 593 | DB queries |
| JobQueue | `server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js` | 177 | Job scheduling |
| MonitorController | `server/src/controllers/v1/monitorController.js` | 475 | HTTP handlers |
| MonitorRoute | `server/src/routes/v1/monitorRoute.js` | 61 | Endpoint definitions |
| Frontend Hooks | `client/src/Hooks/v1/monitorHooks.js` | 500+ | Data fetching |
| NetworkService (FE) | `client/src/Utils/NetworkService.js` | 1157 | API client |
| Form Hook | `client/src/Pages/v1/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx` | 97 | Form state |
---
## DEPLOYMENT & DOCKER INTEGRATION
### Docker-Compose Setup
**Files:** `/docker/dev/docker-compose.yaml`, `/docker/prod/docker-compose.yaml`
**Volumes for Docker Monitoring:**
```yaml
volumes:
- /var/run/docker.sock:/var/run/docker.sock # Required for Dockerode
```
### Environment Variables
**Backend (.env):**
```
MONGODB_URI=mongodb://mongo:27017/checkmate
JWT_SECRET=your-secret-key
NODE_ENV=production|development
PORT=5000
```
**Frontend (.env):**
```
VITE_APP_API_BASE_URL=http://localhost:5000/api/v1
```
+145
View File
@@ -0,0 +1,145 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Checkmate is an open-source uptime and infrastructure monitoring application. It monitors server hardware, uptime, response times, and incidents with real-time alerts. The companion agent [Capture](https://github.com/bluewave-labs/capture) provides infrastructure metrics (CPU, RAM, disk, temperature).
## Development Commands
### Client (React/Vite)
```bash
cd client
npm install
npm run dev # Start dev server at http://localhost:5173
npm run build # TypeScript check + production build
npm run lint # ESLint (strict, max-warnings 0)
npm run format # Prettier formatting
npm run format-check # Check formatting
```
### Server (Node.js/Express)
```bash
cd server
npm install
npm run dev # Start with hot-reload (nodemon + tsx) at http://localhost:52345
npm run build # TypeScript compile + path alias resolution
npm run test # Run Mocha tests with c8 coverage
npm run lint # ESLint v9
npm run lint-fix # Auto-fix lint issues
npm run format # Prettier formatting
```
### Docker Development
```bash
cd docker/dev
./build_images.sh
docker run -d -p 27017:27017 -v uptime_mongo_data:/data/db --name uptime_database_mongo mongo:6.0
```
## Environment Setup
### Server `.env` (minimum required)
```env
CLIENT_HOST="http://localhost:5173"
JWT_SECRET="my_secret_key_change_this"
DB_CONNECTION_STRING="mongodb://localhost:27017/uptime_db"
TOKEN_TTL="99d"
ORIGIN="localhost"
LOG_LEVEL="debug"
```
### Client `.env`
```env
VITE_APP_API_BASE_URL="http://localhost:52345/api/v1"
VITE_APP_LOG_LEVEL="debug"
```
## Architecture
### Monorepo Structure
- `/client` - React 18 + TypeScript + Vite + MUI frontend
- `/server` - Node.js 20+ + Express + TypeScript backend
- `/docker` - Multi-environment Docker configs (dev, staging, prod, arm, mono)
### Backend Layers
```
server/src/
├── controllers/ # Route handlers (authController, monitorController, etc.)
├── service/ # Business logic
│ ├── business/ # Core monitoring logic
│ ├── infrastructure/ # Server/system utilities
│ └── system/ # App-level settings
├── db/
│ ├── models/ # Mongoose schemas (Monitor, Check, Incident, User, etc.)
│ ├── migration/ # Database migrations (run on startup)
│ └── modules/ # Database-specific modules
├── middleware/v1/ # verifyJWT, rateLimiter, sanitization, responseHandler
├── routes/v1/ # API route definitions
├── validation/ # Joi input validation schemas
└── repositories/ # Data access layer
```
### Frontend Structure
```
client/src/
├── Components/ # Reusable UI components
├── Pages/ # Page components (Auth, Uptime, Infrastructure, Incidents, etc.)
├── Features/ # Redux slices (Auth, UI)
├── Hooks/ # Custom React hooks
├── Utils/ # Utilities (NetworkService.js is main API client)
├── Validation/ # Input validation
└── locales/ # i18n translations
```
### API
- Base URL: `/api/v1`
- Documentation: `http://localhost:52345/api-docs` (Swagger UI)
- OpenAPI spec: `/server/openapi.json`
### Key Technologies
- **State Management**: Redux Toolkit + Redux-Persist
- **Data Fetching**: SWR + Axios
- **Database**: MongoDB with Mongoose ODM
- **Queue/Cache**: Redis + BullMQ + Pulse (cron scheduling)
- **i18n**: i18next + react-i18next (translations via PoEditor)
## Code Conventions
### Internationalization
All user-facing strings must use the translation function:
```javascript
t('your.key') // Never hardcode UI strings
```
### Branching
- Always branch from `develop` (not master)
- Use descriptive names: `feat/add-alerts`, `fix/login-error`
- PRs target `develop` branch
### Formatting
- **Client**: Prettier with `printWidth: 90`, tabs, double quotes
- **Server**: Prettier with `printWidth: 150`, tabs, double quotes
- Both use ESLint with strict settings
### Testing
Server tests use Mocha + Chai + Sinon:
```bash
npm test # Run all tests with coverage
npm test -- --grep "pattern" # Run specific tests
```
Test files: `server/tests/**/*.test.js`
## Database Models
Key Mongoose models in `/server/src/db/models/`:
- **Monitor** - Monitoring configuration (website, infrastructure, port, etc.)
- **Check** - Individual monitoring check results
- **Incident** - Downtime incidents
- **User** - User accounts
- **Team** - Team/workspace management
- **StatusPage** - Public status pages
- **Notification** - Alert configuration (email, Discord, Slack, webhooks)
- **MaintenanceWindow** - Scheduled maintenance periods
- **AppSettings** - Global application settings
+25
View File
@@ -10,6 +10,31 @@ PS: **We work closely with contributors on our [Discord channel](https://discord
---
## 🚀 Quick Setup Checklist
Before you dive in, make sure you have these installed:
```bash
# Check Node.js (v16+ required)
node --version
# Check npm
npm --version
# Check Docker
docker --version
# Check Git
git --version
```
**New to contributing?** Start here:
1. Pick a [`good-first-issue`](https://github.com/bluewave-labs/checkmate/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
2. Comment that you'd like to work on it
3. Follow the [setup guide](#set-up-checkmate-locally) below
4. Join our [Discord](https://discord.com/invite/NAb6H3UTjK) if you get stuck
## Table of contents
- [How do I...?](#how-do-i)
+1
View File
@@ -91,6 +91,7 @@ Feel free to ask questions or share your ideas - we'd love to hear from you!
- Website monitoring
- Page speed monitoring
- Infrastructure monitoring (memory, disk usage, CPU performance, network etc) - requires [Capture](https://github.com/bluewave-labs/capture) agent
- Selective disk monitoring with mountpoint selection
- Docker monitoring
- Ping monitoring
- SSL monitoring
+73
View File
@@ -26,6 +26,7 @@ Edit `values.yaml` to update:
- Old: `persistence.redisSize` → New: `persistence.redis.size`
- Add: `persistence.mongo.storageClass` and `persistence.redis.storageClass` (leave empty for default)
- Secrets under the `secrets` section (`JWT_SECRET`, email credentials, API keys, etc.) — replace all change_me values
- **For TLS/HTTPS**: Configure ingress TLS settings (see section below)
### 3. Deploy the Helm chart
```bash
@@ -41,3 +42,75 @@ kubectl get svc
```
Once all pods are `Running` and `Ready`, you can access Checkmate via the configured ingress hosts.
## Enabling TLS/HTTPS with cert-manager
If you have [cert-manager](https://cert-manager.io/) installed in your cluster, you can enable automatic TLS certificate provisioning using Let's Encrypt or other certificate issuers.
### Prerequisites
- cert-manager installed in your cluster
- A ClusterIssuer or Issuer configured (e.g., `letsencrypt-prod`)
### Configuration
Edit `values.yaml` to enable TLS (and update protocols to https):
```yaml
client:
protocol: https
ingress:
enabled: true
host: checkmate.example.com
className: nginx
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
tls:
enabled: true
secretName: checkmate-client-tls
server:
protocol: https
ingress:
enabled: true
host: checkmate.example.com
className: nginx
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
tls:
enabled: true
secretName: checkmate-server-tls
```
### Alternative: Using --set flags
You can also enable TLS during installation using Helm's `--set` flags:
```bash
helm install checkmate ./charts/helm/checkmate \
--set client.protocol=https \
--set server.protocol=https \
--set client.ingress.annotations."cert-manager\.io/cluster-issuer"="letsencrypt-prod" \
--set client.ingress.tls.enabled=true \
--set client.ingress.tls.secretName=checkmate-client-tls \
--set server.ingress.annotations."cert-manager\.io/cluster-issuer"="letsencrypt-prod" \
--set server.ingress.tls.enabled=true \
--set server.ingress.tls.secretName=checkmate-server-tls
```
### Verification
After deployment, cert-manager will automatically create the TLS secrets. You can verify the certificate status:
```bash
# Check certificates
kubectl get certificates
# Check certificate details
kubectl describe certificate checkmate-client-tls
kubectl describe certificate checkmate-server-tls
# Verify the secrets were created
kubectl get secrets | grep checkmate-tls
```
The ingress will automatically use these secrets to enable HTTPS access to your Checkmate instance.
@@ -1,12 +0,0 @@
{{- if eq .Values.client.ingress.host "change_me" }}
{{- fail "client.ingress.host must be overridden and not set to 'change_me'" }}
{{- end }}
{{- if eq .Values.server.ingress.host "change_me" }}
{{- fail "server.ingress.host must be overridden and not set to 'change_me'" }}
{{- end }}
{{- $protocol := .Values.server.protocol }}
{{- if not (or (eq $protocol "http") (eq $protocol "https")) }}
{{- fail "server.protocol must be either 'http' or 'https'" }}
{{- end }}
@@ -11,6 +11,12 @@ metadata:
{{- end }}
spec:
ingressClassName: {{ .Values.client.ingress.className }}
{{- if .Values.client.ingress.tls.enabled }}
tls:
- hosts:
- {{ .Values.client.ingress.host }}
secretName: {{ default (printf "%s-client-tls" .Release.Name) .Values.client.ingress.tls.secretName }}
{{- end }}
rules:
- host: {{ .Values.client.ingress.host }}
http:
@@ -0,0 +1,48 @@
{{- if eq .Values.client.ingress.host "change_me" }}
{{- fail "client.ingress.host must be overridden and not set to 'change_me'" }}
{{- end }}
{{- if eq .Values.server.ingress.host "change_me" }}
{{- fail "server.ingress.host must be overridden and not set to 'change_me'" }}
{{- end }}
{{- $serverProtocol := .Values.server.protocol }}
{{- if not (or (eq $serverProtocol "http") (eq $serverProtocol "https")) }}
{{- fail "server.protocol must be either 'http' or 'https'" }}
{{- end }}
{{- $clientProtocol := .Values.client.protocol }}
{{- if not (or (eq $clientProtocol "http") (eq $clientProtocol "https")) }}
{{- fail "client.protocol must be either 'http' or 'https'" }}
{{- end }}
{{/* Enforce protocol when TLS is enabled to avoid mixed-content */}}
{{- if and .Values.client.ingress.tls.enabled (ne $clientProtocol "https") }}
{{- fail "client.ingress.tls.enabled is true but client.protocol is not 'https'. Set client.protocol: https to avoid mixed content." }}
{{- end }}
{{- if and .Values.server.ingress.tls.enabled (ne $serverProtocol "https") }}
{{- fail "server.ingress.tls.enabled is true but server.protocol is not 'https'. Set server.protocol: https to ensure correct API base URL." }}
{{- end }}
{{/* If client runs on https, API must also be https to avoid mixed content */}}
{{- if and (eq $clientProtocol "https") (ne $serverProtocol "https") }}
{{- fail "client.protocol is 'https' but server.protocol is not. Set server.protocol: https to prevent browser mixed-content issues." }}
{{- end }}
{{/* Fail early if TLS enabled without cert-manager annotations (cluster-issuer or issuer) */}}
{{- $cAnn := .Values.client.ingress.annotations | default dict }}
{{- $sAnn := .Values.server.ingress.annotations | default dict }}
{{- $clientHasIssuer := or (hasKey $cAnn "cert-manager.io/cluster-issuer") (hasKey $cAnn "cert-manager.io/issuer") }}
{{- $serverHasIssuer := or (hasKey $sAnn "cert-manager.io/cluster-issuer") (hasKey $sAnn "cert-manager.io/issuer") }}
{{- if and .Values.client.ingress.tls.enabled (not $clientHasIssuer) }}
{{- fail "client.ingress.tls.enabled is true but no cert-manager issuer annotation found. Add 'cert-manager.io/cluster-issuer' or 'cert-manager.io/issuer'." }}
{{- end }}
{{- if and .Values.server.ingress.tls.enabled (not $serverHasIssuer) }}
{{- fail "server.ingress.tls.enabled is true but no cert-manager issuer annotation found. Add 'cert-manager.io/cluster-issuer' or 'cert-manager.io/issuer'." }}
{{- end }}
{{/* Secret name can be omitted; we default to <release>-client|server-tls in templates */}}
@@ -18,6 +18,12 @@ metadata:
#nginx.ingress.kubernetes.io/cors-allow-credentials: "true"*/}}
spec:
ingressClassName: {{ .Values.server.ingress.className }}
{{- if .Values.server.ingress.tls.enabled }}
tls:
- hosts:
- {{ .Values.server.ingress.host }}
secretName: {{ default (printf "%s-server-tls" .Release.Name) .Values.server.ingress.tls.secretName }}
{{- end }}
rules:
- host: {{ .Values.server.ingress.host }}
http:
+18
View File
@@ -7,6 +7,15 @@ client:
host: change_me
className: nginx
annotations: {}
# Example annotations for cert-manager:
# annotations:
# cert-manager.io/cluster-issuer: "letsencrypt-prod"
tls:
enabled: false
# secretName: {{ .Release.Name }}-client-tls # Optional; defaults to <release>-client-tls if omitted
# Note: when enabling TLS, also set client.protocol: https and add
# a cert-manager issuer annotation (e.g. cert-manager.io/cluster-issuer: "letsencrypt-prod").
# The secret will be automatically created by cert-manager when using the cert-manager.io/cluster-issuer annotation
server:
image: ghcr.io/bluewave-labs/checkmate-backend:v3.2.0
@@ -17,6 +26,15 @@ server:
host: change_me
className: nginx
annotations: {}
# Example annotations for cert-manager:
# annotations:
# cert-manager.io/cluster-issuer: "letsencrypt-prod"
tls:
enabled: false
# secretName: {{ .Release.Name }}-server-tls # Optional; defaults to <release>-server-tls if omitted
# Note: when enabling TLS, also set server.protocol: https and add
# a cert-manager issuer annotation (e.g. cert-manager.io/cluster-issuer: "letsencrypt-prod").
# The secret will be automatically created by cert-manager when using the cert-manager.io/cluster-issuer annotation
redis:
enabled: false
+2 -1
View File
@@ -28,4 +28,5 @@ dist-ssr
# Scripts
*.sh
!env.sh
!env.sh
.env.development
+651 -64
View File
@@ -12,7 +12,6 @@
"@emotion/styled": "^11.13.0",
"@hello-pangea/dnd": "^18.0.0",
"@hookform/resolvers": "5.2.2",
"@mui/icons-material": "6.4.11",
"@mui/lab": "6.0.0-dev.240424162023-9968b4889d",
"@mui/material": "6.4.11",
"@mui/x-charts": "^7.5.1",
@@ -25,8 +24,9 @@
"human-interval": "2.0.1",
"i18next": "25.4.2",
"joi": "17.13.3",
"lucide-react": "^0.562.0",
"mui-color-input": "^6.0.0",
"pretty-ms": "9.3.0",
"pretty-ms": "^9.3.0",
"react": "18.3.1",
"react-dom": "^18.2.0",
"react-hook-form": "7.63.0",
@@ -38,7 +38,7 @@
"react-toastify": "^10.0.5",
"recharts": "2.15.2",
"redux-persist": "6.0.0",
"swr": "2.3.6",
"swr": "^2.3.6",
"vite-plugin-svgr": "^4.2.0",
"zod": "4.1.11"
},
@@ -97,6 +97,7 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz",
"integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.27.1",
@@ -408,6 +409,7 @@
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5",
@@ -451,6 +453,7 @@
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz",
"integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5",
@@ -496,6 +499,262 @@
"integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
"license": "MIT"
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz",
"integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==",
"cpu": [
"ppc64"
],
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz",
"integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz",
"integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz",
"integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz",
"integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz",
"integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz",
"integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz",
"integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz",
"integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz",
"integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz",
"integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==",
"cpu": [
"ia32"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz",
"integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==",
"cpu": [
"loong64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz",
"integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==",
"cpu": [
"mips64el"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz",
"integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==",
"cpu": [
"ppc64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz",
"integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==",
"cpu": [
"riscv64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz",
"integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==",
"cpu": [
"s390x"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz",
@@ -512,6 +771,134 @@
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz",
"integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz",
"integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz",
"integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz",
"integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz",
"integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz",
"integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz",
"integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==",
"cpu": [
"ia32"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz",
"integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@eslint-community/eslint-utils": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
@@ -802,32 +1189,6 @@
"url": "https://opencollective.com/mui-org"
}
},
"node_modules/@mui/icons-material": {
"version": "6.4.11",
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.11.tgz",
"integrity": "sha512-+jjJGIrB1awNbMv4ZVPPdN/p7O1UKFZ+xqRvNIQ8B1KnlID5hPMPBLM6UUbRF4bu3UDCbu79rn9Nye5LGNzmeA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@mui/material": "^6.4.11",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/lab": {
"version": "6.0.0-dev.240424162023-9968b4889d",
"resolved": "https://registry.npmjs.org/@mui/lab/-/lab-6.0.0-dev.240424162023-9968b4889d.tgz",
@@ -989,6 +1350,7 @@
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.11.tgz",
"integrity": "sha512-k2D3FLJS+/qD0qnd6ZlAjGFvaaxe1Dl10NyvpeDzIebMuYdn8VqYe6XBgGueEAtnzSJM4V03VD9kb5Fi24dnTA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.26.0",
"@mui/core-downloads-tracker": "^6.4.11",
@@ -1153,7 +1515,6 @@
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.1.0.tgz",
"integrity": "sha512-4Kck4jxhqF6YxNwJdSae1WgDfXVg0lIH6JVJ7gtuFfuKcQCgomJxPvUEOySTFRPz1IZzwz5OAcToskRdffElDA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.27.1",
"@mui/utils": "^7.1.0",
@@ -1181,7 +1542,6 @@
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.1.0.tgz",
"integrity": "sha512-/OM3S8kSHHmWNOP+NH9xEtpYSG10upXeQ0wLZnfDgmgadTAk5F4MQfFLyZ5FCRJENB3eRzltMmaNl6UtDnPovw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.27.1",
"@mui/types": "^7.4.2",
@@ -1212,7 +1572,6 @@
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.1.0.tgz",
"integrity": "sha512-m0mJ0c6iRC+f9hMeRe0W7zZX1wme3oUX0+XTVHjPG7DJz6OdQ6K/ggEOq7ZdwilcpdsDUwwMfOmvO71qDkYd2w==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.27.1",
"@emotion/cache": "^11.13.5",
@@ -1288,7 +1647,6 @@
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.1.0.tgz",
"integrity": "sha512-/OM3S8kSHHmWNOP+NH9xEtpYSG10upXeQ0wLZnfDgmgadTAk5F4MQfFLyZ5FCRJENB3eRzltMmaNl6UtDnPovw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.27.1",
"@mui/types": "^7.4.2",
@@ -1666,9 +2024,9 @@
}
},
"node_modules/@remix-run/router": {
"version": "1.23.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz",
"integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==",
"version": "1.23.2",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz",
"integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==",
"license": "MIT",
"engines": {
"node": ">=14.0.0"
@@ -1696,6 +2054,123 @@
}
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.0.tgz",
"integrity": "sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.0.tgz",
"integrity": "sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.0.tgz",
"integrity": "sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.0.tgz",
"integrity": "sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.0.tgz",
"integrity": "sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.0.tgz",
"integrity": "sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.0.tgz",
"integrity": "sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.0.tgz",
"integrity": "sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.0.tgz",
"integrity": "sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.0.tgz",
@@ -1709,6 +2184,71 @@
"linux"
]
},
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.0.tgz",
"integrity": "sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==",
"cpu": [
"loong64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.0.tgz",
"integrity": "sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==",
"cpu": [
"ppc64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.0.tgz",
"integrity": "sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==",
"cpu": [
"riscv64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.0.tgz",
"integrity": "sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==",
"cpu": [
"riscv64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.0.tgz",
"integrity": "sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==",
"cpu": [
"s390x"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.0.tgz",
@@ -1735,6 +2275,45 @@
"linux"
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.0.tgz",
"integrity": "sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.0.tgz",
"integrity": "sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==",
"cpu": [
"ia32"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.0.tgz",
"integrity": "sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@sideway/address": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
@@ -2133,6 +2712,7 @@
"integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~7.12.0"
}
@@ -2154,6 +2734,7 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.22.tgz",
"integrity": "sha512-vUhG0YmQZ7kL/tmKLrD3g5zXbXXreZXB3pmROW8bg3CnLnpjkRVwUlLne7Ufa2r9yJ8+/6B73RzhAek5TBKh2Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
@@ -2217,6 +2798,7 @@
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2523,6 +3105,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001716",
"electron-to-chromium": "^1.5.149",
@@ -2935,7 +3518,8 @@
"version": "1.11.13",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/debug": {
"version": "4.4.1",
@@ -3342,6 +3926,7 @@
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
@@ -4149,6 +4734,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.27.6"
},
@@ -4685,9 +5271,9 @@
"license": "MIT"
},
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
@@ -4852,6 +5438,15 @@
"yallist": "^3.0.2"
}
},
"node_modules/lucide-react": {
"version": "0.562.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.562.0.tgz",
"integrity": "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==",
"license": "ISC",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -5416,6 +6011,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
@@ -5429,6 +6025,7 @@
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.63.0.tgz",
"integrity": "sha512-ZwueDMvUeucovM2VjkCf7zIHcs1aAlDimZu2Hvel5C5907gUzMpm4xCrQXtRzCvsBqFjonB4m3x4LzCFI1ZKWA==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18.0.0"
},
@@ -5515,12 +6112,12 @@
}
},
"node_modules/react-router": {
"version": "6.30.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz",
"integrity": "sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==",
"version": "6.30.3",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz",
"integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==",
"license": "MIT",
"dependencies": {
"@remix-run/router": "1.23.0"
"@remix-run/router": "1.23.2"
},
"engines": {
"node": ">=14.0.0"
@@ -5530,13 +6127,13 @@
}
},
"node_modules/react-router-dom": {
"version": "6.30.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.1.tgz",
"integrity": "sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==",
"version": "6.30.3",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz",
"integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==",
"license": "MIT",
"dependencies": {
"@remix-run/router": "1.23.0",
"react-router": "6.30.1"
"@remix-run/router": "1.23.2",
"react-router": "6.30.3"
},
"engines": {
"node": ">=14.0.0"
@@ -5632,7 +6229,8 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/redux-persist": {
"version": "6.0.0",
@@ -5770,6 +6368,7 @@
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.0.tgz",
"integrity": "sha512-HqMFpUbWlf/tvcxBFNKnJyzc7Lk+XO3FGc3pbNBLqEbOz0gPLRgcrlS3UF4MfUrVlstOaP/q0kM6GVvi+LrLRg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/estree": "1.0.7"
},
@@ -6405,6 +7004,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -6524,6 +7124,7 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz",
"integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==",
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
@@ -6744,20 +7345,6 @@
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"license": "ISC"
},
"node_modules/yaml": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz",
"integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==",
"license": "ISC",
"optional": true,
"peer": true,
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14.6"
}
},
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+3 -3
View File
@@ -17,7 +17,6 @@
"@emotion/styled": "^11.13.0",
"@hello-pangea/dnd": "^18.0.0",
"@hookform/resolvers": "5.2.2",
"@mui/icons-material": "6.4.11",
"@mui/lab": "6.0.0-dev.240424162023-9968b4889d",
"@mui/material": "6.4.11",
"@mui/x-charts": "^7.5.1",
@@ -30,8 +29,9 @@
"human-interval": "2.0.1",
"i18next": "25.4.2",
"joi": "17.13.3",
"lucide-react": "^0.562.0",
"mui-color-input": "^6.0.0",
"pretty-ms": "9.3.0",
"pretty-ms": "^9.3.0",
"react": "18.3.1",
"react-dom": "^18.2.0",
"react-hook-form": "7.63.0",
@@ -43,7 +43,7 @@
"react-toastify": "^10.0.5",
"recharts": "2.15.2",
"redux-persist": "6.0.0",
"swr": "2.3.6",
"swr": "^2.3.6",
"vite-plugin-svgr": "^4.2.0",
"zod": "4.1.11"
},
+18 -2
View File
@@ -22,13 +22,29 @@ function App() {
};
}, []);
const theme = mode === "light" ? lightTheme : darkTheme;
return (
<ThemeProvider theme={mode === "light" ? lightTheme : darkTheme}>
<ThemeProvider theme={theme}>
<CssBaseline />
<GlobalStyles
styles={{
body: {
backgroundColor: theme.palette.background.main,
},
}}
/>
<AppLayout>
<Routes />
</AppLayout>
<ToastContainer />
<ToastContainer
newestOnTop={true}
theme={mode}
style={{
"--toastify-color-progress-light": "#7C8BA1",
"--toastify-color-progress-dark": "#7C8BA1",
}}
/>
</ThemeProvider>
);
}
+1 -1
View File
@@ -16,7 +16,7 @@ const langMap = {
const LanguageSelector = () => {
const { i18n } = useTranslation();
const theme = useTheme();
const { language } = useSelector((state) => state.ui);
const { language = "en" } = useSelector((state) => state.ui);
const dispatch = useDispatch();
const handleChange = (event) => {
const newLang = event.target.value;
+11 -7
View File
@@ -2,7 +2,7 @@
import IconButton from "@mui/material/IconButton";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import Settings from "../../../assets/icons/settings-bold.svg?react";
import Icon from "../Icon";
import Dialog from "../Dialog/index.jsx";
// Utils
@@ -12,7 +12,7 @@ import { useNavigate } from "react-router-dom";
import { createToast } from "../../../Utils/toastUtils.jsx";
import PropTypes from "prop-types";
import { usePauseMonitor, useDeleteMonitor } from "../../../Hooks/v1/monitorHooks.js";
import { usePauseMonitor, useDeleteMonitor } from "../../../Hooks/monitorHooks.js";
const ActionsMenu = ({
monitor,
@@ -31,7 +31,7 @@ const ActionsMenu = ({
const handleRemove = async (event) => {
event.preventDefault();
event.stopPropagation();
let monitor = { _id: actions.id };
let monitor = { id: actions.id };
await deleteMonitor({ monitor });
updateRowCallback();
};
@@ -39,7 +39,7 @@ const ActionsMenu = ({
const handlePause = async () => {
try {
setIsLoading(true);
await pauseMonitor({ monitorId: monitor._id });
await pauseMonitor({ monitorId: monitor.id });
pauseCallback();
} catch (error) {
createToast({ body: "Failed to pause monitor." });
@@ -72,7 +72,7 @@ const ActionsMenu = ({
aria-label="monitor actions"
onClick={(event) => {
event.stopPropagation();
openMenu(event, monitor._id, monitor.type === "ping" ? null : monitor.url);
openMenu(event, monitor.id, monitor.type === "ping" ? null : monitor.url);
}}
sx={{
"&:focus": {
@@ -83,7 +83,10 @@ const ActionsMenu = ({
},
}}
>
<Settings />
<Icon
name="Settings"
size={20}
/>
</IconButton>
<Menu
@@ -197,6 +200,7 @@ const ActionsMenu = ({
confirmationButtonLabel="Delete"
/* Do we need stop propagation? */
onConfirm={(e) => {
console.log(e);
e.stopPropagation();
handleRemove(e);
}}
@@ -210,7 +214,7 @@ const ActionsMenu = ({
ActionsMenu.propTypes = {
monitor: PropTypes.shape({
_id: PropTypes.string,
id: PropTypes.string,
url: PropTypes.string,
type: PropTypes.string,
isActive: PropTypes.bool,
+22 -11
View File
@@ -1,10 +1,7 @@
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { Box, Button, IconButton, Stack, Typography } from "@mui/material";
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
import ErrorOutlineOutlinedIcon from "@mui/icons-material/ErrorOutlineOutlined";
import WarningAmberOutlinedIcon from "@mui/icons-material/WarningAmberOutlined";
import CloseIcon from "@mui/icons-material/Close";
import Icon from "../Icon";
import "./index.css";
/**
@@ -13,9 +10,24 @@ import "./index.css";
*/
const icons = {
info: <InfoOutlinedIcon />,
error: <ErrorOutlineOutlinedIcon />,
warning: <WarningAmberOutlinedIcon />,
info: (
<Icon
name="Info"
size={24}
/>
),
error: (
<Icon
name="AlertCircle"
size={24}
/>
),
warning: (
<Icon
name="AlertTriangle"
size={24}
/>
),
};
/**
@@ -97,10 +109,9 @@ const Alert = ({ variant, title, body, isToast, hasIcon = true, onClick }) => {
},
}}
>
<CloseIcon
sx={{
fontSize: "20px",
}}
<Icon
name="X"
size={20}
/>
</IconButton>
)}
+10 -9
View File
@@ -1,27 +1,28 @@
import LeftArrow from "../../../assets/icons/left-arrow.svg?react";
import LeftArrowDouble from "../../../assets/icons/left-arrow-double.svg?react";
import LeftArrowLong from "../../../assets/icons/left-arrow-long.svg?react";
import Icon from "../Icon";
import PropTypes from "prop-types";
const ArrowLeft = ({ type, color = "#667085", ...props }) => {
if (type === "double") {
return (
<LeftArrowDouble
style={{ color }}
<Icon
name="ChevronsLeft"
color={color}
{...props}
/>
);
} else if (type === "long") {
return (
<LeftArrowLong
style={{ color }}
<Icon
name="ArrowLeft"
color={color}
{...props}
/>
);
} else {
return (
<LeftArrow
style={{ color }}
<Icon
name="ChevronLeft"
color={color}
{...props}
/>
);
@@ -1,19 +1,20 @@
import RightArrow from "../../../assets/icons/right-arrow.svg?react";
import RightArrowDouble from "../../../assets/icons/right-arrow-double.svg?react";
import Icon from "../Icon";
import PropTypes from "prop-types";
const ArrowRight = ({ type, color = "#667085", ...props }) => {
if (type === "double") {
return (
<RightArrowDouble
style={{ color }}
<Icon
name="ChevronsRight"
color={color}
{...props}
/>
);
} else {
return (
<RightArrow
style={{ color }}
<Icon
name="ChevronRight"
color={color}
{...props}
/>
);
@@ -1,6 +1,6 @@
import { useTheme } from "@emotion/react";
import { Box, Stack, Tooltip, Typography } from "@mui/material";
import { formatDateWithTz } from "../../../../Utils/timeUtils.js";
import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js";
import { useEffect, useState } from "react";
import "./index.css";
import { useSelector } from "react-redux";
@@ -103,7 +103,7 @@ const BarChart = ({ checks = [] }) => {
</>
}
placement="top"
key={`check-${check?._id}`}
key={`check-${check?.id}`}
slotProps={{
popper: {
className: "bar-tooltip",
@@ -145,7 +145,7 @@ const BarChart = ({ checks = [] }) => {
position="relative"
width="9px"
height="100%"
backgroundColor={theme.palette.primary.lowContrast} // CAIO_REVIEW
backgroundColor={theme.palette.primary.lowContrast}
sx={{
borderRadius: theme.spacing(1.5),
/*
@@ -5,7 +5,7 @@ import { Stack, Box, Tooltip, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import { useState, useEffect } from "react";
import PropTypes from "prop-types";
import { formatDateWithTz } from "../../../../Utils/timeUtils.js";
import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js";
import { useSelector } from "react-redux";
const PlaceholderCheck = ({ daysToShow }) => {
@@ -12,7 +12,7 @@ import { Box, Stack, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import { useMemo, useState } from "react";
import { useSelector } from "react-redux";
import { formatDateWithTz } from "../../../../Utils/timeUtils.js";
import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js";
import {
tooltipDateFormatLookup,
tickDateFormatLookup,
@@ -1,6 +1,6 @@
import { useTheme } from "@emotion/react";
import { Box, Stack, Tooltip, Typography } from "@mui/material";
import { formatDateWithTz } from "../../../../Utils/timeUtils.js";
import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js";
import { useEffect, useState, forwardRef } from "react";
import { useSelector } from "react-redux";
import PropTypes from "prop-types";
@@ -148,7 +148,7 @@ const StatusPageBarChart = ({ checks = [] }) => {
</>
}
placement="top"
key={`check-${check?._id}`}
key={`check-${check?.id}`}
slotProps={{
popper: {
className: "bar-tooltip",
@@ -2,7 +2,7 @@ import PropTypes from "prop-types";
import { useSelector } from "react-redux";
import { useTheme } from "@mui/material";
import { Text } from "recharts";
import { formatDateWithTz } from "../../../../Utils/timeUtils.js";
import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js";
import { Box, Stack, Typography } from "@mui/material";
import { tickDateFormatLookup, tooltipDateFormatLookup } from "./chartUtilFunctions.js";
/**
+18 -12
View File
@@ -1,7 +1,6 @@
import "./check.css";
import PropTypes from "prop-types";
import CheckGrey from "../../../assets/icons/check.svg?react";
import CheckOutlined from "../../../assets/icons/check-outlined.svg?react";
import Icon from "../Icon";
import { Box, Stack, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
@@ -35,16 +34,16 @@ const Check = ({ text, noHighlightText, variant = "info", outlined = false }) =>
alignItems="center"
>
{outlined ? (
<CheckOutlined alt="check" />
<Icon
name="CheckCircle"
size={20}
/>
) : (
<Box
lineHeight={0}
sx={{
"& svg > path": { fill: colors[variant] },
}}
>
<CheckGrey alt="form checks" />
</Box>
<Icon
name="Check"
size={16}
color={colors[variant]}
/>
)}
<Typography
component="span"
@@ -57,7 +56,14 @@ const Check = ({ text, noHighlightText, variant = "info", outlined = false }) =>
fontWeight: 450,
}}
>
{noHighlightText && <Typography component="span">{noHighlightText}</Typography>}{" "}
{noHighlightText && (
<Typography
component="span"
sx={{ color: "inherit" }}
>
{noHighlightText}
</Typography>
)}{" "}
{text}
</Typography>
</Stack>
+11 -4
View File
@@ -8,8 +8,7 @@ import IconButton from "@mui/material/IconButton";
import Container from "@mui/material/Container";
import MenuItem from "@mui/material/MenuItem";
import Drawer from "@mui/material/Drawer";
import MenuIcon from "@mui/icons-material/Menu";
import CloseRoundedIcon from "@mui/icons-material/CloseRounded";
import Icon from "../Icon";
import ThemeSwitch from "../ThemeSwitch/index.jsx";
import { useTheme } from "@mui/material/styles";
import { useSelector } from "react-redux";
@@ -153,7 +152,11 @@ const AppAppBar = () => {
aria-label="Menu button"
onClick={toggleDrawer(true)}
>
<MenuIcon sx={{ color: theme.palette.text.primary }} />
<Icon
name="Menu"
size={24}
color={theme.palette.text.primary}
/>
</IconButton>
<Drawer
anchor="top"
@@ -176,7 +179,11 @@ const AppAppBar = () => {
}}
>
<IconButton onClick={toggleDrawer(false)}>
<CloseRoundedIcon sx={{ color: theme.palette.text.primary }} />
<Icon
name="X"
size={24}
color={theme.palette.text.primary}
/>
</IconButton>
</Box>
@@ -1,7 +1,7 @@
import { Checkbox, FormControl, ListItemText, MenuItem, Select } from "@mui/material";
import { useTheme } from "@emotion/react";
import PropTypes from "prop-types";
import AddCircleOutlineIcon from "@mui/icons-material/AddCircleOutline";
import Icon from "../Icon";
/**
* A reusable filter header component that displays a dropdown menu with selectable options.
@@ -29,9 +29,10 @@ const FilterHeader = ({ header, options, value, onChange, multiple = true }) =>
<Select
multiple={multiple}
IconComponent={(props) => (
<AddCircleOutlineIcon
<Icon
{...props}
sx={{ fontSize: "medium" }}
name="PlusCircle"
size={18}
/>
)}
displayEmpty
@@ -40,13 +40,13 @@ const GenericFallback = ({ children }) => {
<Box
component="img"
src={mode === "light" ? OutputAnimation : DarkmodeOutput}
Background="transparent"
alt="Loading animation"
sx={{
zIndex: 1,
border: "none",
borderRadius: theme.spacing(8),
width: "100%",
backgroundColor: "transparent",
}}
/>
<Box
+5 -5
View File
@@ -23,12 +23,12 @@ const Host = ({ url, title, percentageColor, percentage, showURL, status }) => {
direction="row"
position="relative"
alignItems="center"
gap={theme.spacing(5)}
gap={theme.spacing(4)}
>
<Typography
variant="h6"
component="span"
sx={{
fontWeight: 600,
fontWeight: 500,
}}
>
{title}
@@ -38,9 +38,9 @@ const Host = ({ url, title, percentageColor, percentage, showURL, status }) => {
<>
<Dot />
<Typography
variant="h6"
component="span"
sx={{
fontWeight: 600,
fontWeight: 500,
color: percentageColor,
}}
>
@@ -2,7 +2,7 @@ import i18n from "../../../Utils/i18n.js";
import { useSelector } from "react-redux";
import { useEffect } from "react";
const I18nLoader = () => {
const language = useSelector((state) => state.ui.language);
const language = useSelector((state) => state.ui.language ?? "en");
useEffect(() => {
if (language && i18n.language !== language) {
+120
View File
@@ -0,0 +1,120 @@
/**
* Maps legacy icon names to Lucide icon names
* Use: import { iconMap } from './iconMap'; LucideIcons[iconMap['monitors']]
*/
export const iconMap = {
// Navigation/Sidebar icons
monitors: "Globe",
incidents: "AlertTriangle",
integrations: "Link",
"page-speed": "Gauge",
settings: "Settings",
notifications: "Bell",
maintenance: "Wrench",
"status-pages": "Wifi",
docs: "FileText",
discussions: "MessageCircle",
support: "HelpCircle",
changeLog: "Code",
logs: "Database",
// User icons
user: "User",
"user-two": "Users",
"user-edit": "UserPen",
groups: "Users",
lock: "Lock",
key: "Key",
logout: "LogOut",
// Action icons
edit: "Pencil",
"trash-bin": "Trash2",
search: "Search",
"close-icon": "X",
check: "Check",
"check-icon": "CheckCircle",
"check-outlined": "CheckCircle",
// Arrow icons
"left-arrow": "ChevronLeft",
"right-arrow": "ChevronRight",
"up-arrow": "ChevronUp",
"down-arrow": "ChevronDown",
"left-arrow-double": "ChevronsLeft",
"right-arrow-double": "ChevronsRight",
"left-arrow-long": "ArrowLeft",
"top-right-arrow": "ArrowUpRight",
"open-in-new-page": "ExternalLink",
"selector-vertical": "ChevronsUpDown",
// Status/Alert icons
"alert-icon": "Bell",
"warning-icon": "AlertCircle",
"history-icon": "History",
// Data/Chart icons
dashboard: "LayoutGrid",
"monitor-graph-line": "TrendingUp",
"response-time-icon": "TrendingUp",
"uptime-icon": "Activity",
"speedometer-icon": "Gauge",
"spedometer-icon": "Gauge",
"ruler-icon": "Ruler",
"performance-report": "Layers",
"average-response-icon": "BarChart3",
"distributed-uptime": "Network",
// Misc icons
calendar: "Calendar",
"calendar-check": "CalendarCheck",
"clock-snooze": "Clock",
mail: "Mail",
email: "Mail",
folder: "Folder",
"dots-vertical": "MoreVertical",
"pause-icon": "PauseCircle",
"resume-icon": "PlayCircle",
"checkbox-filled": "CheckSquare",
"checkbox-outline": "Square",
"radio-checked": "Circle",
"cpu-chip": "Cpu",
certificate: "Award",
"interval-check": "RefreshCw",
};
/**
* MUI icon to Lucide mapping
*/
export const muiToLucideMap = {
AddCircleOutline: "PlusCircle",
ArrowDownwardRounded: "ArrowDown",
ArrowUpwardRounded: "ArrowUp",
ArrowDropDown: "ChevronDown",
ArrowOutward: "ExternalLink",
CheckCircle: "CheckCircle2",
Clear: "X",
Close: "X",
CloseRounded: "X",
CloudUpload: "Upload",
Delete: "Trash2",
DeleteOutlineRounded: "Trash2",
Email: "Mail",
ErrorOutline: "AlertTriangle",
ErrorOutlineOutlined: "AlertTriangle",
Image: "ImageIcon",
InfoOutlined: "Info",
KeyboardArrowDown: "ChevronDown",
Menu: "Menu",
PauseCircleOutline: "PauseCircle",
PauseOutlined: "Pause",
PlayArrowOutlined: "Play",
PlayCircleOutlineRounded: "PlayCircle",
RadioButtonChecked: "Circle",
ReorderRounded: "GripVertical",
SettingsOutlined: "Settings",
Share: "Share2",
Visibility: "Eye",
VisibilityOff: "EyeOff",
WarningAmberOutlined: "AlertTriangle",
};
+198
View File
@@ -0,0 +1,198 @@
import { useTheme } from "@mui/material";
import PropTypes from "prop-types";
import {
Activity,
AlertCircle,
AlertTriangle,
ArrowDown,
ArrowLeft,
ArrowUp,
ArrowUpRight,
Bell,
Calendar,
Check,
CheckCircle,
CheckCircle2,
CheckSquare,
ChevronDown,
ChevronLeft,
ChevronRight,
ChevronsLeft,
ChevronsRight,
ChevronsUpDown,
Circle,
CircleDot,
Clock,
Code,
Cpu,
Database,
ExternalLink,
Eye,
EyeOff,
FileText,
Gauge,
Globe,
GripVertical,
HelpCircle,
History,
Image as ImageIcon,
Info,
Key,
Layers,
Link,
Lock,
LogOut,
Mail,
Menu,
MessageCircle,
MoreVertical,
Pause,
PauseCircle,
Play,
PlayCircle,
PlusCircle,
RefreshCw,
Ruler,
Search,
Settings,
Square,
Trash2,
TrendingUp,
Upload,
User,
Users,
Wifi,
Wrench,
X,
} from "lucide-react";
/**
* Map of icon names to Lucide icon components.
* Only icons explicitly imported here will be included in the bundle (tree-shaking).
* To add a new icon: import it above and add it to this object.
*/
const iconComponents = {
Activity,
AlertCircle,
AlertTriangle,
ArrowDown,
ArrowLeft,
ArrowUp,
ArrowUpRight,
Bell,
Calendar,
Check,
CheckCircle,
CheckCircle2,
CheckSquare,
ChevronDown,
ChevronLeft,
ChevronRight,
ChevronsLeft,
ChevronsRight,
ChevronsUpDown,
Circle,
CircleDot,
Clock,
Code,
Cpu,
Database,
ExternalLink,
Eye,
EyeOff,
FileText,
Gauge,
Globe,
GripVertical,
HelpCircle,
History,
Image: ImageIcon,
Info,
Key,
Layers,
Link,
Lock,
LogOut,
Mail,
Menu,
MessageCircle,
MoreVertical,
Pause,
PauseCircle,
Play,
PlayCircle,
PlusCircle,
RefreshCw,
Ruler,
Search,
Settings,
Square,
Trash2,
TrendingUp,
Upload,
User,
Users,
Wifi,
Wrench,
X,
};
/**
* Theme-aware icon component wrapping Lucide icons
*
* @param {string} name - Lucide icon name (e.g., "Bell", "Check", "ArrowLeft")
* @param {number} size - Icon size in pixels (default: 20)
* @param {string} color - Direct color value OR theme path like "primary.contrastText"
* @param {number} strokeWidth - Stroke width (default: 1.5)
* @param {string} fill - Fill color (default: "none")
*/
const Icon = ({ name, size = 20, color, strokeWidth = 1.5, fill = "none", ...props }) => {
const theme = useTheme();
// Resolve color from theme path or use direct value
const resolveColor = (colorValue) => {
if (!colorValue) {
return theme.palette.primary.contrastTextTertiary; // Default icon color
}
// If it's a theme path like "primary.contrastText"
if (typeof colorValue === "string" && colorValue.includes(".")) {
const parts = colorValue.split(".");
let resolved = theme.palette;
for (const part of parts) {
resolved = resolved?.[part];
}
return resolved || colorValue;
}
// Direct color value
return colorValue;
};
const LucideIcon = iconComponents[name];
if (!LucideIcon) {
console.warn(`Icon "${name}" not found in Icon component`);
return null;
}
return (
<LucideIcon
size={size}
color={resolveColor(color)}
strokeWidth={strokeWidth}
fill={fill}
{...props}
/>
);
};
Icon.propTypes = {
name: PropTypes.string.isRequired,
size: PropTypes.number,
color: PropTypes.string,
strokeWidth: PropTypes.number,
fill: PropTypes.string,
};
export default Icon;
@@ -1,8 +1,7 @@
import PropTypes from "prop-types";
import { FormControlLabel, Checkbox as MuiCheckbox } from "@mui/material";
import { useTheme } from "@emotion/react";
import CheckboxOutline from "../../../../assets/icons/checkbox-outline.svg?react";
import CheckboxFilled from "../../../../assets/icons/checkbox-filled.svg?react";
import Icon from "../../Icon";
/**
* Checkbox Component
@@ -65,8 +64,8 @@ const Checkbox = ({
name={name}
value={value}
onChange={onChange}
icon={<CheckboxOutline />}
checkedIcon={<CheckboxFilled />}
icon={<Icon name="Square" />}
checkedIcon={<Icon name="CheckSquare" />}
inputProps={{
"aria-label": "controlled checkbox",
id: id,
@@ -1,11 +1,10 @@
// Components
import { Box, Stack, Typography } from "@mui/material";
import CloudUploadIcon from "@mui/icons-material/CloudUpload";
import Icon from "../../Icon";
import Image from "../../Image/index.jsx";
import TextField from "@mui/material/TextField";
import IconButton from "@mui/material/IconButton";
import ProgressUpload from "../../ProgressBars/index.jsx";
import ImageIcon from "@mui/icons-material/Image";
// Utils
import PropTypes from "prop-types";
@@ -176,7 +175,10 @@ const ImageUpload = ({
boxShadow: theme.shape.boxShadow,
}}
>
<CloudUploadIcon />
<Icon
name="Upload"
size={24}
/>
</IconButton>
<Typography
component="h2"
@@ -203,7 +205,12 @@ const ImageUpload = ({
</Box>
{(localError || progress.isLoading || progress.value !== 0) && (
<ProgressUpload
icon={<ImageIcon />}
icon={
<Icon
name="Image"
size={20}
/>
}
label={file?.name || "Upload failed"}
size={file?.size}
progress={progress.value}
@@ -1,7 +1,7 @@
import PropTypes from "prop-types";
import { FormControlLabel, Radio as MUIRadio, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import RadioChecked from "../../../../assets/icons/radio-checked.svg?react";
import Icon from "../../Icon";
import "./index.css";
/**
@@ -47,7 +47,12 @@ const Radio = ({
<MUIRadio
id={id}
size={size}
checkedIcon={<RadioChecked />}
checkedIcon={
<Icon
name="CircleDot"
size={16}
/>
}
sx={{
color: "transparent",
width: 16,
@@ -8,7 +8,7 @@ import {
Checkbox,
} from "@mui/material";
import { useTheme } from "@emotion/react";
import SearchIcon from "../../../../assets/icons/search.svg?react";
import Icon from "../../Icon";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import FieldWrapper from "../FieldWrapper/index.jsx";
@@ -31,20 +31,13 @@ import FieldWrapper from "../FieldWrapper/index.jsx";
const SearchAdornment = () => {
const theme = useTheme();
return (
<Box
height={16}
sx={{
"& svg": {
width: 16,
height: 16,
"& path": {
stroke: theme.palette.primary.contrastTextTertiary,
strokeWidth: 1.2,
},
},
}}
>
<SearchIcon />
<Box height={16}>
<Icon
name="Search"
size={16}
color="primary.contrastTextTertiary"
strokeWidth={1.2}
/>
</Box>
);
};
@@ -85,7 +78,12 @@ const Search = ({
const enhancedOptions = React.useMemo(() => {
return multiple && isAdorned
? [
{ [filteredBy]: t("selectAll"), isSelectAll: true, _id: "select_all" },
{
[filteredBy]: t("selectAll"),
isSelectAll: true,
_id: "select_all",
id: "select_all",
},
...options,
]
: options;
@@ -93,7 +91,7 @@ const Search = ({
const isOptionSelected = (option) => {
if (!multiple && !isAdorned) return false;
if (Array.isArray(value)) {
return value.some((item) => item._id === option._id);
return value.some((item) => (item._id ?? item.id) === (option._id ?? option.id));
}
return false;
};
@@ -145,7 +143,9 @@ const Search = ({
disableClearable
options={enhancedOptions}
getOptionLabel={(option) => option[filteredBy]}
isOptionEqualToValue={(option, value) => option._id === value._id} // Compare by unique identifier
isOptionEqualToValue={(option, value) =>
(option._id ?? option.id) === (value?._id ?? value?.id)
} // Compare by unique identifier
renderInput={(params) => (
<FieldWrapper
label={label}
@@ -206,7 +206,7 @@ const Search = ({
return filtered;
}}
getOptionKey={(option) => {
return option._id;
return option._id ?? option.id;
}}
renderOption={(props, option) => {
const { key, ...optionProps } = props;
@@ -1,7 +1,7 @@
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { MenuItem, Select as MuiSelect, Stack, Typography } from "@mui/material";
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
import Icon from "../../Icon";
import FieldWrapper from "../FieldWrapper/index.jsx";
import "./index.css";
@@ -62,6 +62,7 @@ const Select = ({
fieldWrapperSx = {},
}) => {
const theme = useTheme();
const getItemValue = (item) => item?._id ?? item?.id;
const itemStyles = {
fontSize: "var(--env-var-font-size-medium)",
color: theme.palette.primary.contrastTextTertiary,
@@ -97,7 +98,13 @@ const Select = ({
error={error}
name={name}
inputProps={{ id: id }}
IconComponent={KeyboardArrowDownIcon}
IconComponent={(props) => (
<Icon
name="ChevronDown"
size={20}
{...props}
/>
)}
MenuProps={{ disableScrollLock: true }}
sx={{
fontSize: 13,
@@ -123,7 +130,7 @@ const Select = ({
...sx,
}}
renderValue={(selected) => {
const selectedItem = items.find((item) => item._id === selected);
const selectedItem = items.find((item) => getItemValue(item) === selected);
const displayName = selectedItem ? selectedItem.name : placeholder;
return (
<Typography
@@ -152,17 +159,25 @@ const Select = ({
{placeholder}
</MenuItem>
)}
{items.map((item) => (
<MenuItem
value={item._id}
key={`${id}-${item._id}`}
sx={{
...itemStyles,
}}
>
{item.name}
</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>
);
@@ -179,8 +194,8 @@ Select.propTypes = {
.isRequired,
items: PropTypes.arrayOf(
PropTypes.shape({
_id: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool])
.isRequired,
_id: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]),
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]),
name: PropTypes.string.isRequired,
})
@@ -1,10 +1,7 @@
import { Stack, Typography, InputAdornment, IconButton } from "@mui/material";
import { useTheme } from "@mui/material/styles";
import PropTypes from "prop-types";
import VisibilityOff from "@mui/icons-material/VisibilityOff";
import Visibility from "@mui/icons-material/Visibility";
import ReorderRoundedIcon from "@mui/icons-material/ReorderRounded";
import DeleteIcon from "../../../../../assets/icons/trash-bin.svg?react";
import Icon from "../../../Icon";
export const HttpAdornment = ({ https }) => {
const theme = useTheme();
@@ -56,7 +53,17 @@ export const PasswordEndAdornment = ({ fieldType, setFieldType }) => {
},
}}
>
{fieldType === "password" ? <VisibilityOff /> : <Visibility />}
{fieldType === "password" ? (
<Icon
name="EyeOff"
size={20}
/>
) : (
<Icon
name="Eye"
size={20}
/>
)}
</IconButton>
</InputAdornment>
);
@@ -3,6 +3,8 @@
min-height: 100vh;
margin: 0 auto;
padding: 0;
overflow-x: hidden;
width: 100%;
}
/* TODO go for this approach for responsiveness. The aside needs to be taken care of */
@@ -12,15 +14,7 @@
}
} */
/* .home-layout aside {
position: sticky;
top: 0;
left: 0;
height: 100vh;
max-width: var(--env-var-side-bar-width);
} */
.home-layout > div {
.home-layout > .home-content-wrapper {
min-height: calc(100vh - var(--env-var-spacing-2) * 2);
flex: 1;
}
@@ -30,4 +24,6 @@
max-width: 1400px;
margin: 0 auto;
flex: 1;
min-width: 0;
overflow-x: hidden;
}
@@ -1,17 +1,27 @@
import Sidebar from "../../Sidebar/index.jsx";
import { Outlet } from "react-router";
import { Stack } from "@mui/material";
import { Box, Stack } from "@mui/material";
import { useSidebar } from "@/Hooks/useSidebar.js";
import "./index.css";
const HomeLayout = () => {
const { width, transition } = useSidebar();
return (
<Stack
className="home-layout"
flexDirection="row"
gap={14}
>
<Sidebar />
{/* Spacer for fixed sidebar */}
<Box
sx={{
width,
flexShrink: 0,
transition,
}}
/>
<Stack className="home-content-wrapper">
<Outlet />
</Stack>
@@ -1,7 +1,7 @@
import * as React from "react";
import Button from "@mui/material/Button";
import ButtonGroup from "@mui/material/ButtonGroup";
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
import Icon from "../Icon";
import ClickAwayListener from "@mui/material/ClickAwayListener";
import Grow from "@mui/material/Grow";
import Paper from "@mui/material/Paper";
@@ -11,7 +11,7 @@ import MenuList from "@mui/material/MenuList";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { createToast } from "../../../Utils/toastUtils.jsx";
import { useExportMonitors } from "../../../Hooks/v1/monitorHooks.js";
import { useExportMonitors } from "../../../Hooks/monitorHooks.js";
const MonitorActions = ({ isLoading }) => {
const [open, setOpen] = React.useState(false);
@@ -70,7 +70,10 @@ const MonitorActions = ({ isLoading }) => {
aria-haspopup="menu"
onClick={handleToggle}
>
<ArrowDropDownIcon />
<Icon
name="ChevronDown"
size={20}
/>
</Button>
</ButtonGroup>
<Popper
@@ -3,19 +3,16 @@ import Status from "./status.jsx";
import Skeleton from "./skeleton.jsx";
import Button from "@mui/material/Button";
import { Tooltip } from "@mui/material";
import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined";
import PauseOutlinedIcon from "@mui/icons-material/PauseOutlined";
import PlayArrowOutlinedIcon from "@mui/icons-material/PlayArrowOutlined";
import EmailIcon from "@mui/icons-material/Email";
import Icon from "../Icon";
// Utils
import PropTypes from "prop-types";
import { useNavigate } from "react-router-dom";
import { useTheme } from "@mui/material/styles";
import { usePauseMonitor } from "../../../Hooks/v1/monitorHooks.js";
import { useSendTestEmail } from "../../../Hooks/v1/useSendTestEmail.js";
import { usePauseMonitor } from "../../../Hooks/monitorHooks.js";
import { useSendTestEmail } from "../../../Hooks/useSendTestEmail.js";
import { useTranslation } from "react-i18next";
import { useTestAllNotifications } from "../../../Hooks/v1/useNotifications.js";
import { useTestAllNotifications } from "../../../Hooks/useNotifications.js";
/**
* MonitorDetailsControlHeader component displays the control header for monitor details.
* It includes status display, pause/resume button, and a configure button for admins.
@@ -66,7 +63,7 @@ const MonitorDetailsControlHeader = ({
gap={theme.spacing(2)}
>
<Tooltip
key={monitor?._id}
key={monitor?.id}
placement="bottom"
title={tooltipTitle}
>
@@ -75,10 +72,15 @@ const MonitorDetailsControlHeader = ({
variant="contained"
color="secondary"
loading={isSending}
startIcon={<EmailIcon />}
startIcon={
<Icon
name="Mail"
size={18}
/>
}
disabled={isTestNotificationsDisabled}
onClick={() => {
testAllNotifications({ monitorId: monitor?._id });
testAllNotifications({ monitorId: monitor?.id });
}}
sx={{
whiteSpace: "nowrap",
@@ -92,7 +94,7 @@ const MonitorDetailsControlHeader = ({
variant="contained"
color="secondary"
onClick={(e) => {
navigate(`/incidents/${monitor?._id}`);
navigate(`/incidents/${monitor?.id}`);
}}
>
{t("menu.incidents")}
@@ -103,24 +105,39 @@ const MonitorDetailsControlHeader = ({
color="secondary"
loading={isPausing}
startIcon={
monitor?.isActive ? <PauseOutlinedIcon /> : <PlayArrowOutlinedIcon />
monitor?.isActive ? (
<Icon
name="Pause"
size={18}
/>
) : (
<Icon
name="Play"
size={18}
/>
)
}
onClick={() => {
pauseMonitor({
monitorId: monitor?._id,
monitorId: monitor?.id,
triggerUpdate,
});
}}
>
{monitor?.isActive ? "Pause" : "Resume"}
{monitor?.isActive ? t("pause") : t("resume")}
</Button>
)}
{isAdmin && (
<Button
variant="contained"
color="secondary"
startIcon={<SettingsOutlinedIcon />}
onClick={() => navigate(`/${path}/configure/${monitor._id}`)}
startIcon={
<Icon
name="Settings"
size={18}
/>
}
onClick={() => navigate(`/${path}/configure/${monitor.id}`)}
>
Configure
</Button>
@@ -4,10 +4,10 @@ import PulseDot from "../Animated/PulseDot.jsx";
import Typography from "@mui/material/Typography";
import Dot from "../Dot/index.jsx";
// Utils
import { formatDurationRounded } from "../../../Utils/timeUtils.js";
import { formatDurationRounded } from "../../../Utils/timeUtilsLegacy.js";
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { useMonitorUtils } from "../../../Hooks/v1/useMonitorUtils.js";
import { useMonitorUtils } from "../../../Hooks/useMonitorUtils.js";
import { formatMonitorUrl } from "../../../Utils/utils.js";
/**
* Status component displays the status information of a monitor.
@@ -2,7 +2,7 @@
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import Divider from "@mui/material/Divider";
import DeleteOutlineRoundedIcon from "@mui/icons-material/DeleteOutlineRounded";
import Icon from "../Icon";
import Search from "../Inputs/Search/index.jsx";
// Utils
@@ -31,7 +31,7 @@ const NotificationConfig = ({
setMonitor((prev) => {
return {
...prev,
notifications: value.map((notification) => notification._id),
notifications: value.map((notification) => notification.id),
};
});
};
@@ -39,14 +39,14 @@ const NotificationConfig = ({
// Handlers
const handleDelete = (id) => {
const updatedNotifications = selectedNotifications.filter(
(notification) => notification._id !== id
(notification) => notification.id !== id
);
setSelectedNotifications(updatedNotifications);
setMonitor((prev) => {
return {
...prev,
notifications: updatedNotifications.map((notification) => notification._id),
notifications: updatedNotifications.map((notification) => notification.id),
};
});
};
@@ -57,7 +57,7 @@ const NotificationConfig = ({
useEffect(() => {
if (setNotifications) {
const toSet = setNotifications.map((notification) => {
return notifications.find((n) => n._id === notification);
return notifications.find((n) => n.id === notification);
});
setSelectedNotifications(toSet);
}
@@ -94,7 +94,7 @@ const NotificationConfig = ({
<Stack
direction="row"
alignItems="center"
key={notification._id}
key={notification.id}
width="100%"
>
<Typography
@@ -102,11 +102,13 @@ const NotificationConfig = ({
>
{notification.notificationName}
</Typography>
<DeleteOutlineRoundedIcon
<Icon
name="Trash2"
size={20}
onClick={() => {
handleDelete(notification._id);
handleDelete(notification.id);
}}
sx={{ cursor: "pointer" }}
style={{ cursor: "pointer" }}
/>
{index < selectedNotifications.length - 1 && <Divider />}
</Stack>
@@ -3,7 +3,7 @@ import Fallback from "../Fallback/index.jsx";
import { Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
import { useIsAdmin } from "../../../Hooks/v1/useIsAdmin.js";
import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
import PropTypes from "prop-types";
const PageStateWrapper = ({
networkError,
@@ -1,8 +1,7 @@
import { useTheme } from "@emotion/react";
import PropTypes from "prop-types";
import { Box, IconButton, LinearProgress, Stack, Typography } from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
import ErrorOutlineOutlinedIcon from "@mui/icons-material/ErrorOutlineOutlined";
import Icon from "../Icon";
import "./index.css";
/**
@@ -51,7 +50,10 @@ const ProgressUpload = ({ icon, label, size, progress = 0, onClick, error }) =>
alignItems={error ? "center" : "flex-start"}
>
{error ? (
<ErrorOutlineOutlinedIcon />
<Icon
name="AlertTriangle"
size={20}
/>
) : icon ? (
<Box
sx={{
@@ -127,10 +129,9 @@ const ProgressUpload = ({ icon, label, size, progress = 0, onClick, error }) =>
}
}
>
<CloseIcon
sx={{
fontSize: "20px",
}}
<Icon
name="X"
size={20}
/>
</IconButton>
</Stack>
@@ -1,15 +1,22 @@
import React from "react";
import { Step, Stepper, StepLabel, Typography } from "@mui/material";
import RadioButtonCheckedIcon from "@mui/icons-material/RadioButtonChecked";
import CheckCircle from "@mui/icons-material/CheckCircle";
import Icon from "../Icon";
import PropTypes from "prop-types";
const CustomStepIcon = (props) => {
const { completed, active } = props;
return completed ? (
<CheckCircle color="accent" />
<Icon
name="CheckCircle2"
size={24}
color="accent.main"
/>
) : (
<RadioButtonCheckedIcon color={active ? "accent" : "disabled"} />
<Icon
name="Circle"
size={24}
color={active ? "accent.main" : "text.disabled"}
/>
);
};
@@ -1,5 +1,5 @@
import html2canvas from "html2canvas";
import ShareIcon from "@mui/icons-material/Share";
import Icon from "../Icon";
import { Button } from "@mui/material";
import { useTheme } from "@emotion/react";
@@ -55,7 +55,13 @@ const ShareComponent = ({ elementToCapture, fileName = "screenshot" }) => {
return (
<Button
variant="outlined"
startIcon={<ShareIcon sx={{ color: theme.palette.success.main }} />}
startIcon={
<Icon
name="Share2"
size={18}
color="success.main"
/>
}
color="success"
onClick={captureAndShare}
>
@@ -7,8 +7,7 @@ import Avatar from "../../Avatar/index.jsx";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import Divider from "@mui/material/Divider";
import DotsVertical from "../../../../assets/icons/dots-vertical.svg?react";
import LogoutSvg from "../../../../assets/icons/logout.svg?react";
import Icon from "../../Icon";
import { useTheme } from "@emotion/react";
import { useSelector } from "react-redux";
@@ -81,6 +80,10 @@ const AuthFooter = ({ collapsed, accountMenuItems }) => {
gap: theme.spacing(2),
borderRadius: theme.shape.borderRadius,
pl: theme.spacing(4),
color: theme.palette.primary.contrastTextTertiary,
"& svg": {
stroke: theme.palette.primary.contrastTextTertiary,
},
}}
>
{item.icon}
@@ -160,19 +163,14 @@ const AuthFooter = ({ collapsed, accountMenuItems }) => {
ml: "50px",
"&:focus": { outline: "none" },
alignSelf: "center",
"& svg": {
width: "22px",
height: "22px",
},
"& svg path": {
/* Vertical three dots */
stroke: theme.palette.primary.contrastTextTertiary,
},
}}
onClick={(event) => openPopup(event)}
>
<DotsVertical />
<Icon
name="MoreVertical"
size={22}
color="primary.contrastTextTertiary"
/>
</IconButton>
</Tooltip>
</Stack>
@@ -256,12 +254,13 @@ const AuthFooter = ({ collapsed, accountMenuItems }) => {
gap: theme.spacing(4),
borderRadius: theme.shape.borderRadius,
pl: theme.spacing(4),
"& svg path": {
stroke: theme.palette.primary.contrastTextTertiary,
},
}}
>
<LogoutSvg />
<Icon
name="LogOut"
size={20}
color="primary.contrastTextTertiary"
/>
{t("menu.logOut", "Log out")}
</MenuItem>
</Menu>
@@ -52,12 +52,11 @@ const NavItem = ({ item, collapsed, selected, onClick }) => {
<ListItemIcon
sx={{
minWidth: 0,
color: iconStroke,
"& svg": {
height: 20,
width: 20,
opacity: 0.81,
},
"& svg path": {
stroke: iconStroke,
},
}}
+27 -46
View File
@@ -8,28 +8,13 @@ import NavItem from "./components/navItem.jsx";
import AuthFooter from "./components/authFooter.jsx";
import StarPrompt from "../StarPrompt/index.jsx";
import LockSvg from "../../../assets/icons/lock.svg?react";
import UserSvg from "../../../assets/icons/user.svg?react";
import TeamSvg from "../../../assets/icons/user-two.svg?react";
import Support from "../../../assets/icons/support.svg?react";
import Maintenance from "../../../assets/icons/maintenance.svg?react";
import Monitors from "../../../assets/icons/monitors.svg?react";
import Incidents from "../../../assets/icons/incidents.svg?react";
import Integrations from "../../../assets/icons/integrations.svg?react";
import PageSpeed from "../../../assets/icons/page-speed.svg?react";
import Settings from "../../../assets/icons/settings.svg?react";
import ChangeLog from "../../../assets/icons/changeLog.svg?react";
import Docs from "../../../assets/icons/docs.svg?react";
import StatusPages from "../../../assets/icons/status-pages.svg?react";
import Discussions from "../../../assets/icons/discussions.svg?react";
import Notifications from "../../../assets/icons/notifications.svg?react";
import Logs from "../../../assets/icons/logs.svg?react";
import Icon from "../Icon";
// Utils
import { useTheme } from "@mui/material/styles";
import { useSelector } from "react-redux";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router";
import { useSidebar } from "@/Hooks/useSidebar.js";
const URL_MAP = {
support: "https://discord.com/invite/NAb6H3UTjK",
@@ -39,51 +24,48 @@ const URL_MAP = {
};
const getMenu = (t) => [
{ name: t("menu.uptime"), path: "uptime", icon: <Monitors /> },
{ name: t("menu.pagespeed"), path: "pagespeed", icon: <PageSpeed /> },
{ name: t("menu.infrastructure"), path: "infrastructure", icon: <Integrations /> },
{ name: t("menu.uptime"), path: "uptime", icon: <Icon name="Globe" /> },
{ name: t("menu.pagespeed"), path: "pagespeed", icon: <Icon name="Gauge" /> },
{ name: t("menu.infrastructure"), path: "infrastructure", icon: <Icon name="Link" /> },
{
name: t("menu.notifications"),
path: "notifications",
icon: <Notifications />,
icon: <Icon name="Bell" />,
},
{ name: t("menu.incidents"), path: "incidents", icon: <Incidents /> },
{ name: t("menu.statusPages"), path: "status", icon: <StatusPages /> },
{ name: t("menu.maintenance"), path: "maintenance", icon: <Maintenance /> },
{ name: t("menu.logs"), path: "logs", icon: <Logs /> },
{ name: t("menu.checks"), path: "checks", icon: <Icon name="FileText" /> },
{ name: t("menu.incidents"), path: "incidents", icon: <Icon name="AlertTriangle" /> },
{ name: t("menu.statusPages"), path: "status", icon: <Icon name="Wifi" /> },
{ name: t("menu.maintenance"), path: "maintenance", icon: <Icon name="Wrench" /> },
{ name: t("menu.logs"), path: "logs", icon: <Icon name="Database" /> },
{
name: t("menu.settings"),
icon: <Settings />,
icon: <Icon name="Settings" />,
path: "settings",
},
];
const getOtherMenuItems = (t) => [
{ name: t("menu.support"), path: "support", icon: <Support /> },
{ name: t("menu.support"), path: "support", icon: <Icon name="HelpCircle" /> },
{
name: t("menu.discussions"),
path: "discussions",
icon: <Discussions />,
icon: <Icon name="MessageCircle" />,
},
{ name: t("menu.docs"), path: "docs", icon: <Docs /> },
{ name: t("menu.changelog"), path: "changelog", icon: <ChangeLog /> },
{ name: t("menu.docs"), path: "docs", icon: <Icon name="FileText" /> },
{ name: t("menu.changelog"), path: "changelog", icon: <Icon name="Code" /> },
];
const getAccountMenuItems = (t) => [
{ name: t("menu.profile"), path: "account/profile", icon: <UserSvg /> },
{ name: t("menu.password"), path: "account/password", icon: <LockSvg /> },
{ name: t("menu.team"), path: "account/team", icon: <TeamSvg /> },
{ name: t("menu.profile"), path: "account/profile", icon: <Icon name="User" /> },
{ name: t("menu.password"), path: "account/password", icon: <Icon name="Lock" /> },
{ name: t("menu.team"), path: "account/team", icon: <Icon name="Users" /> },
];
const Sidebar = () => {
const theme = useTheme();
const { t } = useTranslation();
const navigate = useNavigate();
// Redux state
const collapsed = useSelector((state) => state.ui.sidebar.collapsed);
const { collapsed, width, transition } = useSidebar();
const menu = getMenu(t);
const otherMenuItems = getOtherMenuItems(t);
@@ -92,20 +74,19 @@ const Sidebar = () => {
return (
<Stack
height="100vh"
width={
collapsed
? "var(--env-var-side-bar-collapsed-width)"
: "var(--env-var-side-bar-width)"
}
width={width}
component="aside"
position="sticky"
position="fixed"
top={0}
borderRight={`1px solid ${theme.palette.primary.lowContrast}`}
left={0}
paddingTop={theme.spacing(6)}
paddingBottom={theme.spacing(6)}
gap={theme.spacing(6)}
sx={{
transition: "width 650ms cubic-bezier(0.36, -0.01, 0, 0.77)",
transition,
backgroundColor: theme.palette.background.main,
borderRight: `1px solid ${theme.palette.primary.lowContrast}`,
zIndex: 1000,
}}
>
<CollapseButton collapsed={collapsed} />
@@ -1,6 +1,6 @@
import React from "react";
import { Typography, IconButton, Stack, Box } from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
import Icon from "../Icon";
import { useTheme } from "@emotion/react";
import { useSelector, useDispatch } from "react-redux";
import { useTranslation } from "react-i18next";
@@ -67,7 +67,10 @@ const StarPrompt = ({ repoUrl = "https://github.com/bluewave-labs/checkmate" })
},
}}
>
<CloseIcon sx={{ fontSize: "1.25rem" }} />
<Icon
name="X"
size={20}
/>
</IconButton>
</Stack>
+1 -1
View File
@@ -2,7 +2,7 @@ import { Stack, Typography } from "@mui/material";
import Image from "../Image/index.jsx";
import { useTheme } from "@mui/material/styles";
import PropTypes from "prop-types";
import { useMonitorUtils } from "../../../Hooks/v1/useMonitorUtils.js";
import { useMonitorUtils } from "../../../Hooks/useMonitorUtils.js";
/**
* StatBox Component
@@ -2,7 +2,7 @@ import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { Stack, TablePagination, Typography } from "@mui/material";
import { TablePaginationActions } from "./Actions/index.jsx";
import SelectorVertical from "../../../../assets/icons/selector-vertical.svg?react";
import Icon from "../../Icon";
Pagination.propTypes = {
paginationLabel: PropTypes.string, // Label for the pagination.
@@ -100,7 +100,12 @@ function Pagination({
},
},
inputProps: { id: "pagination-dropdown" },
IconComponent: SelectorVertical,
IconComponent: () => (
<Icon
name="ChevronsUpDown"
size={16}
/>
),
sx: {
ml: theme.spacing(4),
mr: theme.spacing(12),
+27 -9
View File
@@ -3,19 +3,31 @@ import IconButton from "@mui/material/IconButton";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import ToastBody from "./body.jsx";
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
import ErrorOutlineOutlinedIcon from "@mui/icons-material/ErrorOutlineOutlined";
import WarningAmberOutlinedIcon from "@mui/icons-material/WarningAmberOutlined";
import CloseIcon from "@mui/icons-material/Close";
import Icon from "../Icon";
// Utils
import { useTheme } from "@emotion/react";
import PropTypes from "prop-types";
const icons = {
info: <InfoOutlinedIcon />,
error: <ErrorOutlineOutlinedIcon />,
warning: <WarningAmberOutlinedIcon />,
info: (
<Icon
name="Info"
size={24}
/>
),
error: (
<Icon
name="AlertCircle"
size={24}
/>
),
warning: (
<Icon
name="AlertTriangle"
size={24}
/>
),
};
const Toast = ({ variant, title, body, onClick, hasDismiss, hasIcon }) => {
@@ -50,7 +62,10 @@ const Toast = ({ variant, title, body, onClick, hasDismiss, hasIcon }) => {
)}
{title && (
<IconButton onClick={onClick}>
<CloseIcon />
<Icon
name="X"
size={20}
/>
</IconButton>
)}
</Stack>
@@ -63,7 +78,10 @@ const Toast = ({ variant, title, body, onClick, hasDismiss, hasIcon }) => {
<ToastBody body={body} />
{!title && (
<IconButton onClick={onClick}>
<CloseIcon />
<Icon
name="X"
size={20}
/>
</IconButton>
)}
</Stack>
@@ -1,36 +0,0 @@
import LeftArrow from "@/assets/icons/left-arrow.svg?react";
import LeftArrowDouble from "@/assets/icons/left-arrow-double.svg?react";
import LeftArrowLong from "@/assets/icons/left-arrow-long.svg?react";
export const ArrowLeft = ({
type,
color = "#667085",
...props
}: {
type?: string;
color?: string | undefined;
[key: string]: any;
}) => {
if (type === "double") {
return (
<LeftArrowDouble
style={{ color }}
{...props}
/>
);
} else if (type === "long") {
return (
<LeftArrowLong
style={{ color }}
{...props}
/>
);
} else {
return (
<LeftArrow
style={{ color }}
{...props}
/>
);
}
};
@@ -1,28 +0,0 @@
import RightArrow from "@/assets/icons/right-arrow.svg?react";
import RightArrowDouble from "@/assets/icons/right-arrow-double.svg?react";
export const ArrowRight = ({
type,
color = "#667085",
...props
}: {
type?: string;
color?: string | undefined;
[key: string]: any;
}) => {
if (type === "double") {
return (
<RightArrowDouble
style={{ color }}
{...props}
/>
);
} else {
return (
<RightArrow
style={{ color }}
{...props}
/>
);
}
};
@@ -1,48 +0,0 @@
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import { HeaderAuth } from "@/Components/v2/Auth";
import Logo from "@/assets/icons/checkmate-icon.svg?react";
import type { StackProps } from "@mui/material/Stack";
import { useTheme } from "@mui/material/styles";
import { Typography } from "@mui/material";
interface AuthBasePageProps extends StackProps {
title?: string;
subtitle?: string;
children: React.ReactNode;
}
export const AuthBasePage: React.FC<AuthBasePageProps> = ({
children,
title,
subtitle,
...props
}) => {
const theme = useTheme();
return (
<Stack
gap={theme.spacing(10)}
minHeight="100vh"
{...props}
>
<HeaderAuth />
<Stack
alignItems="center"
margin="auto"
width="100%"
gap={theme.spacing(4)}
>
<Box
width={{ xs: 60, sm: 70, md: 80 }}
mb={theme.spacing(10)}
>
<Logo style={{ width: "100%", height: "100%" }} />
</Box>
<Typography variant="h1">{title}</Typography>
<Typography variant="h1">{subtitle}</Typography>
{children}
</Stack>
</Stack>
);
};
@@ -1,26 +0,0 @@
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import Logo from "@/assets/icons/checkmate-icon.svg?react";
import { useTheme } from "@mui/material/styles";
import { useTranslation } from "react-i18next";
import { LanguageSelector, ThemeSwitch } from "@/Components/v2/Inputs";
export const HeaderAuth = () => {
const theme = useTheme();
const { t } = useTranslation();
return (
<Stack
width={"100%"}
direction="row"
alignItems="center"
justifyContent="flex-end"
py={theme.spacing(4)}
px={theme.spacing(12)}
gap={theme.spacing(4)}
>
<LanguageSelector />
<ThemeSwitch color="red" />
</Stack>
);
};
-2
View File
@@ -1,2 +0,0 @@
export { HeaderAuth } from "./HeaderAuth";
export { AuthBasePage } from "./AuthBasePage";
@@ -1,23 +0,0 @@
import Box from "@mui/material/Box";
import { useTheme } from "@mui/material/styles";
import type { SxProps } from "@mui/material/styles";
type BaseBoxProps = React.PropsWithChildren<{ sx?: SxProps }>;
export const BaseBox: React.FC<BaseBoxProps> = ({ children, sx }) => {
const theme = useTheme();
return (
<Box
sx={{
backgroundColor: theme.palette.primary.main,
border: 1,
borderStyle: "solid",
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
...sx,
}}
>
{children}
</Box>
);
};
@@ -1,80 +0,0 @@
import Stack from "@mui/material/Stack";
import { ErrorFallback, EmptyFallback } from "./Fallback";
import type { StackProps } from "@mui/material/Stack";
import { useTheme } from "@mui/material/styles";
import { useTranslation } from "react-i18next";
interface BasePageProps extends StackProps {
children: React.ReactNode;
}
export const BasePage: React.FC<BasePageProps> = ({
children,
...props
}: {
children: React.ReactNode;
}) => {
const theme = useTheme();
return (
<Stack
spacing={theme.spacing(10)}
{...props}
>
{children}
</Stack>
);
};
interface BasePageWithStatesProps extends StackProps {
loading: boolean;
error: any;
items: any[];
page: string;
actionLink?: string;
children: React.ReactNode;
}
const isEmpty = (items: any[]) => {
if (!items) return true;
if (Array.isArray(items) && items.length === 0) return true;
return false;
};
export const BasePageWithStates: React.FC<BasePageWithStatesProps> = ({
loading,
error,
items,
page,
actionLink,
children,
...props
}: BasePageWithStatesProps) => {
const { t } = useTranslation();
if (loading) {
return null;
}
if (error) {
return (
<ErrorFallback
title="Something went wrong..."
subtitle="Please try again later"
/>
);
}
if (isEmpty(items)) {
return (
<EmptyFallback
page={page}
title={t(`${page}Monitor.fallback.title`)}
bullets={t(`${page}Monitor.fallback.checks`, { returnObjects: true })}
actionButtonText={t(`${page}Monitor.fallback.actionButton`)}
actionLink={actionLink || ""}
/>
);
}
return <BasePage {...props}>{children}</BasePage>;
};
@@ -1,158 +0,0 @@
import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import OutputAnimation from "@/assets/Animations/output.gif";
import DarkmodeOutput from "@/assets/Animations/darkmodeOutput.gif";
import Typography from "@mui/material/Typography";
import { BulletPointCheck } from "@/Components/v2/DesignElements";
import { Button } from "@/Components/v2/Inputs";
import { useNavigate } from "react-router";
import { useMediaQuery } from "@mui/material";
import { useTheme } from "@mui/material/styles";
import { useSelector } from "react-redux";
import type { BoxProps } from "@mui/material";
interface BaseFallbackProps extends BoxProps {
children: React.ReactNode;
}
export const BaseFallback: React.FC<BaseFallbackProps> = ({ children, ...props }) => {
const theme = useTheme();
const mode = useSelector((state: any) => state.ui.mode);
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
return (
<Box
margin={isSmall ? "inherit" : "auto"}
marginTop={isSmall ? "33%" : "auto"}
width={{
sm: "90%",
md: "70%",
lg: "50%",
xl: "40%",
}}
padding={theme.spacing(16)}
bgcolor={theme.palette.primary.main}
position="relative"
border={1}
borderColor={theme.palette.primary.lowContrast}
borderRadius={theme.shape.borderRadius}
overflow="hidden"
sx={{
borderStyle: "dashed",
}}
{...props}
>
<Stack
alignItems="center"
gap={theme.spacing(20)}
sx={{
width: "fit-content",
margin: "auto",
marginTop: "100px",
}}
>
<Box
component="img"
src={mode === "light" ? OutputAnimation : DarkmodeOutput}
bgcolor="transparent"
alt="Loading animation"
width="100%"
sx={{
zIndex: 1,
border: "none",
borderRadius: theme.spacing(8),
}}
/>
<Stack
gap={theme.spacing(4)}
alignItems="center"
maxWidth={"300px"}
zIndex={1}
>
{children}
</Stack>
</Stack>
</Box>
);
};
export const ErrorFallback = ({
title,
subtitle,
}: {
title: string;
subtitle: string;
}) => {
const theme = useTheme();
return (
<BaseFallback>
<Typography
variant="h1"
marginY={theme.spacing(4)}
color={theme.palette.primary.contrastTextTertiary}
>
{title}
</Typography>
<Typography>{subtitle}</Typography>
</BaseFallback>
);
};
export const EmptyFallback = ({
page,
title,
bullets,
actionButtonText,
actionLink,
}: {
page: string;
title: string;
bullets: any;
actionButtonText: string;
actionLink: string;
}) => {
const theme = useTheme();
const navigate = useNavigate();
return (
<BaseFallback>
<Stack
gap={theme.spacing(10)}
zIndex={1}
alignItems="center"
>
<Typography
component="h1"
color={theme.palette.primary.contrastText}
>
{title}
</Typography>
<Stack
sx={{
flexWrap: "wrap",
gap: theme.spacing(2),
maxWidth: { xs: "90%", md: "80%", lg: "75%" },
}}
>
{bullets?.map((bullet: string, index: number) => (
<BulletPointCheck
text={bullet}
key={`${(page + "Monitors").trim().split(" ")[0]}-${index}`}
/>
))}
</Stack>
<Stack>
<Button
variant="contained"
color="accent"
onClick={() => navigate(actionLink)}
>
{actionButtonText}
</Button>
</Stack>
</Stack>
</BaseFallback>
);
};
@@ -1,68 +0,0 @@
import { Fragment } from "react";
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import { useTheme } from "@mui/material/styles";
import { useMediaQuery } from "@mui/material";
export const SplitBox = ({
left,
right,
}: {
left: React.ReactNode;
right: React.ReactNode;
}) => {
const theme = useTheme();
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
return (
<Stack
direction={isSmall ? "column" : "row"}
bgcolor={theme.palette.primary.main}
border={1}
borderColor={theme.palette.primary.lowContrast}
borderRadius={theme.spacing(2)}
>
<Box
padding={theme.spacing(15)}
borderRight={isSmall ? 0 : 1}
borderBottom={isSmall ? 1 : 0}
borderColor={theme.palette.primary.lowContrast}
flex={0.7}
>
{left}
</Box>
<Box
flex={1}
padding={theme.spacing(15)}
>
{right}
</Box>
</Stack>
);
};
export const ConfigBox = ({
title,
subtitle,
rightContent,
}: {
title: string;
subtitle: string;
rightContent: React.ReactNode;
}) => {
return (
<SplitBox
left={
<Fragment>
<Typography
component="h2"
variant="h2"
>
{title}
</Typography>
<Typography component="p">{subtitle}</Typography>
</Fragment>
}
right={rightContent}
/>
);
};
@@ -1,57 +0,0 @@
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { useTheme } from "@mui/material/styles";
import { useMediaQuery } from "@mui/material";
import type { PaletteKey } from "@/Utils/Theme/v2/theme";
import { BaseBox } from "@/Components/v2/DesignElements";
type GradientBox = React.PropsWithChildren<{ palette?: PaletteKey }>;
export const GradientBox: React.FC<GradientBox> = ({ children, palette }) => {
const theme = useTheme();
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
const bg = palette
? `linear-gradient(to bottom right, ${theme.palette[palette].main} 30%, ${theme.palette[palette].lowContrast} 70%)`
: `linear-gradient(340deg, ${theme.palette.tertiary.main} 10%, ${theme.palette.primary.main} 45%)`;
return (
<BaseBox
sx={{
padding: `${theme.spacing(4)} ${theme.spacing(8)}`,
width: isSmall
? `calc(50% - (1 * ${theme.spacing(8)} / 2))`
: `calc(25% - (3 * ${theme.spacing(8)} / 4))`,
background: bg,
}}
>
{children}
</BaseBox>
);
};
type StatBoxProps = React.PropsWithChildren<{
title: string;
subtitle: string;
palette?: PaletteKey;
}>;
export const StatBox: React.FC<StatBoxProps> = ({
title,
subtitle,
palette,
children,
}) => {
const theme = useTheme();
const textColor = palette ? theme.palette[palette].contrastText : "inherit";
return (
<GradientBox palette={palette}>
<Stack>
<Typography color={textColor}>{title}</Typography>
<Typography color={textColor}>{subtitle}</Typography>
{children}
</Stack>
</GradientBox>
);
};
@@ -1,41 +0,0 @@
import Box from "@mui/material/Box";
import { BaseBox } from "@/Components/v2/DesignElements";
import type { MonitorStatus } from "@/Types/Monitor";
import { getStatusPalette } from "@/Utils/v2/MonitorUtils";
import { useTheme } from "@mui/material/styles";
export const StatusLabel = ({
status,
isActive,
}: {
status: MonitorStatus;
isActive?: boolean;
}) => {
const theme = useTheme();
const palette = getStatusPalette(status);
const transformedText = status.charAt(0).toUpperCase() + status.slice(1).toLowerCase();
return (
<BaseBox
sx={{
display: "inline-flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
padding: theme.spacing(3, 5),
color: theme.palette[palette].main,
borderColor: theme.palette[palette].lowContrast,
}}
>
<Box
width={7}
height={7}
bgcolor={theme.palette[palette].lowContrast}
borderRadius="50%"
marginRight="5px"
/>
{isActive === false ? "Paused" : transformedText}
</BaseBox>
);
};
@@ -1,215 +0,0 @@
import Paper from "@mui/material/Paper";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import IconButton from "@mui/material/IconButton";
import LastPageIcon from "@mui/icons-material/LastPage";
import FirstPageIcon from "@mui/icons-material/FirstPage";
import KeyboardArrowLeft from "@mui/icons-material/KeyboardArrowLeft";
import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight";
import Box from "@mui/material/Box";
import TablePagination from "@mui/material/TablePagination";
import type { TablePaginationProps } from "@mui/material/TablePagination";
import { useTheme } from "@mui/material/styles";
import { useMediaQuery } from "@mui/material";
export type Header<T> = {
id: number | string;
content: React.ReactNode;
onClick?: (event: React.MouseEvent<HTMLTableCellElement | null>, row: T) => void;
render: (row: T) => React.ReactNode;
};
type DataTableProps<T extends { id?: string | number; _id?: string | number }> = {
headers: Header<T>[];
data: T[];
onRowClick?: (row: T) => void;
};
export function DataTable<
T extends {
id?: string | number;
_id?: string | number;
onRowClick?: (row: T) => void;
},
>({ headers, data, onRowClick }: DataTableProps<T>) {
const theme = useTheme();
if (data.length === 0 || headers.length === 0) return <div>No data</div>;
return (
<TableContainer component={Paper}>
<Table
stickyHeader
sx={{
"&.MuiTable-root :is(.MuiTableHead-root, .MuiTableBody-root) :is(th, td)": {
paddingLeft: theme.spacing(8),
},
"& :is(th)": {
backgroundColor: theme.palette.secondary.main,
color: theme.palette.secondary.contrastText,
fontWeight: 600,
},
"& :is(td)": {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastTextSecondary,
},
"& .MuiTableBody-root .MuiTableRow-root:last-child .MuiTableCell-root": {
borderBottom: "none",
},
}}
>
<TableHead>
<TableRow>
{headers.map((header, idx) => {
return (
<TableCell
align={idx === 0 ? "left" : "center"}
key={header.id}
>
{header.content}
</TableCell>
);
})}
</TableRow>
</TableHead>
<TableBody>
{data.map((row) => {
const key = row.id || row._id || Math.random();
return (
<TableRow
key={key}
sx={{ cursor: onRowClick ? "pointer" : "default" }}
onClick={() => (onRowClick ? onRowClick(row) : null)}
>
{headers.map((header, index) => {
return (
<TableCell
align={index === 0 ? "left" : "center"}
key={header.id}
onClick={
header.onClick ? (e) => header.onClick!(e, row) : undefined
}
>
{header.render(row)}
</TableCell>
);
})}
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
);
}
interface TablePaginationActionsProps {
count: number;
page: number;
rowsPerPage: number;
onPageChange: (event: React.MouseEvent<HTMLButtonElement>, newPage: number) => void;
}
function TablePaginationActions(props: TablePaginationActionsProps) {
const theme = useTheme();
const { count, page, rowsPerPage, onPageChange } = props;
const handleFirstPageButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
onPageChange(event, 0);
};
const handleBackButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
onPageChange(event, page - 1);
};
const handleNextButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
onPageChange(event, page + 1);
};
const handleLastPageButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1));
};
return (
<Box
sx={{ flexShrink: 0, ml: 2.5 }}
className="table-pagination-actions"
>
<IconButton
onClick={handleFirstPageButtonClick}
disabled={page === 0}
aria-label="first page"
>
{theme.direction === "rtl" ? <LastPageIcon /> : <FirstPageIcon />}
</IconButton>
<IconButton
onClick={handleBackButtonClick}
disabled={page === 0}
aria-label="previous page"
>
{theme.direction === "rtl" ? <KeyboardArrowRight /> : <KeyboardArrowLeft />}
</IconButton>
<IconButton
onClick={handleNextButtonClick}
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
aria-label="next page"
>
{theme.direction === "rtl" ? <KeyboardArrowLeft /> : <KeyboardArrowRight />}
</IconButton>
<IconButton
onClick={handleLastPageButtonClick}
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
aria-label="last page"
>
{theme.direction === "rtl" ? <FirstPageIcon /> : <LastPageIcon />}
</IconButton>
</Box>
);
}
export const Pagination: React.FC<TablePaginationProps> = ({ ...props }) => {
const isSmall = useMediaQuery((theme: any) => theme.breakpoints.down("sm"));
const theme = useTheme();
return (
<TablePagination
ActionsComponent={TablePaginationActions}
rowsPerPageOptions={[5, 10, 25]}
{...props}
sx={{
"& .MuiTablePagination-toolbar": {
display: isSmall ? "grid" : "flex",
},
"& .MuiTablePagination-selectLabel": {
gridColumn: "1",
gridRow: "1",
justifySelf: "center",
},
"& .MuiTablePagination-select": {
gridColumn: "2",
gridRow: "1",
justifySelf: "center",
},
"& .MuiTablePagination-displayedRows": {
gridColumn: "2",
gridRow: "2",
justifySelf: "center ",
},
"& .table-pagination-actions": {
gridColumn: "1",
gridRow: "2",
justifySelf: "center",
},
"& .MuiSelect-select": {
border: 1,
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
},
}}
/>
);
};
@@ -1,9 +0,0 @@
export { SplitBox as HorizontalSplitBox, ConfigBox } from "./SplitBox";
export { BasePage, BasePageWithStates } from "./BasePage";
export { BGBox, UpStatusBox, DownStatusBox, PausedStatusBox } from "./StatusBox";
export { DataTable as Table, Pagination } from "./Table";
export { GradientBox, StatBox } from "./StatBox";
export { BaseBox } from "./BaseBox";
export { StatusLabel } from "./StatusLabel";
export { BaseFallback, ErrorFallback, EmptyFallback } from "./Fallback";
export { BulletPointCheck } from "./BulletPointCheck";
@@ -1,48 +0,0 @@
import Autocomplete from "@mui/material/Autocomplete";
import type { AutocompleteProps } from "@mui/material/Autocomplete";
import { TextInput } from "@/Components/v2/Inputs/TextInput";
import { CheckboxInput } from "@/Components/v2/Inputs/Checkbox";
import ListItem from "@mui/material/ListItem";
import { useTheme } from "@mui/material/styles";
type AutoCompleteInputProps = Omit<
AutocompleteProps<any, boolean, boolean, boolean>,
"renderInput"
> & {
renderInput?: AutocompleteProps<any, boolean, boolean, boolean>["renderInput"];
};
export const AutoCompleteInput: React.FC<AutoCompleteInputProps> = ({ ...props }) => {
const theme = useTheme();
return (
<Autocomplete
{...props}
disableCloseOnSelect
renderInput={(params) => (
<TextInput
{...params}
placeholder="Type to search"
/>
)}
getOptionKey={(option) => option._id}
renderTags={() => null}
renderOption={(props, option, { selected }) => {
const { key, ...optionProps } = props;
return (
<ListItem
key={key}
{...optionProps}
>
<CheckboxInput checked={selected} />
{option.name}
</ListItem>
);
}}
sx={{
"&.MuiAutocomplete-root .MuiAutocomplete-input": {
padding: `0 ${theme.spacing(5)}`,
},
}}
/>
);
};
@@ -1,11 +0,0 @@
import Button from "@mui/material/Button";
import type { ButtonProps } from "@mui/material/Button";
export const ButtonInput: React.FC<ButtonProps> = ({ sx, ...props }) => {
return (
<Button
{...props}
sx={{ textTransform: "none", height: 34, fontWeight: 400, borderRadius: 2, ...sx }}
/>
);
};
@@ -1,13 +0,0 @@
import ButtonGroup from "@mui/material/ButtonGroup";
import type { ButtonGroupProps } from "@mui/material/ButtonGroup";
export const ButtonGroupInput: React.FC<ButtonGroupProps> = ({
orientation,
...props
}) => {
return (
<ButtonGroup
orientation={orientation}
{...props}
/>
);
};
@@ -1,23 +0,0 @@
import Checkbox from "@mui/material/Checkbox";
import type { CheckboxProps } from "@mui/material/Checkbox";
import CheckboxOutline from "@/assets/icons/checkbox-outline.svg?react";
import CheckboxFilled from "@/assets/icons/checkbox-filled.svg?react";
import { useTheme } from "@mui/material/styles";
type CheckboxInputProps = CheckboxProps & {
label?: string;
};
export const CheckboxInput: React.FC<CheckboxInputProps> = ({ label, ...props }) => {
const theme = useTheme();
return (
<Checkbox
{...props}
icon={<CheckboxOutline />}
checkedIcon={<CheckboxFilled />}
sx={{
"&:hover": { backgroundColor: "transparent" },
"& svg": { width: theme.spacing(8), height: theme.spacing(8) },
}}
/>
);
};
@@ -1,64 +0,0 @@
import "flag-icons/css/flag-icons.min.css";
import { Select } from "@/Components/v2/Inputs";
import MenuItem from "@mui/material/MenuItem";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { useTranslation } from "react-i18next";
import { useTheme } from "@mui/material/styles";
import { useDispatch } from "react-redux";
import { useSelector } from "react-redux";
import { setLanguage } from "@/Features/UI/uiSlice";
import type { SelectChangeEvent } from "@mui/material/Select";
export const LanguageSelector = () => {
const { i18n } = useTranslation();
const theme = useTheme();
const dispatch = useDispatch();
const language = useSelector((state: any) => state.ui.language);
const languages = Object.keys(i18n.options.resources || {});
const languageMap: Record<string, string> = {
cs: "cz",
ja: "jp",
uk: "ua",
vi: "vn",
};
const handleChange = (event: SelectChangeEvent<unknown>) => {
const newLang = event.target.value;
dispatch(setLanguage(newLang));
};
const languagesForDisplay = languages.map((l) => {
let formattedLanguage = l === "en" ? "gb" : l;
formattedLanguage = formattedLanguage.includes("-")
? formattedLanguage.split("-")[1].toLowerCase()
: formattedLanguage;
formattedLanguage = languageMap[formattedLanguage] || formattedLanguage;
const flag = formattedLanguage ? `fi fi-${formattedLanguage}` : null;
return (
<MenuItem
key={l}
value={l}
>
<Stack
direction="row"
gap={theme.spacing(4)}
>
{flag && <span className={flag} />}
<Typography textTransform={"uppercase"}>{l}</Typography>
</Stack>
</MenuItem>
);
});
return (
<Select
value={language}
onChange={handleChange}
>
{languagesForDisplay}
</Select>
);
};
@@ -1,66 +0,0 @@
import Radio from "@mui/material/Radio";
import type { RadioProps } from "@mui/material/Radio";
import { useTheme } from "@mui/material/styles";
import RadioChecked from "@/assets/icons/radio-checked.svg?react";
import FormControlLabel from "@mui/material/FormControlLabel";
import Typography from "@mui/material/Typography";
interface RadioInputProps extends RadioProps {}
export const RadioInput: React.FC<RadioInputProps> = ({ ...props }) => {
const theme = useTheme();
return (
<Radio
{...props}
checkedIcon={<RadioChecked />}
sx={{
color: "transparent",
boxShadow: `inset 0 0 0 1px ${theme.palette.secondary.main}`,
"&:not(.Mui-checked)": {
boxShadow: `inset 0 0 0 1px ${theme.palette.primary.contrastText}70`, // Use theme text color for the outline
},
mt: theme.spacing(0.5),
padding: 0,
"& .MuiSvgIcon-root": {
fontSize: 16,
},
}}
/>
);
};
export const RadioWithDescription: React.FC<
RadioInputProps & { label: string; description: string }
> = ({ label, description, ...props }) => {
const theme = useTheme();
return (
<FormControlLabel
control={<RadioInput {...props} />}
label={
<>
<Typography component="p">{label}</Typography>
<Typography
component="h6"
color={theme.palette.primary.contrastTextSecondary}
>
{description}
</Typography>
</>
}
sx={{
alignItems: "flex-start",
p: theme.spacing(2.5),
m: theme.spacing(-2.5),
borderRadius: theme.shape.borderRadius,
"&:hover": {
backgroundColor: theme.palette.tertiary.main,
},
"& .MuiButtonBase-root": {
p: 0,
mr: theme.spacing(6),
},
}}
/>
);
};
@@ -1,88 +0,0 @@
import { Typography, Select } from "@mui/material";
import MenuItem from "@mui/material/MenuItem";
import type { SelectProps } from "@mui/material/Select";
import { useTheme } from "@mui/material/styles";
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
export const SelectInput: React.FC<SelectProps> = ({ ...props }) => {
const theme = useTheme();
return (
<Select
{...props}
sx={{
height: "34px",
"& .MuiOutlinedInput-notchedOutline": {
borderRadius: theme.shape.borderRadius,
borderColor: theme.palette.primary.lowContrast,
},
"&:hover .MuiOutlinedInput-notchedOutline": {
borderColor: theme.palette.primary.lowContrast,
},
}}
/>
);
};
type ItemTypes = string | number;
interface SelectItem {
_id: ItemTypes;
name: string;
}
export type CustomSelectProps = SelectProps & {
items: SelectItem[];
placeholder?: string;
isHidden?: boolean;
hasError?: boolean;
};
export const SelectFromItems: React.FC<CustomSelectProps> = ({
items,
placeholder,
isHidden = false,
hasError = false,
...props
}) => {
return (
<SelectInput
error={hasError}
IconComponent={KeyboardArrowDownIcon}
displayEmpty
MenuProps={{ disableScrollLock: true }}
renderValue={(selected) => {
if (!selected) {
return (
<Typography
noWrap
color="text.secondary"
>
{placeholder ?? ""}
</Typography>
);
}
const selectedItem = items.find((item) => item._id === selected);
const displayName = selectedItem ? selectedItem.name : placeholder;
return (
<Typography
noWrap
title={displayName}
>
{displayName}
</Typography>
);
}}
{...props}
>
{items.map((item) => (
<MenuItem
key={item._id}
value={item._id}
>
{item.name}
</MenuItem>
))}
</SelectInput>
);
};
SelectInput.displayName = "SelectInput";
SelectFromItems.displayName = "SelectFromItems";
@@ -1,22 +0,0 @@
import { forwardRef } from "react";
import TextField from "@mui/material/TextField";
import type { TextFieldProps } from "@mui/material";
import { typographyLevels } from "@/Utils/Theme/v2/palette";
export const TextInput = forwardRef<HTMLInputElement, TextFieldProps>(
function TextInput(props, ref) {
return (
<TextField
{...props}
inputRef={ref}
sx={{
"& .MuiOutlinedInput-root": {
height: 34,
fontSize: typographyLevels.base,
},
}}
/>
);
}
);
TextInput.displayName = "TextInput";
@@ -1,37 +0,0 @@
import Typography from "@mui/material/Typography";
import Stack from "@mui/material/Stack";
import Link from "@mui/material/Link";
import { Link as RouterLink } from "react-router-dom";
import { useTheme } from "@mui/material/styles";
export const TextLink = ({
text,
linkText,
href,
target = "_self",
}: {
text: string;
linkText: string;
href: string;
target?: string;
}) => {
const theme = useTheme();
return (
<Stack
direction="row"
gap={theme.spacing(4)}
>
<Typography>{text}</Typography>
<Link
color="accent"
to={href}
component={RouterLink}
target={target}
>
{linkText}
</Link>
</Stack>
);
};
@@ -1,135 +0,0 @@
import { useTheme } from "@mui/material/styles";
import { useDispatch, useSelector } from "react-redux";
import { setMode } from "@/Features/UI/uiSlice.js";
import { useTranslation } from "react-i18next";
import IconButton from "@mui/material/IconButton";
const SunAndMoonIcon = () => {
const theme = useTheme();
return (
<svg
className="sun-and-moon"
aria-hidden="true"
width="24"
height="24"
viewBox="0 0 24 24"
>
<mask
className="moon"
id="moon-mask"
>
<rect
x="0"
y="0"
width="100%"
height="100%"
fill="#fff"
/>
<circle
cx="24"
cy="10"
r="6"
fill="#000"
/>
</mask>
<circle
className="sun"
cx="12"
cy="12"
r="6"
fill={theme.palette.primary.contrastTextSecondary}
mask="url(#moon-mask)"
/>
<g
className="sun-beams"
stroke={theme.palette.primary.contrastTextSecondary}
>
<line
x1="12"
y1="1"
x2="12"
y2="3"
/>
<line
x1="12"
y1="21"
x2="12"
y2="23"
/>
<line
x1="4.22"
y1="4.22"
x2="5.64"
y2="5.64"
/>
<line
x1="18.36"
y1="18.36"
x2="19.78"
y2="19.78"
/>
<line
x1="1"
y1="12"
x2="3"
y2="12"
/>
<line
x1="21"
y1="12"
x2="23"
y2="12"
/>
<line
x1="4.22"
y1="19.78"
x2="5.64"
y2="18.36"
/>
<line
x1="18.36"
y1="5.64"
x2="19.78"
y2="4.22"
/>
</g>
</svg>
);
};
export const ThemeSwitch = ({
width = 48,
height = 48,
}: {
width?: number;
height?: number;
}) => {
const mode = useSelector((state: any) => state.ui.mode);
const dispatch = useDispatch();
const { t } = useTranslation();
const handleChange = () => {
dispatch(setMode(mode === "light" ? "dark" : "light"));
};
return (
<IconButton
id="theme-toggle"
title={t("common.buttons.toggleTheme")}
className={`theme-${mode}`}
aria-label="auto"
aria-live="polite"
onClick={handleChange}
sx={{
width,
height,
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<SunAndMoonIcon />
</IconButton>
);
};
@@ -1,7 +0,0 @@
export { ButtonInput as Button } from "./Button";
export { ButtonGroupInput as ButtonGroup } from "./ButtonGroup";
export { TextInput } from "./TextInput";
export { SelectInput as Select } from "./Select";
export { LanguageSelector } from "./LanguageSelector";
export { ThemeSwitch } from "./ThemeSwitch";
export { TextLink } from "./TextLink";
@@ -1,23 +0,0 @@
import { Outlet } from "react-router";
import Stack from "@mui/material/Stack";
import { SideBar } from "@/Components/v2/Layouts/Sidebar";
import { useTheme } from "@mui/material/styles";
const RootLayout = () => {
const theme = useTheme();
return (
<Stack
direction="row"
minHeight="100vh"
>
<SideBar />
<Stack
flex={1}
padding={theme.spacing(12)}
>
<Outlet />
</Stack>
</Stack>
);
};
export default RootLayout;
@@ -1,16 +0,0 @@
import { useTheme } from "@mui/material/styles";
import Stack from "@mui/material/Stack";
export const BottomControls = ({}) => {
const theme = useTheme();
return (
<Stack
direction="row"
height={50}
py={theme.spacing(4)}
px={theme.spacing(8)}
gap={theme.spacing(2)}
></Stack>
);
};
@@ -1,50 +0,0 @@
import IconButton from "@mui/material/IconButton";
import { ArrowRight } from "@/Components/v2/Arrows/ArrowRight";
import { ArrowLeft } from "@/Components/v2/Arrows/ArrowLeft";
import { useTheme } from "@mui/material/styles";
import { useDispatch } from "react-redux";
import { toggleSidebar } from "../../../../Features/UI/uiSlice.js";
export const CollapseButton = ({ collapsed }: { collapsed: boolean }) => {
const theme = useTheme();
const dispatch = useDispatch();
const arrowIcon = collapsed ? (
<ArrowRight
height={theme.spacing(8)}
width={theme.spacing(8)}
color={theme.palette.primary.contrastTextSecondary}
/>
) : (
<ArrowLeft
height={theme.spacing(8)}
width={theme.spacing(8)}
color={theme.palette.primary.contrastTextSecondary}
/>
);
return (
<IconButton
sx={{
position: "absolute",
/* TODO 60 is a magic number. if logo chnges size this might break */
top: 60,
right: 0,
transform: `translate(50%, 0)`,
backgroundColor: theme.palette.tertiary.main,
border: `1px solid ${theme.palette.primary.lowContrast}`,
p: theme.spacing(2.5),
"&:focus": { outline: "none" },
"&:hover": {
backgroundColor: theme.palette.primary.lowContrast,
borderColor: theme.palette.primary.lowContrast,
},
}}
onClick={() => {
dispatch(toggleSidebar());
}}
>
{arrowIcon}
</IconButton>
);
};
@@ -1,59 +0,0 @@
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import { useTheme } from "@mui/material/styles";
import { useNavigate } from "react-router";
import { useTranslation } from "react-i18next";
export const Logo = ({ collapsed }: { collapsed: boolean }) => {
const { t } = useTranslation();
const theme = useTheme();
const navigate = useNavigate();
return (
<Stack
pt={theme.spacing(6)}
pb={theme.spacing(12)}
pl={theme.spacing(8)}
direction="row"
alignItems="center"
gap={theme.spacing(4)}
onClick={() => navigate("/")}
sx={{ cursor: "pointer" }}
>
<Typography
pl={theme.spacing("1px")}
minWidth={theme.spacing(16)}
minHeight={theme.spacing(16)}
display={"flex"}
justifyContent={"center"}
alignItems={"center"}
bgcolor={theme.palette.accent.main}
borderRadius={theme.shape.borderRadius}
color={theme.palette.accent.contrastText}
fontSize={18}
>
C
</Typography>
<Box
overflow={"hidden"}
sx={{
transition: "opacity 900ms ease, width 900ms ease",
opacity: collapsed ? 0 : 1,
whiteSpace: "nowrap",
width: collapsed ? 0 : "100%",
}}
>
{" "}
<Typography
lineHeight={1}
mt={theme.spacing(2)}
color={theme.palette.primary.contrastText}
variant="h2"
>
{t("common.appName")}
</Typography>
</Box>
</Stack>
);
};
@@ -1,58 +0,0 @@
// import Notifications from "@/assets/icons/notifications.svg?react";
import Monitors from "@/assets/icons/monitors.svg?react";
// import PageSpeed from "@/assets/icons/page-speed.svg?react";
// import Integrations from "@/assets/icons/integrations.svg?react";
// import Incidents from "@/assets/icons/incidents.svg?react";
// import StatusPages from "@/assets/icons/status-pages.svg?react";
// import Maintenance from "@/assets/icons/maintenance.svg?react";
// import Logs from "@/assets/icons/logs.svg?react";
// import Settings from "@/assets/icons/settings.svg?react";
import Support from "@/assets/icons/support.svg?react";
import Discussions from "@/assets/icons/discussions.svg?react";
import Docs from "@/assets/icons/docs.svg?react";
import ChangeLog from "@/assets/icons/changeLog.svg?react";
export const getMenu = (t: Function) => [
{ name: t("menu.uptime"), path: "v2/uptime", icon: <Monitors /> },
// { name: t("menu.pagespeed"), path: "pagespeed", icon: <PageSpeed /> },
// { name: t("menu.infrastructure"), path: "infrastructure", icon: <Integrations /> },
// {
// name: t("menu.notifications"),
// path: "notifications",
// icon: <Notifications />,
// },
// { name: t("menu.incidents"), path: "incidents", icon: <Incidents /> },
// { name: t("menu.statusPages"), path: "status", icon: <StatusPages /> },
// { name: t("menu.maintenance"), path: "maintenance", icon: <Maintenance /> },
// { name: t("menu.logs"), path: "logs", icon: <Logs /> },
// {
// name: t("menu.settings"),
// icon: <Settings />,
// path: "settings",
// },
];
export const getBottomMenu = (t: Function) => [
{ name: t("menu.support"), path: "support", icon: <Support />, url: "invite" },
{
name: t("menu.discussions"),
path: "discussions",
icon: <Discussions />,
url: "https://github.com/bluewave-labs/checkmate/discussions",
},
{
name: t("menu.docs"),
path: "docs",
icon: <Docs />,
url: "https://bluewavelabs.gitbook.io/checkmate",
},
{
name: t("menu.changelog"),
path: "changelog",
icon: <ChangeLog />,
url: "https://github.com/bluewave-labs/checkmate/releases",
},
];
@@ -1,103 +0,0 @@
import Tooltip from "@mui/material/Tooltip";
import ListItemButton from "@mui/material/ListItemButton";
import ListItemIcon from "@mui/material/ListItemIcon";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import { useTheme } from "@mui/material/styles";
export interface NavData {
name: string;
icon: JSX.Element;
}
export const NavItem = ({
item,
collapsed,
selected,
onClick,
}: {
item: NavData;
collapsed: boolean;
selected: boolean;
onClick: (event: React.MouseEvent) => void;
}) => {
const theme = useTheme();
const iconStroke = selected
? theme.palette.primary.contrastText
: theme.palette.primary.contrastTextTertiary;
const buttonBgColor = selected ? theme.palette.secondary.main : "transparent";
const buttonBgHoverColor = selected
? theme.palette.secondary.main
: theme.palette.tertiary.main;
const fontWeight = selected ? 600 : 400;
return (
<Tooltip
placement="right"
title={collapsed ? item.name : ""}
slotProps={{
popper: {
modifiers: [
{
name: "offset",
options: {
offset: [0, -16],
},
},
],
},
}}
disableInteractive
>
<ListItemButton
sx={{
backgroundColor: buttonBgColor,
"&:hover": {
backgroundColor: buttonBgHoverColor,
},
height: 37,
gap: theme.spacing(4),
borderRadius: theme.shape.borderRadius,
px: theme.spacing(4),
pl: theme.spacing(5),
}}
onClick={onClick}
>
<ListItemIcon
sx={{
minWidth: 0,
"& svg": {
height: 20,
width: 20,
opacity: 0.81,
},
"& svg path": {
stroke: iconStroke,
},
}}
>
{item.icon}
</ListItemIcon>
<Box
sx={{
overflow: "hidden",
transition: "opacity 900ms ease",
opacity: collapsed ? 0 : 1,
whiteSpace: "nowrap",
}}
>
<Typography
variant="body1"
color={theme.palette.primary.contrastText}
sx={{
fontWeight: fontWeight,
opacity: 0.9,
}}
>
{item.name}
</Typography>
</Box>
</ListItemButton>
</Tooltip>
);
};
@@ -1,106 +0,0 @@
import { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { setCollapsed } from "@/Features/UI/uiSlice";
import useMediaQuery from "@mui/material/useMediaQuery";
import { useTheme } from "@mui/material/styles";
import { useTranslation } from "react-i18next";
import { useNavigate, useLocation } from "react-router-dom";
import { CollapseButton } from "@/Components/v2/Layouts/Sidebar/CollapseButton";
import Stack from "@mui/material/Stack";
import List from "@mui/material/List";
import Divider from "@mui/material/Divider";
import { Logo } from "@/Components/v2/Layouts/Sidebar/Logo";
import { getMenu, getBottomMenu } from "@/Components/v2/Layouts/Sidebar/Menu";
import { NavItem } from "@/Components/v2/Layouts/Sidebar/NavItem";
import { BottomControls } from "@/Components/v2/Layouts/Sidebar/BottomControls";
export const COLLAPSED_WIDTH = 64;
export const EXPANDED_WIDTH = 250;
export const SideBar = () => {
const theme = useTheme();
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
const dispatch = useDispatch();
const collapsed = useSelector((state: any) => state.ui.sidebar.collapsed);
const { t } = useTranslation();
const navigate = useNavigate();
const location = useLocation();
const menu = getMenu(t);
const bottomMenu = getBottomMenu(t);
useEffect(() => {
dispatch(setCollapsed({ collapsed: isSmall }));
}, [isSmall]);
return (
<Stack
component="aside"
position="sticky"
top={0}
minHeight={"100vh"}
maxHeight={"100vh"}
paddingTop={theme.spacing(6)}
paddingBottom={theme.spacing(6)}
gap={theme.spacing(6)}
borderRight={`1px solid ${theme.palette.primary.lowContrast}`}
width={collapsed ? COLLAPSED_WIDTH : EXPANDED_WIDTH}
sx={{
transition: "width 650ms cubic-bezier(0.36, -0.01, 0, 0.77)",
}}
>
<CollapseButton collapsed={collapsed} />
<Logo collapsed={collapsed} />
<List
component="nav"
disablePadding
sx={{
px: theme.spacing(6),
height: "100%",
}}
>
{menu.map((item) => {
const selected = location.pathname.startsWith(`/${item.path}`);
return (
<NavItem
key={item.path}
item={item}
collapsed={collapsed}
selected={selected}
onClick={() => navigate(`/${item.path}`)}
/>
);
})}
</List>
<List
component="nav"
disablePadding
sx={{
px: theme.spacing(6),
}}
>
{bottomMenu.map((item) => {
const selected = location.pathname.startsWith(`/${item.path}`);
return (
<NavItem
key={item.path}
item={item}
collapsed={collapsed}
selected={selected}
onClick={() => {
if (item.url) {
window.open(item.url, "_blank", "noreferrer");
} else {
navigate(`/${item.path}`);
}
}}
/>
);
})}
</List>
<Divider sx={{ mt: "auto", borderColor: theme.palette.primary.lowContrast }} />
<BottomControls />
</Stack>
);
};
@@ -1,67 +0,0 @@
import Stack from "@mui/material/Stack";
import { MonitorStatus } from "@/Components/v2/Monitors/MonitorStatus";
import { ButtonGroup, Button } from "@/Components/v2/Inputs";
import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined";
import PauseOutlinedIcon from "@mui/icons-material/PauseOutlined";
import PlayArrowOutlinedIcon from "@mui/icons-material/PlayArrowOutlined";
import EmailIcon from "@mui/icons-material/Email";
import BugReportOutlinedIcon from "@mui/icons-material/BugReportOutlined";
import { useMediaQuery } from "@mui/material";
import { useTranslation } from "react-i18next";
import { useTheme } from "@mui/material/styles";
import type { IMonitor } from "@/Types/Monitor";
export const HeaderControls = ({
monitor,
patch,
isPatching,
refetch,
}: {
monitor: IMonitor;
patch: Function;
isPatching: boolean;
refetch: Function;
}) => {
const { t } = useTranslation();
const theme = useTheme();
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
return (
<Stack
direction={isSmall ? "column" : "row"}
spacing={isSmall ? theme.spacing(4) : 0}
justifyContent={"space-between"}
>
<MonitorStatus monitor={monitor} />
<Stack
direction={"row"}
spacing={theme.spacing(2)}
>
<ButtonGroup
orientation={isSmall ? "vertical" : "horizontal"}
fullWidth={isSmall}
variant="contained"
color="secondary"
>
<Button startIcon={<EmailIcon />}>{t("sendTestNotifications")}</Button>
<Button startIcon={<BugReportOutlinedIcon />}>{t("menu.incidents")}</Button>
<Button
loading={isPatching}
onClick={async () => {
await patch(`/monitors/${monitor._id}/active`);
refetch();
}}
startIcon={
monitor?.isActive ? <PauseOutlinedIcon /> : <PlayArrowOutlinedIcon />
}
>
{monitor?.isActive ? t("pause") : t("resume")}
</Button>
<Button startIcon={<SettingsOutlinedIcon />}>{t("configure")}</Button>
</ButtonGroup>
</Stack>
</Stack>
);
};
@@ -1,35 +0,0 @@
import Stack from "@mui/material/Stack";
import { Button } from "@/Components/v2/Inputs";
import { useTheme } from "@mui/material/styles";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router";
export const HeaderCreate = ({
label,
isLoading,
path,
}: {
label?: string;
isLoading: boolean;
path: string;
}) => {
const theme = useTheme();
const { t } = useTranslation();
const navigate = useNavigate();
return (
<Stack
direction="row"
justifyContent="end"
alignItems="center"
gap={theme.spacing(6)}
>
<Button
loading={isLoading}
variant="contained"
color="accent"
onClick={() => navigate(path)}
>
{label || t("createNew")}
</Button>
</Stack>
);
};
@@ -1,62 +0,0 @@
import Stack from "@mui/material/Stack";
import { ButtonGroup, Button } from "@/Components/v2/Inputs";
import { useTheme } from "@mui/material/styles";
import Typography from "@mui/material/Typography";
import { useMediaQuery } from "@mui/material";
export const HeaderRange = ({
range,
setRange,
loading,
}: {
range: string;
setRange: Function;
loading: boolean;
}) => {
const theme = useTheme();
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
return (
<Stack
gap={theme.spacing(9)}
direction={isSmall ? "column" : "row"}
alignItems={"center"}
justifyContent="flex-end"
>
<Typography variant="body2">{`Showing statistics for past ${range}`}</Typography>
<ButtonGroup
orientation={isSmall ? "vertical" : "horizontal"}
fullWidth={isSmall}
variant="contained"
color={"primary"}
>
<Button
color={range === "2h" ? "secondary" : "inherit"}
onClick={() => setRange("2h")}
loading={loading}
>
Recent
</Button>
<Button
color={range === "24h" ? "secondary" : "inherit"}
onClick={() => setRange("24h")}
loading={loading}
>
Day
</Button>
<Button
color={range === "7d" ? "secondary" : "inherit"}
onClick={() => setRange("7d")}
loading={loading}
>
7 days
</Button>
<Button
color={range === "30d" ? "secondary" : "inherit"}
onClick={() => setRange("30d")}
loading={loading}
>
30 days
</Button>
</ButtonGroup>
</Stack>
);
};
@@ -1,87 +0,0 @@
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import { useTheme } from "@mui/material/styles";
import type { Check } from "@/Types/Check";
import { HistogramResponseTimeTooltip } from "@/Components/v2/Monitors/HistogramResponseTimeTooltip";
import { normalizeResponseTimes } from "@/Utils/v2/DataUtils";
export const HistogramResponseTime = ({ checks }: { checks: Check[] }) => {
const normalChecks = normalizeResponseTimes(checks, "responseTime");
let data = Array<any>();
if (!normalChecks || normalChecks.length === 0) {
return null;
}
if (normalChecks.length !== 25) {
const placeholders = Array(25 - normalChecks.length).fill("placeholder");
data = [...normalChecks, ...placeholders];
} else {
data = normalChecks;
}
const theme = useTheme();
return (
<Stack
direction="row"
flexWrap="nowrap"
gap={theme.spacing(1.5)}
height="50px"
width="fit-content"
onClick={(event) => event.stopPropagation()}
sx={{
cursor: "default",
}}
>
{data.map((check, index) => {
if (check === "placeholder") {
return (
<Box
key={`${check}-${index}`}
position="relative"
width={theme.spacing(4.5)}
height="100%"
bgcolor={theme.palette.primary.lowContrast}
sx={{
borderRadius: theme.spacing(1.5),
}}
/>
);
} else {
return (
<HistogramResponseTimeTooltip
key={`${check}-${index}`}
check={check}
>
<Box
position="relative"
width="9px"
height="100%"
bgcolor={theme.palette.primary.lowContrast}
sx={{
borderRadius: theme.spacing(1.5),
}}
>
<Box
position="absolute"
bottom={0}
width="100%"
height={`${check.normalResponseTime}%`}
bgcolor={
check.status
? theme.palette.success.lowContrast
: theme.palette.error.lowContrast
}
sx={{
borderRadius: theme.spacing(1.5),
transition: "height 600ms cubic-bezier(0.4, 0, 0.2, 1)",
}}
/>
</Box>
</HistogramResponseTimeTooltip>
);
}
})}
</Stack>
);
};
@@ -1,29 +0,0 @@
import Stack from "@mui/material/Stack";
import Tooltip from "@mui/material/Tooltip";
import Typography from "@mui/material/Typography";
import { formatDateWithTz } from "@/Utils/v2/TimeUtils";
import { useSelector } from "react-redux";
import type { LatestCheck } from "@/Types/Check";
export const HistogramResponseTimeTooltip: React.FC<{
children: React.ReactElement;
check: LatestCheck;
}> = ({ children, check }) => {
const uiTimezone = useSelector((state: any) => state.ui.timezone);
return (
<Tooltip
title={
<Stack>
<Typography>
{formatDateWithTz(check?.checkedAt, "ddd, MMMM D, YYYY, HH:mm A", uiTimezone)}
</Typography>
{check?.responseTime && (
<Typography>Response Time: {check.responseTime} ms</Typography>
)}
</Stack>
}
>
{children}
</Tooltip>
);
};
@@ -1,218 +0,0 @@
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import { BaseBox } from "@/Components/v2/DesignElements";
import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts";
import UptimeIcon from "@/assets/icons/uptime-icon.svg?react";
import IncidentsIcon from "@/assets/icons/incidents.svg?react";
import type { GroupedCheck } from "@/Types/Check";
import type { MonitorStatus } from "@/Types/Monitor";
import { normalizeResponseTimes } from "@/Utils/v2/DataUtils";
import { useState } from "react";
import { formatDateWithTz } from "@/Utils/v2/TimeUtils";
import { useSelector } from "react-redux";
import { useTheme } from "@mui/material/styles";
import { getResponseTimeColor } from "@/Utils/v2/MonitorUtils";
const XLabel = ({
p1,
p2,
range,
}: {
p1: GroupedCheck;
p2: GroupedCheck;
range: string;
}) => {
const theme = useTheme();
const uiTimezone = useSelector((state: any) => state.ui.timezone);
const dateFormat = range === "day" ? "MMM D, h:mm A" : "MMM D";
return (
<>
<text
x={0}
y="100%"
dy={-3}
textAnchor="start"
fontSize={11}
fill={theme.palette.primary.contrastTextTertiary}
>
{formatDateWithTz(p1._id, dateFormat, uiTimezone)}
</text>
<text
x="100%"
y="100%"
dy={-3}
textAnchor="end"
fontSize={11}
fill={theme.palette.primary.contrastTextTertiary}
>
{formatDateWithTz(p2._id, dateFormat, uiTimezone)}
</text>
</>
);
};
type BaseChartProps = React.PropsWithChildren<{
icon: React.ReactNode;
title: string;
}>;
export const BaseChart: React.FC<BaseChartProps> = ({ children, icon, title }) => {
const theme = useTheme();
return (
<BaseBox
sx={{
padding: theme.spacing(8),
display: "flex",
flex: 1,
}}
>
<Stack
gap={theme.spacing(8)}
flex={1}
>
<Stack
direction="row"
alignItems={"center"}
gap={theme.spacing(4)}
>
<BaseBox
sx={{
display: "flex",
alignItems: "center",
justifyContent: "center",
width: 34,
height: 34,
backgroundColor: theme.palette.tertiary.main,
"& svg": {
width: 20,
height: 20,
"& path": {
stroke: theme.palette.primary.contrastTextTertiary,
},
},
}}
>
{icon}
</BaseBox>
<Typography variant="h2">{title}</Typography>
</Stack>
<Box flex={1}>{children}</Box>
</Stack>
</BaseBox>
);
};
export const HistogramStatus = ({
checks,
status,
range,
title,
}: {
checks: GroupedCheck[];
status: MonitorStatus;
range: string;
title: string;
}) => {
const uiTimezone = useSelector((state: any) => state.ui.timezone);
const icon = status === "up" ? <UptimeIcon /> : <IncidentsIcon />;
const theme = useTheme();
const [idx, setIdx] = useState<number | null>(null);
const dateFormat = range === "1d" || range === "2h" ? "MMM D, h A" : "MMM D";
const normalChecks = normalizeResponseTimes(checks, "avgResponseTime");
if (normalChecks.length === 0) {
return (
<BaseChart
icon={icon}
title={title}
>
<Stack
height={"100%"}
alignItems={"center"}
justifyContent={"center"}
>
<Typography variant="h2">
{status === "up" ? "No checks yet" : "Great, no downtime yet!"}
</Typography>
</Stack>
</BaseChart>
);
}
const totalChecks = normalChecks.reduce((count, check) => {
return count + check.count;
}, 0);
return (
<BaseChart
icon={icon}
title={title}
>
<Stack gap={theme.spacing(8)}>
<Stack
position="relative"
direction="row"
justifyContent="space-between"
>
<Stack>
<Typography>Total checks</Typography>
{idx ? (
<Stack>
<Typography variant="h2">{normalChecks[idx].count}</Typography>
<Typography
position={"absolute"}
top={"100%"}
>
{formatDateWithTz(normalChecks[idx]._id, dateFormat, uiTimezone)}
</Typography>
</Stack>
) : (
<Typography variant="h2">{totalChecks}</Typography>
)}
</Stack>
</Stack>
<ResponsiveContainer
width="100%"
height={155}
>
<BarChart data={normalChecks}>
<XAxis
stroke={theme.palette.primary.lowContrast}
height={15}
tick={false}
label={
<XLabel
p1={normalChecks[0]}
p2={normalChecks[normalChecks.length - 1]}
range={range}
/>
}
/>
<Bar
dataKey="normalResponseTime"
maxBarSize={7}
background={{ fill: "transparent" }}
>
{normalChecks?.map((groupedCheck, idx) => {
const fillColor = getResponseTimeColor(groupedCheck.normalResponseTime);
return (
<Cell
onMouseEnter={() => setIdx(idx)}
onMouseLeave={() => setIdx(null)}
key={groupedCheck._id}
fill={theme.palette[fillColor].main}
/>
);
})}
</Bar>
</BarChart>
</ResponsiveContainer>
</Stack>
</BaseChart>
);
};
@@ -1 +0,0 @@
export { HeaderCreate } from "./HeaderCreate";
@@ -2,7 +2,7 @@ import React, { useState } from "react";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import IconButton from "@mui/material/IconButton";
import Settings from "@/assets/icons/settings-bold.svg?react";
import { Settings } from "lucide-react";
export type ActionMenuItem = {
id: number | string;
@@ -28,7 +28,10 @@ export const ActionsMenu = ({ items }: { items: ActionMenuItem[] }) => {
return (
<div>
<IconButton onClick={handleClick}>
<Settings />
<Settings
size={20}
strokeWidth={1.5}
/>
</IconButton>
<Menu
anchorEl={anchorEl}
@@ -0,0 +1,140 @@
import Box from "@mui/material/Box";
import { useTheme } from "@mui/material/styles";
import type { CheckSnapshot } from "@/Types/Check";
import { getResponseColor } from "@/Utils/DataUtils";
import { HeatmapResponseTimeTooltip } from "@/Components/v2/common/charts/HeatmapResponseTimeTooltip";
import type { SxProps } from "@mui/material/styles";
import type { ResponsiveStyleValue } from "@mui/system";
interface PlaceholderCheck {
status: "placeholder";
responseTime: 0;
createdAt: "";
}
interface HeatmapResponseTimeProps {
checks: CheckSnapshot[];
gap?: ResponsiveStyleValue<number | string>;
availabilityCellSx?: SxProps;
responseCellSx?: SxProps;
}
export const HeatmapResponseTime = ({
checks,
gap,
availabilityCellSx,
responseCellSx,
}: HeatmapResponseTimeProps) => {
const theme = useTheme();
if (!gap) {
gap = theme.spacing(0.5);
}
if (!checks || checks.length === 0) return null;
const latestChecks = checks.slice(-25).reverse();
let data: Array<CheckSnapshot | PlaceholderCheck>;
if (latestChecks.length !== 25) {
const placeholders = Array(25 - latestChecks.length).fill({
status: "placeholder" as const,
});
data = [...latestChecks, ...placeholders];
} else {
data = latestChecks;
}
return (
<Box sx={{ width: "100%" }}>
<Box
sx={{
width: "100%",
display: "grid",
gridTemplateColumns: "repeat(25, 1fr)",
gap: gap,
alignItems: "stretch",
}}
>
{data.map((check, index) => {
const statusBg =
check.status === "placeholder"
? theme.palette.action.hover
: check.status === true
? theme.palette.success.light
: theme.palette.error.light;
const respBg =
check.status === "placeholder"
? theme.palette.action.hover
: getResponseColor(check.responseTime || 0, {
start: theme.palette.success.main,
mid: theme.palette.warning.main,
end: theme.palette.error.main,
});
const statusBorder =
check.status === "placeholder"
? theme.palette.divider
: check.status === true
? theme.palette.success.main
: theme.palette.error.main;
return (
<HeatmapResponseTimeTooltip
key={`${check}-${index}`}
check={check}
>
<Box
sx={{
display: "grid",
gridTemplateRows: "auto auto",
}}
>
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: theme.spacing(2),
}}
>
<Box
sx={{
position: "relative",
width: "100%",
aspectRatio: "10",
}}
>
<Box
sx={{
position: "absolute",
inset: 0,
bgcolor: statusBg,
borderRadius: theme.spacing(0.5),
...availabilityCellSx,
}}
/>
<Box
sx={{
position: "absolute",
inset: 0,
pointerEvents: "none",
borderRadius: theme.spacing(0.5),
boxShadow: `inset 0 0 0 2px ${statusBorder}`,
}}
/>
</Box>
<Box
sx={{
width: "100%",
aspectRatio: "1 / 1",
bgcolor: respBg,
borderRadius: theme.spacing(0.5),
...responseCellSx,
}}
/>
</Box>
</Box>
</HeatmapResponseTimeTooltip>
);
})}
</Box>
</Box>
);
};
@@ -0,0 +1,78 @@
import Stack from "@mui/material/Stack";
import Tooltip from "@mui/material/Tooltip";
import Typography from "@mui/material/Typography";
import { formatDateWithTz } from "@/Utils/TimeUtils";
import type { CheckSnapshot } from "@/Types/Check";
import { useTheme } from "@mui/material/styles";
import { useSelector } from "react-redux";
import type { RootState } from "@/Types/state";
import { useTranslation } from "react-i18next";
type HeatmapCheck =
| CheckSnapshot
| { status: "placeholder"; responseTime: 0; createdAt: "" };
export const HeatmapResponseTimeTooltip = ({
children,
check,
}: {
children: React.ReactElement;
check: HeatmapCheck;
}) => {
const { t } = useTranslation();
const uiTimezone = useSelector((state: RootState) => state.ui.timezone);
const theme = useTheme();
const getColor = (status: boolean) => {
if (status === true) return theme.palette.success.light;
if (status === false) return theme.palette.error.light;
};
if (check.status === "placeholder") {
return children;
}
return (
<Tooltip
slotProps={{
tooltip: {
sx: {
backgroundColor: "transparent",
color: "inherit",
boxShadow: "none",
},
},
}}
title={
<Stack
sx={{
backgroundColor: theme.palette.secondary.main,
border: 1,
borderColor: theme.palette.divider,
borderRadius: theme.shape.borderRadius,
p: theme.spacing(4),
}}
>
<Typography>
{formatDateWithTz(check?.createdAt, "ddd, MMMM D, YYYY, HH:mm A", uiTimezone)}
</Typography>
{check?.responseTime && (
<Typography>
{t("common.labels.responseTime")}: {check.responseTime.toFixed()} ms
</Typography>
)}
<Typography textTransform={"capitalize"}>
Status:{" "}
<span style={{ color: getColor(check?.status) }}>
{check?.status === true
? t("pages.common.monitors.status.up")
: t("pages.common.monitors.status.down")}
</span>
</Typography>
</Stack>
}
>
{children}
</Tooltip>
);
};
@@ -0,0 +1,102 @@
import Box from "@mui/material/Box";
import { useTheme } from "@mui/material/styles";
import type { ResponsiveStyleValue } from "@mui/system";
import type { CheckSnapshot } from "@/Types/Check";
import { HeatmapResponseTimeTooltip } from "./HeatmapResponseTimeTooltip";
interface HistogramResponseTimeProps {
checks: CheckSnapshot[];
height?: ResponsiveStyleValue<number | string>;
gap?: ResponsiveStyleValue<number | string>;
}
const DEFAULT_HEIGHT = 50;
export const HistogramResponseTime = ({
checks,
height = DEFAULT_HEIGHT,
gap,
}: HistogramResponseTimeProps) => {
const theme = useTheme();
if (!Array.isArray(checks) || checks.length === 0) return null;
let data = Array<any>();
if (!checks || checks.length === 0) {
data = [];
}
if (checks.length !== 25) {
const placeholders = Array(25 - checks.length).fill({
status: "placeholder",
});
data = [...checks, ...placeholders];
} else {
data = checks;
}
const chartHeight = typeof height === "number" ? `${height}px` : height;
const gridGap = gap ?? theme.spacing(0.5);
return (
<Box sx={{ width: "100%" }}>
<Box
sx={{
width: "100%",
display: "grid",
gridTemplateColumns: "repeat(25, 1fr)",
gap: gridGap,
alignItems: "end",
height: chartHeight,
}}
>
{data.map((check, index) => {
const isPlaceholder = (check as any).status === "placeholder";
const heightPct = `${Math.max(0, Math.min(100, (check as any).responseTime ?? 0))}%`;
const barColor =
check.status === true
? theme.palette.success.light
: theme.palette.error.light;
const bar = (
<Box
sx={{
position: "relative",
width: "100%",
height: "100%",
borderRadius: theme.spacing(1),
bgcolor: theme.palette.action.hover,
overflow: "hidden",
}}
>
<Box
sx={{
position: "absolute",
bottom: 0,
left: 0,
width: "100%",
height: isPlaceholder ? 0 : heightPct,
bgcolor: barColor,
transition: "height 500ms cubic-bezier(0.4, 0, 0.2, 1)",
}}
/>
</Box>
);
return isPlaceholder ? (
<Box
key={index}
sx={{ height: "100%" }}
>
{bar}
</Box>
) : (
<HeatmapResponseTimeTooltip
key={index}
check={check}
>
{bar}
</HeatmapResponseTimeTooltip>
);
})}
</Box>
</Box>
);
};
@@ -0,0 +1,47 @@
import { Stack } from "@mui/material";
import { useNavigate } from "react-router-dom";
import { ButtonInput } from "../../inputs/Button";
import { useTranslation } from "react-i18next";
interface HeaderCreateProps {
label?: string;
path: string;
isLoading?: boolean;
isAdmin?: boolean;
}
export const HeaderCreate = ({
label,
path,
isLoading = false,
isAdmin = false,
}: HeaderCreateProps) => {
const navigate = useNavigate();
const { t } = useTranslation();
if (!isAdmin) {
return null;
}
const handleClick = () => {
navigate(path);
};
return (
<Stack
direction="row"
justifyContent="flex-end"
alignItems="center"
width="100%"
>
<ButtonInput
variant="contained"
color="primary"
onClick={handleClick}
loading={isLoading}
>
{label || t("common.buttons.create")}
</ButtonInput>
</Stack>
);
};
@@ -0,0 +1,102 @@
import Stack from "@mui/material/Stack";
import { Icon, MonitorStatus } from "@/Components/v2/design-elements";
import { Button } from "@/Components/v2/inputs";
import { Settings, Pause, Play, Mail, Bug } from "lucide-react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { useTheme } from "@mui/material/styles";
import { usePost } from "@/Hooks/UseApi";
import type { Monitor } from "@/Types/Monitor.js";
interface HeaderMonitorControlsProps {
path: string;
monitor?: Monitor;
isAdmin: boolean;
refetch: Function;
}
export const HeaderMonitorControls = ({
path,
monitor,
isAdmin,
refetch,
}: HeaderMonitorControlsProps) => {
const { t } = useTranslation();
const navigate = useNavigate();
const theme = useTheme();
const {
post,
loading: isPosting,
// error: postError,
} = usePost<any, Monitor>();
if (!monitor) {
return null;
}
return (
<Stack
spacing={{ xs: theme.spacing(8), md: 0 }}
direction={{ xs: "column", md: "row" }}
alignItems={"center"}
justifyContent={"space-between"}
>
<MonitorStatus monitor={monitor} />
<Stack
width={{ xs: "100%", md: "auto" }}
direction={{ xs: "column", md: "row" }}
gap={theme.spacing(2)}
>
<Button
variant="contained"
color="secondary"
loading={isPosting}
startIcon={<Icon icon={Mail} />}
onClick={async () => {
await post(`/notifications/test/all`, { monitorId: monitor.id });
}}
sx={{
whiteSpace: "nowrap",
}}
>
{t("common.buttons.testNotifications")}
</Button>
<Button
variant="contained"
color="secondary"
startIcon={<Icon icon={Bug} />}
onClick={() => {
navigate(`/incidents/${monitor?.id}`);
}}
>
{t("common.buttons.incidents")}
</Button>
{isAdmin && (
<Button
variant="contained"
color="secondary"
loading={isPosting}
startIcon={monitor?.isActive ? <Icon icon={Pause} /> : <Icon icon={Play} />}
onClick={async () => {
await post(`/monitors/pause/${monitor.id}`, {});
await refetch();
}}
>
{monitor?.isActive ? t("pause") : t("resume")}
</Button>
)}
{isAdmin && (
<Button
variant="contained"
color="secondary"
startIcon={<Icon icon={Settings} />}
onClick={() => navigate(`/${path}/configure/${monitor.id}`)}
>
Configure
</Button>
)}
</Stack>
</Stack>
);
};
Loaded 100 of 738 files, more files were not shown because too many files have changed in this diff. Show more