Compare commits

..
687 Commits
Author SHA1 Message Date
Alex Holliday c5ba1b1dbe Merge remote-tracking branch 'upstream/master' 2025-01-23 07:12:35 -08:00
Alex Holliday 6f53c31e99 Merge branch 'master' of github.com:popnfresh234/bluewave-uptime 2025-01-23 07:06:14 -08:00
Alexander Holliday 986166e7a5 Merge pull request #1613 from renovate-bot/renovate/nodemailer-6.x-lockfile
fix(deps): update dependency nodemailer to v6.10.0
2025-01-23 07:02:55 -08:00
Alexander Holliday 0e14d8a52e Merge pull request #1614 from renovate-bot/renovate/bullmq-5.x
fix(deps): update dependency bullmq to v5.35.1
2025-01-23 07:02:32 -08:00
Mend Renovate 01592cf0f0 fix(deps): update dependency bullmq to v5.35.1 2025-01-23 13:37:56 +00:00
Mend Renovate ffffda7282 fix(deps): update dependency nodemailer to v6.10.0 2025-01-23 09:14:59 +00:00
Alexander Holliday d3ad898f73 Merge pull request #1611 from bluewave-labs/fix/maintenance-colors
Fix/maintenance colors
2025-01-22 12:45:10 -08:00
Alexander Holliday 027b331220 Merge pull request #1607 from bluewave-labs/fix/be/incidents
fix: be/incidents, resolves #1567
2025-01-22 12:44:14 -08:00
Alex Holliday 7b5d49c0e2 Fix maintenace table and colors 2025-01-22 12:40:29 -08:00
Alex Holliday bc8d43e222 Merge branch 'fix/be/incidents' into fix/maintenance-colors 2025-01-22 12:18:39 -08:00
Alex Holliday 1b31f9ac4c Remove console logs, update UI 2025-01-22 11:36:27 -08:00
Alex Holliday 15cd3b23c3 Add daterange prop 2025-01-22 11:13:44 -08:00
Alex Holliday 4268966b44 Add hour filter to incidents page 2025-01-22 11:10:09 -08:00
Alex Holliday f46f8cb12b Add hour filter to incidents page 2025-01-22 11:07:43 -08:00
Alex Holliday fc36da4f7b Add hour filters to check queries 2025-01-22 11:07:32 -08:00
Alex Holliday 2a74f2e8da remove seed from index 2025-01-22 11:06:39 -08:00
Alex Holliday 9ffe17e9eb add db utils 2025-01-22 08:29:38 -08:00
Alexander Holliday e4f2cfb978 Merge pull request #1609 from renovate-bot/renovate/mocha-11.x
chore(deps): update dependency mocha to v11.1.0
2025-01-22 07:20:53 -08:00
Alex Holliday 9a8ce41c95 add db utils for seeding db 2025-01-22 07:06:53 -08:00
Mend Renovate 90e697a1bd chore(deps): update dependency mocha to v11.1.0 2025-01-22 14:57:22 +00:00
Alexander Holliday 488e50ace5 Merge pull request #1608 from renovate-bot/renovate/bullmq-5.x
fix(deps): update dependency bullmq to v5.35.0
2025-01-22 06:56:36 -08:00
Mend Renovate 00e1286f24 fix(deps): update dependency bullmq to v5.35.0 2025-01-22 13:04:14 +00:00
Gorkem Cetin (BWL) fcd28afc97 Add github trending #1 badge 2025-01-22 01:38:07 -05:00
Alex Holliday 0d53a1e5fb Merge branch 'develop' into fix/be/incidents 2025-01-21 22:22:10 -08:00
Gorkem Cetin (BWL) c7b65e537a Update roadmap 2025-01-22 00:02:17 -05:00
Gorkem Cetin (BWL) 2c8e1c8444 Merge pull request #1606 from bluewave-labs/gorkem-bwl-patch-1
Update index.jsx
2025-01-21 20:21:09 -05:00
Gorkem Cetin (BWL) 9e9f5d435d Update index.jsx 2025-01-21 20:20:34 -05:00
Alex Holliday f5480cbfba Add default key to table rows 2025-01-21 15:39:49 -08:00
Alex Holliday 9535ffa192 Refactor Pagination component to accept a label to improve reusability. Use Pagination component with Incident table 2025-01-21 15:31:40 -08:00
Alex Holliday 4ed995692d Refactor incident endpoints to use aggregation 2025-01-21 15:30:51 -08:00
Alexander Holliday c4d223aee5 Merge pull request #1556 from bluewave-labs/fix/colors-general
Fix/colors general
2025-01-21 13:26:13 -08:00
Alex Holliday f8b3847577 Remove console log 2025-01-21 13:22:29 -08:00
Alex Holliday d92a477892 Add darker color to accent 2025-01-21 11:38:32 -08:00
Alex Holliday 6eedaa5ca0 various infra details page color fixes 2025-01-21 10:58:08 -08:00
Alex Holliday 239164e779 Fixed invisible text on login page 2025-01-21 10:57:53 -08:00
Alex Holliday 2070e24829 Fix gauge colors 2025-01-21 10:57:34 -08:00
Alex Holliday 8021786d0c fix gauge colors 2025-01-21 10:24:09 -08:00
Alex Holliday f68e885c88 status box fix 2025-01-21 10:20:43 -08:00
Alexander Holliday 0cfc041088 Merge pull request #1596 from renovate-bot/renovate/vite-5.x-lockfile
chore(deps): update dependency vite to v5.4.14
2025-01-21 07:04:22 -08:00
Alexander Holliday a53e0f7f20 Merge pull request #1601 from renovate-bot/renovate/material-ui-monorepo
fix(deps): update material-ui monorepo
2025-01-21 07:04:04 -08:00
Gorkem Cetin (BWL) 5fb748156f Delete guides directory as all documentation is kept in a separate repo now 2025-01-21 09:38:19 -05:00
Mend Renovate 9ebe9fe613 fix(deps): update material-ui monorepo 2025-01-21 13:40:22 +00:00
Mend Renovate 2b957e5cac chore(deps): update dependency vite to v5.4.14 2025-01-21 13:40:15 +00:00
Alex Holliday 8048dae81c color fixes 2025-01-20 15:26:15 -08:00
Alexander Holliday d91fe03acc Merge pull request #1591 from bluewave-labs/feat/be/consistent-response-middleare
feat: be/consistent response middleare
2025-01-20 11:48:40 -08:00
Alexander Holliday 8d148cd4bf Merge pull request #1587 from DMJain/develop
#1584 Removed Checkboxes for Notification : SMS & Multi Email
2025-01-20 11:03:57 -08:00
Alex Holliday 792b51b488 fix typos 2025-01-20 11:02:55 -08:00
Alexander Holliday 261d32e51e Merge pull request #1592 from renovate-bot/renovate/vite-5.x-lockfile
chore(deps): update dependency vite to v5.4.12
2025-01-20 06:56:13 -08:00
Alexander Holliday abb4a53816 Merge pull request #1594 from mertssmnoglu/fix/client-old-name
Implement new Checkmate logo to frontend
2025-01-20 06:55:03 -08:00
Mert Şişmanoğlu f73be34877 chore: Replace BlueWave Uptime logo with Checkmate logo across the pages
Signed-off-by: Mert Şişmanoğlu <mert190737fb@gmail.com>
2025-01-20 15:06:17 +03:00
Mert Şişmanoğlu 2cee0b4690 chore(client): Update checkmate favicon
Signed-off-by: Mert Şişmanoğlu <mert190737fb@gmail.com>
2025-01-20 15:05:21 +03:00
Darshan Jain 4ed6942d8b Merge branch 'issue#1584' into develop 2025-01-20 16:37:16 +05:30
Darshan Jain ed9a89b526 #1584 comments 2025-01-20 16:36:50 +05:30
Darshan Jain e99010e81d Merge branch 'develop' of https://github.com/DMJain/Checkmate into develop 2025-01-20 16:33:56 +05:30
Mend Renovate 36d20f90f6 Update dependency vite to v5.4.12 2025-01-20 09:45:16 +00:00
Darshan Jain d8b1d63faa commenting out checkboxs and input box instead of deleting for future implementations 2025-01-20 13:26:29 +05:30
Alex Holliday 9f43336a03 Use response handler in handleErrors middleare 2025-01-19 20:14:03 -08:00
Mert Şişmanoğlu 84dd4a49c9 fix: Update BlueWave Uptime references to Checkmate on the Client side and /index.html
Signed-off-by: Mert Şişmanoğlu <mert190737fb@gmail.com>
2025-01-20 02:48:26 +03:00
Alex Holliday e9eadfe838 use response middleware in StatusPageController 2025-01-19 12:05:45 -08:00
Alex Holliday 627c80581d use response middleware in SettingsController 2025-01-19 12:05:15 -08:00
Alex Holliday 3608eadb8c use response middleware in QueueController 2025-01-19 12:04:47 -08:00
Alex Holliday 378a190cd3 use response middleware in MonitorController 2025-01-19 12:01:27 -08:00
Darshan Jain f04cc6caf5 Merge branch 'bluewave-labs:develop' into develop 2025-01-19 23:40:59 +05:30
Darshan Jain 6ed8f93688 #1584 removed conditional input box for multi email 2025-01-19 23:35:01 +05:30
Alex Holliday f0bf680f19 use response middleware in MaintenanceWindowController 2025-01-19 10:01:48 -08:00
Darshan Jain 4d2c59b30c #1584 removed checkbox for incident notification: SMS, Multi Email 2025-01-19 23:30:08 +05:30
Alex Holliday e5f8d88c5a use response middleware in InviteController 2025-01-19 09:59:13 -08:00
Alex Holliday 0aaeeef113 remove unnecessary try/catch 2025-01-19 09:56:28 -08:00
Alex Holliday da89789a97 use response middleware in CheckController 2025-01-19 09:55:48 -08:00
Alex Holliday 252c16ac1b add a response middleware to enforce consistent API response 2025-01-19 09:53:03 -08:00
Alexander Holliday 5f9404178c Merge pull request #1574 from renovate-bot/renovate/dockerode-4.x
Update dependency dockerode to v4.0.4
2025-01-19 09:19:48 -08:00
Alexander Holliday 9b6e40fa34 Merge pull request #1575 from renovate-bot/renovate/material-ui-monorepo
Update material-ui monorepo to v7.24.0
2025-01-19 09:19:30 -08:00
Gorkem Cetin (BWL) 7abc2280af Update README.md 2025-01-19 01:29:03 -05:00
Gorkem Cetin (BWL) 8a356cf38d Map roadmap items to issues 2025-01-18 21:53:53 -05:00
Gorkem Cetin (BWL) cb1751f362 Update README.md 2025-01-18 14:07:35 -05:00
Gorkem Cetin (BWL) 0c7891f136 Update README.md 2025-01-18 13:21:19 -05:00
Mend Renovate 0bf1cbdfa9 Update material-ui monorepo to v7.24.0 2025-01-17 19:35:15 +00:00
Mend Renovate 866f89ef09 Update dependency dockerode to v4.0.4 2025-01-17 19:35:05 +00:00
Alexander Holliday ead3704173 Merge pull request #1563 from renovate-bot/renovate/bullmq-5.x
Update dependency bullmq to v5.34.10
2025-01-16 10:02:46 -08:00
Alexander Holliday b67f0bbfcd Merge pull request #1565 from renovate-bot/renovate/material-ui-monorepo
Update material-ui monorepo
2025-01-16 10:02:31 -08:00
Alexander Holliday 8d49ed50d8 Merge pull request #1572 from renovate-bot/renovate/npm-mongoose-vulnerability
Update dependency mongoose to v8.9.5 [SECURITY]
2025-01-16 10:02:16 -08:00
Mend Renovate 389ed2768a Update dependency mongoose to v8.9.5 [SECURITY] 2025-01-16 18:01:57 +00:00
Alexander Holliday d67acfe0ba Merge pull request #1571 from renovate-bot/renovate/react-router-monorepo
Update react-router monorepo to v6.28.2
2025-01-16 10:00:59 -08:00
Alexander Holliday d9a1f41b9d Merge pull request #1569 from not-Ryan/patch-1
Fix server is down mjml to show monitor name
2025-01-16 10:00:39 -08:00
Mend Renovate c073e086cb Update react-router monorepo to v6.28.2 2025-01-16 17:43:52 +00:00
Caio Cabral bc9d8f0b3c feat: fix INfrastructure counter 2025-01-15 19:34:49 -05:00
Caio Cabral ec94458de4 fix: Name on team table 2025-01-15 19:19:18 -05:00
Caio Cabral 8404cee349 refactor: change all selects instead of relying on sx 2025-01-15 19:16:33 -05:00
Caio Cabral 6864756552 feat: fix select 2025-01-15 19:13:34 -05:00
Caio Cabral 7d76aee647 feat: tabs 2025-01-15 18:58:44 -05:00
Caio Cabral 8661e8305d removing fixed height from css 2025-01-15 18:35:00 -05:00
Caio Cabral 851ad48b3e feat: Profile Panel buttons 2025-01-15 17:51:13 -05:00
Caio Cabral 53fcd5acc7 fix: pagespeed details 2025-01-15 08:07:48 -05:00
Caio Cabral 59f60d2184 fix inputs 2025-01-15 07:55:22 -05:00
notRyan 0345f06614 Fix server is down mjml to show monitor name
Was static google.com is now the monitor name in the header
2025-01-15 09:33:43 +01:00
Alexander Holliday aa852bd162 Merge pull request #1568 from bluewave-labs/hotfix/fe/maintenance-window-montior-list
hotfix: replace limit = -1 with limit = null
2025-01-14 22:24:31 -08:00
Alex Holliday 7e1b8df8fc replace limit = -1 with limit = null 2025-01-14 22:23:40 -08:00
Gorkem Cetin (BWL) f0aea3a534 Better screenshots 2025-01-14 23:11:44 -05:00
Caio Cabral b4ba9efd45 fix: table header color 2025-01-14 22:58:46 -05:00
Caio Cabral d09382bc71 fix: toast 2025-01-14 20:25:20 -05:00
Caio Cabral 8fe200fb8b feat: finished 2025-01-14 19:19:36 -05:00
Gorkem Cetin (BWL) 9538b5670f Update README.md 2025-01-14 18:57:15 -05:00
Caio Cabral 6cdac32bce feat: some colors fixed, table alignment 2025-01-14 18:54:00 -05:00
Mend Renovate bf74e8b64e Update material-ui monorepo 2025-01-14 21:11:41 +00:00
Mend Renovate c665a3777c Update dependency bullmq to v5.34.10 2025-01-14 17:55:00 +00:00
Alexander Holliday b599dcd68f Merge pull request #1560 from renovate-bot/renovate/eslint-plugin-react-7.x-lockfile
Update dependency eslint-plugin-react to v7.37.4
2025-01-14 09:54:36 -08:00
Alexander Holliday cfcaa0cbc5 Merge pull request #1555 from bluewave-labs/fix/fe/network-alerts
fix: fe axios error handler
2025-01-14 09:54:17 -08:00
Alexander Holliday 80ab5f0fd6 Merge pull request #1559 from bluewave-labs/fix/fe/has-monitors
fix: hasMonitors logic
2025-01-14 09:53:59 -08:00
Caio Cabral d54272ba30 cleaning unused code 2025-01-14 08:36:48 -05:00
Caio Cabral 5e9629ee29 fix: primary buttons 2025-01-14 08:09:03 -05:00
Caio Cabral 921fb6e458 spotted errors nd marked to be solved after merge 2025-01-14 07:59:08 -05:00
Gorkem Cetin (BWL) c46ef40cb7 Update README.md 2025-01-13 16:22:47 -05:00
Mend Renovate 701bcf3b68 Update dependency eslint-plugin-react to v7.37.4 2025-01-13 08:21:44 +00:00
Alex Holliday a7c99fcd58 fix hasMonitors logic 2025-01-12 17:18:50 -08:00
Caio Cabral 2ab6a578c3 merging 2025-01-12 13:33:11 -05:00
Caio Cabral a6eb97050b feat: finishing adjusting colors 2025-01-12 13:03:09 -05:00
Alex Holliday ef4b11c72b fix error handler 2025-01-12 09:51:25 -08:00
Caio Cabral e235279af0 feat: fix incident table 2025-01-12 12:35:14 -05:00
Caio Cabral 541a5b3f12 feat: fixing lots of colors 2025-01-12 12:28:13 -05:00
Alexander Holliday 0e443e4088 Merge pull request #1551 from renovate-bot/renovate/eslint-monorepo
Update eslint monorepo to v9.18.0
2025-01-12 09:17:42 -08:00
Alexander Holliday 2b61ff4fca Merge pull request #1553 from renovate-bot/renovate/eslint-plugin-react-refresh-0.x-lockfile
Update dependency eslint-plugin-react-refresh to v0.4.18
2025-01-12 09:17:27 -08:00
Alexander Holliday c171a718f4 Merge pull request #1554 from renovate-bot/renovate/bullmq-5.x
Update dependency bullmq to v5.34.9
2025-01-12 09:17:15 -08:00
Mend Renovate 2f2977cfac Update dependency bullmq to v5.34.9 2025-01-12 16:54:16 +00:00
Caio Cabral 61aaa8489a feat: simplify and adjust status label 2025-01-12 09:39:31 -05:00
Caio Cabral 5eca39cd04 feat: set accent color buttons hover colors 2025-01-12 08:33:33 -05:00
Caio Cabral 9fb6db6921 feat: excluding selected path from hover changes 2025-01-12 08:24:09 -05:00
Caio Cabral 46af783ce6 feat: colors for sidebar 2025-01-12 08:19:29 -05:00
Caio Cabral 5fcc87dab4 feat: all colors inside of sideBar 2025-01-11 18:06:47 -05:00
Caio Cabral 74c598e191 feat: changed a whole bunch of colors 2025-01-11 17:21:10 -05:00
Mend Renovate 3b9c747b76 Update dependency eslint-plugin-react-refresh to v0.4.18 2025-01-11 22:16:36 +00:00
Caio Cabral 3b1657c415 feat: changing background main for primary main 2025-01-11 16:53:00 -05:00
Caio Cabral c0df164e46 feat: changing background accent for tertiary main 2025-01-11 16:50:48 -05:00
Caio Cabral 6bb38c9339 feat: extract function 2025-01-11 16:40:07 -05:00
Caio Cabral b0aa5e64f4 feat: changing green for dark mode 2025-01-11 11:53:35 -05:00
Caio Cabral 059927cc20 feat: changing green for light mode 2025-01-11 11:49:02 -05:00
Caio Cabral 78340ce63b fix: logo 2025-01-11 10:06:31 -05:00
Caio Cabral 8cf28fe947 fix: label background color 2025-01-11 10:03:11 -05:00
Caio Cabral d027407562 fix percentage uptimePoor 2025-01-11 09:54:11 -05:00
Caio Cabral dbd351b2f1 fix many 2025-01-11 09:53:27 -05:00
Gorkem Cetin (BWL) 6542852634 Update README.md 2025-01-11 03:34:25 -05:00
Gorkem Cetin (BWL) 4fab3f8713 Delete grant_funding.md as we are referencing to https://checkmate.so 2025-01-11 02:32:49 -05:00
Gorkem Cetin (BWL) 4a655b0328 Newreleases.io + fix star numbers 2025-01-10 23:48:24 -05:00
Alexander Holliday e4f7d9eb7a Merge pull request #1552 from bluewave-labs/hotfix/be/default-sort-order
hotfix: add a default sort order if a field to sort by is not specified
2025-01-10 20:07:37 -08:00
Alex Holliday c61f08b31c Add a default sort order if a field to sort by is not specified 2025-01-10 20:06:01 -08:00
Mend Renovate 64deae1257 Update eslint monorepo to v9.18.0 2025-01-11 01:09:50 +00:00
Alexander Holliday 9c484a59d6 Merge pull request #1550 from bluewave-labs/fix/be/fe/search
fix: be/fe/search, resolves #1549
2025-01-10 13:45:55 -08:00
Alex Holliday cd14659a59 Use all monitors for search component instead of filtered monitors 2025-01-10 13:23:32 -08:00
Alex Holliday 367d3602c8 add array of all monitors to response for search 2025-01-10 13:23:19 -08:00
Alexander Holliday af8f3a0351 Merge pull request #1548 from bluewave-labs/fix/be/uptime-duration
fix: be/uptime duration
2025-01-10 12:14:33 -08:00
Alexander Holliday b8a6258402 Merge pull request #1542 from bluewave-labs/fix/be/jobqueue-scheduler
fix: use jobScheduler instead of deprecatred repeatable
2025-01-10 12:14:12 -08:00
Alex Holliday 12cc20a852 refactor uptime details query 2025-01-10 12:12:33 -08:00
Alex Holliday d495ca67e1 Merge branch 'fix/be/jobqueue-scheduler' into fix/be/uptime-duration 2025-01-10 09:48:53 -08:00
Alex Holliday 70898da69d Merge branch 'develop' into fix/be/uptime-duration 2025-01-10 09:48:10 -08:00
Alex Holliday 59c6f39ea9 Remove incomplete line 2025-01-10 09:46:22 -08:00
Alexander Holliday 50a7741dcc Merge pull request #1544 from renovate-bot/renovate/mongoose-8.x-lockfile
Update dependency mongoose to v8.9.4
2025-01-10 09:44:03 -08:00
Alexander Holliday a2102ca77a Merge pull request #1524 from jasneetsingh6114/feat/issue-1482
Inform Admin incase of server issue.
2025-01-10 09:43:46 -08:00
Alex Holliday cd3767a040 new duration algorithm 2025-01-09 20:50:08 -08:00
Mend Renovate 5bb1e64f25 Update dependency mongoose to v8.9.4 2025-01-09 23:07:07 +00:00
Jasneet Singh 74f23c1792 Address pr comments 2025-01-09 17:38:08 -05:00
Alex Holliday c1d6433e6c use jobScheduler instead of deprecatred repeatable 2025-01-09 12:01:51 -08:00
Alexander Holliday 2dfe5dec7c Merge pull request #1540 from renovate-bot/renovate/material-ui-monorepo
Update material-ui monorepo to v7.23.6
2025-01-09 10:53:30 -08:00
Alexander Holliday 5db231351e Merge pull request #1541 from bluewave-labs/hotfix/fe/be/incidents-page
hotfix: fe/be/incidents page
2025-01-09 10:53:14 -08:00
Caio Cabral 3787f9c288 feat: fix success grdient 2025-01-09 13:50:50 -05:00
Alex Holliday ef23648941 ommit checks from query if limit is null 2025-01-09 10:42:25 -08:00
Alex Holliday e8aecb4041 fix method name 2025-01-09 10:42:01 -08:00
Alex Holliday d76f02ba51 repalce limit param of -1 with null 2025-01-09 10:41:33 -08:00
Caio Cabral be144c5ed2 feat: search and actions 2025-01-09 12:32:54 -05:00
Caio Cabral 07641a2bd8 feat: fixing table 2025-01-09 12:19:28 -05:00
Mend Renovate 83b43d060a Update material-ui monorepo to v7.23.6 2025-01-09 15:21:09 +00:00
Caio Cabral 7686b1ecf5 fix: breadcrumbs 2025-01-09 08:40:25 -05:00
Caio Cabral 3ffcadc06e feat: setting new colors 2025-01-08 21:41:59 -05:00
Alexander Holliday 975e6d61b5 Merge pull request #1537 from bluewave-labs/hotfix/remove-missing-import
hotfix: remove missing import
2025-01-08 15:22:46 -08:00
Alex Holliday 6d82cb7eb2 remove missing import 2025-01-08 15:22:06 -08:00
Alexander Holliday 7a140e75f1 Merge pull request #1529 from renovate-bot/renovate/bullmq-5.x
Update dependency bullmq to v5.34.8
2025-01-08 15:15:09 -08:00
Alexander Holliday 51ab3487ee Merge pull request #1536 from bluewave-labs/fix/fe/debounce-search
fix: fix unnecessary rerenders
2025-01-08 15:14:48 -08:00
Alex Holliday de01a430a3 fix unnecessary rerenders 2025-01-08 15:13:35 -08:00
Alexander Holliday 5b1fbf8817 Merge pull request #1535 from bluewave-labs/hotfix/fe/be/filtering
hotfix: move filtering to the monitors facet
2025-01-08 14:18:45 -08:00
Alex Holliday 40fbe4a822 move filtering to the monitors facet 2025-01-08 14:17:29 -08:00
Alexander Holliday 75c6a6e024 Merge pull request #1534 from bluewave-labs/hotfix/fe/uptime-skeleton
hotfix: readd skeleton
2025-01-08 14:04:07 -08:00
Alex Holliday 62541f3fdd readd skeleton 2025-01-08 14:03:23 -08:00
Alexander Holliday b909210d21 Merge pull request #1533 from bluewave-labs/feat/be/unified-uptime-query
feat: be/unified uptime query
2025-01-08 13:53:29 -08:00
Alex Holliday de3a8be76c Use new query in Uptime page 2025-01-08 13:47:42 -08:00
Alex Holliday 7862f8d967 use new query in PageSpeed page 2025-01-08 13:47:20 -08:00
Alex Holliday bde4269fd1 use new query in infra page 2025-01-08 13:46:49 -08:00
Alex Holliday e616df2766 Remove unused thunks from uptime slice 2025-01-08 13:46:19 -08:00
Alex Holliday 1805ca8d17 Remove unnecessary params from getMonitorsByTeamId 2025-01-08 13:45:34 -08:00
Alex Holliday 979110dd77 Rewrite team monitors query 2025-01-08 13:44:24 -08:00
Alex Holliday 87f9c35c74 remove unused validation, update monitors by team ID validation 2025-01-08 13:42:43 -08:00
Alex Holliday 8f572ba01d Remove summary controller method, rewrite getMonitosByTeamId 2025-01-08 13:42:08 -08:00
Alex Holliday 7cfc06b2da Remove summary route 2025-01-08 13:40:57 -08:00
Alex Holliday 5da542b99e formatting 2025-01-08 12:01:43 -08:00
Alex Holliday cf5152d0ec remove unused dispatch from pagespeed 2025-01-08 12:01:28 -08:00
Alexander Holliday 61ee7a1059 Merge pull request #1532 from bluewave-labs/hotfix/fe/pagination
hotfix: use redux state for pagination
2025-01-08 11:55:14 -08:00
Alex Holliday 8d8082bb83 use redux state for pagination 2025-01-08 11:53:13 -08:00
Alexander Holliday ac839cae6c Merge pull request #1531 from bluewave-labs/fix/be/get-monitors-by-team-id-query
Fix/be/get monitors by team id query
2025-01-08 10:19:10 -08:00
Alex Holliday 2694f836c7 fix destructuring 2025-01-08 10:17:51 -08:00
Alex Holliday 35796dccd4 add normalize check 2025-01-08 10:06:50 -08:00
Alex Holliday ef56283fa9 Fix merge conflict, remove unused dispatch 2025-01-08 10:05:34 -08:00
Gorkem Cetin (BWL) dc43c0486c Add OpenSSF best practices badge 2025-01-08 01:22:33 -05:00
Mend Renovate dffbce1de1 Update dependency bullmq to v5.34.8 2025-01-08 05:48:23 +00:00
Alexander Holliday cac5dff694 Merge pull request #1526 from KalyanBhowmick/fix/be/incident-filter
Query updated for Incidents page filter
2025-01-07 21:47:50 -08:00
Kalyan Bhowmick 0e4cf8cded resolve query reverted 2025-01-08 10:30:22 +05:30
Caio Cabral db86753701 feat: updating texts 2025-01-07 21:27:52 -05:00
Alex Holliday a4989d3e95 Merge branch 'develop' into fix/be/get-monitors-by-team-id-query 2025-01-07 16:27:58 -08:00
Alex Holliday 0b803da5fb rewrite query to use aggregation pipeline 2025-01-07 16:27:35 -08:00
Alexander Holliday da9345abf5 Merge pull request #1528 from bluewave-labs/fix/fe/uptime-home
fix: fe/uptime home
2025-01-07 14:57:13 -08:00
Alexander Holliday 803a110345 Merge branch 'develop' into fix/fe/uptime-home 2025-01-07 14:57:03 -08:00
Alexander Holliday cba6734623 Merge pull request #1527 from bluewave-labs/fix/fe/simplify-monitor-summary
remove monitors from summary, unused
2025-01-07 14:54:52 -08:00
Alexander Holliday 4409afc0d0 Merge pull request #1523 from renovate-bot/renovate/bullmq-5.x
Update dependency bullmq to v5.34.7
2025-01-07 14:54:32 -08:00
Alex Holliday 787bb09235 fix pagination icon positioning 2025-01-07 14:52:10 -08:00
Alex Holliday 0cc5c732c7 Fix sort arrow positioning 2025-01-07 14:45:18 -08:00
Alex Holliday 1898880774 remove utility hook 2025-01-07 14:34:18 -08:00
Alex Holliday 89d0a74ade Use new table component, remove old table 2025-01-07 14:31:25 -08:00
Alex Holliday e070ef472e Create new UptimeDataTable component to hold search, table, and pagination 2025-01-07 14:30:27 -08:00
Alex Holliday 8bc05e4424 Rewrite uptime home page for simplicity, move queries to parent 2025-01-07 14:29:47 -08:00
Alex Holliday 37207da9fa Rename get method in network service 2025-01-07 14:28:46 -08:00
Alex Holliday 33b3143298 use correct name for redux action 2025-01-07 14:27:55 -08:00
Alex Holliday 290740d58e Add getting monitors by team id to redux slice to make use of loading state 2025-01-07 14:24:47 -08:00
Alex Holliday 741f0d1884 Merge branch 'develop' into fix/fe/uptime-home 2025-01-07 12:05:19 -08:00
Kalyan Bhowmick de513c226c Query updated for Incidents page filter 2025-01-07 23:48:12 +05:30
Alex Holliday e2d5388a30 remove monitors from summary, unused 2025-01-07 10:12:41 -08:00
Alexander Holliday 624ea50684 Merge pull request #1522 from bluewave-labs/fix/fe/uptime-details
Fix incorrect uptime check count, refactor out query pipelines
2025-01-06 16:35:23 -08:00
Mend Renovate 3ff9b19abf Update dependency bullmq to v5.34.7 2025-01-07 00:35:23 +00:00
Alexander Holliday 05bfbe2154 Merge pull request #1515 from bluewave-labs/feat/fe/reusable-data-table
feat: fe/reusable data table
2025-01-06 16:35:05 -08:00
Alexander Holliday a5aa00337a Merge pull request #1512 from bluewave-labs/fix/fe/charts-date-display
fix: fe/charts date display
2025-01-06 16:34:47 -08:00
Alex Holliday 9e1f50bc8e remove unused var 2025-01-06 15:24:16 -08:00
Alexander Holliday 36bbf5aba0 Merge pull request #1521 from bluewave-labs/hotfix/be/jobqueue-logging
hotfic: add more logging to jobqueue
2025-01-06 14:31:40 -08:00
Alex Holliday 299ae95a0d Add more logging to jobqueue 2025-01-06 14:30:48 -08:00
Alex Holliday ca42e2608f Fix incorrect uptime check count, refactor out query pipelines 2025-01-06 14:24:08 -08:00
Alexander Holliday dd5e14b6a5 Merge pull request #1505 from bluewave-labs/feat/devops/redis-health-check
feat: devops/add healthcheck to redis image
2025-01-06 14:08:28 -08:00
Jasneet Singh 4398010a9f Inform Admin incase of server issue. 2025-01-06 16:38:58 -05:00
Gorkem Cetin (BWL) 208677f9bc fix anyways 2025-01-06 13:57:24 -05:00
Gorkem Cetin (BWL) d600779432 Suggest watching 2025-01-06 13:57:06 -05:00
Alexander Holliday 303d3c5ba8 Merge pull request #1520 from bluewave-labs/hotfix/fe/validation
hotfix: make protocol optional for validation
2025-01-06 10:14:16 -08:00
Alex Holliday abe2544a2a make protocol optional for validation 2025-01-06 10:13:24 -08:00
Alexander Holliday 8045d2a17c Merge pull request #1516 from renovate-bot/renovate/dockerode-4.x
Update dependency dockerode to v4.0.3
2025-01-06 10:09:23 -08:00
Alexander Holliday 40b302fcf5 Merge pull request #1519 from bluewave-labs/hotfix/fe/monitor-validation
hotfix: fe/monitor validation, resolves #1518
2025-01-06 10:09:07 -08:00
Alex Holliday 21a33f8f37 Added source 2025-01-06 10:06:26 -08:00
Alex Holliday e2ce886a16 Update regex to avoid catastrophic backtracking 2025-01-06 10:05:53 -08:00
Gorkem Cetin (BWL) a9ceecbdb2 Update README.md 2025-01-06 04:46:28 -05:00
Gorkem Cetin (BWL) a74085abbe Fix screenshot 2025-01-06 04:42:56 -05:00
Gorkem Cetin (BWL) 99089ab103 Update README.md 2025-01-05 20:49:23 -05:00
Gorkem Cetin (BWL) 2a99c87ff2 Update README.md 2025-01-05 11:30:39 -05:00
Mend Renovate 09aa62d42e Update dependency dockerode to v4.0.3 2025-01-04 14:03:58 +00:00
Alexander Holliday 91a93bd8fb Merge pull request #1513 from renovate-bot/renovate/material-ui-monorepo
Update material-ui monorepo
2025-01-03 12:44:29 -08:00
Alexander Holliday 118796c4ef Merge pull request #1514 from rafeyrana/issue#1508
Left align Profile > Password components
2025-01-03 12:44:12 -08:00
Alex Holliday 8dd1dc8cca Removed unused BasicTable 2025-01-03 12:41:14 -08:00
Alex Holliday bd502a610a Move pagination components from Infrastrucutre directory to reusable table directory 2025-01-03 12:24:06 -08:00
Alex Holliday 447c9ce70b Use reuseable table in Uptime home page 2025-01-03 12:23:08 -08:00
Alex Holliday c504fbbad6 use reusable table in infrastructure page 2025-01-03 12:22:24 -08:00
Alex Holliday c69389db2c use reusable table in incidents page 2025-01-03 12:21:59 -08:00
Alex Holliday a053eebdb0 Use reusable table in team page 2025-01-03 12:21:33 -08:00
Alex Holliday 0c608fdc8e Fix conditional rendering of empty view 2025-01-03 12:09:05 -08:00
Rafey Rana 2c21a44e76 Left align Profile > Password components 2025-01-03 14:30:12 -05:00
Alex Holliday c6ef46133c safe data access, add default empty view 2025-01-03 11:17:55 -08:00
Alex Holliday c192d7024e Add empty view to table 2025-01-03 11:14:23 -08:00
Alex Holliday 3d067e8030 make config optional, safe SX access 2025-01-03 10:36:53 -08:00
Alex Holliday 83dc96887d Create a reusable table component 2025-01-03 10:31:38 -08:00
Mend Renovate 6b1d6b64d6 Update material-ui monorepo 2025-01-03 14:04:46 +00:00
Alex Holliday 032adb4a77 Refactor date format lookup to util functions, refactor x-axis for infra details charts 2025-01-02 14:22:31 -08:00
Alex Holliday ef13af3884 Update x-axis labels for uptime monitor details chart 2025-01-02 14:07:38 -08:00
Alexander Holliday 67749585e0 Merge pull request #1511 from bluewave-labs/hotfix/be/env-config
hotfix: fix env vars for settings service
2025-01-02 12:30:15 -08:00
Alex Holliday eee33e85c4 Remove superfluous dotenv from authController, add missing dotenv to settingsService 2025-01-02 12:27:19 -08:00
Alexander Holliday d46cc3d037 Merge pull request #1510 from bluewave-labs/feat/be/distributed-uptime
feat: be/distributed uptime
2025-01-02 12:08:27 -08:00
Alex Holliday 648d40648c move ngrok to dev dependencies 2025-01-02 12:06:01 -08:00
Alex Holliday 56cd3f5d9f update docs 2025-01-02 12:02:44 -08:00
Alex Holliday 74ec29fbc1 add routes to app 2025-01-02 12:02:28 -08:00
Alex Holliday ba7cb151a2 Add distributed uptime router 2025-01-02 11:47:09 -08:00
Alex Holliday 3b5a1827ad Add distributedUptimeController 2025-01-02 11:45:29 -08:00
Alex Holliday 71fd2fb5ef use ngrok 2025-01-02 11:40:26 -08:00
Alex Holliday b91cf296f1 add ngrok 2025-01-02 11:40:13 -08:00
Alex Holliday c284eafd63 update logs to standard 2025-01-02 11:39:53 -08:00
Alex Holliday 3fc5d7a336 udpate log to standard 2025-01-02 11:34:09 -08:00
Alex Holliday 3fb114f898 udpate log to standard 2025-01-02 11:33:57 -08:00
Alex Holliday 50e3e2b97c Add distributed http type to monitor 2025-01-02 11:27:05 -08:00
Alex Holliday 1303497947 Add distributed uptime model 2025-01-02 11:26:08 -08:00
Alexander Holliday 1e0102a5b4 Merge pull request #1507 from bluewave-labs/develop
develop -> master
2025-01-02 11:09:31 -08:00
Alexander Holliday 70ba745e07 Merge pull request #1506 from bluewave-labs/hotfix/fe/bad-merge
add missing auth headers to hardware-details request
2025-01-02 10:59:41 -08:00
Alex Holliday decda4450b add missing auth headers to hardware-details request 2025-01-02 10:58:55 -08:00
Alex Holliday 0583bcb0b1 Add healthcheck to redis image 2025-01-02 10:54:30 -08:00
Alexander Holliday 43ff7a4318 Merge pull request #1504 from bluewave-labs/hotfix/fe/bad-merge
Fix bad merge commit
2025-01-02 10:04:49 -08:00
Alex Holliday 30d8595cac Fix bad merge commit 2025-01-02 10:04:23 -08:00
Alexander Holliday 7b3a83a37f Merge pull request #1502 from bluewave-labs/fix/be/improved-jobqueue
fix: be/improved jobqueue
2025-01-02 09:59:04 -08:00
Alexander Holliday b7bd641c7f Merge pull request #1501 from bluewave-labs/fix/be/pagespeed-index
fix: add indices for PageSpeedCheck
2025-01-02 09:58:44 -08:00
Alexander Holliday 1ce3e227fc Merge pull request #1498 from bluewave-labs/feat/be/hardware-details
feat: be/hardware details
2025-01-02 09:58:28 -08:00
Alexander Holliday 25fd0f7055 Merge branch 'develop' into feat/be/hardware-details 2025-01-02 09:58:19 -08:00
Alexander Holliday f958424793 Merge pull request #1497 from bluewave-labs/feat/fe/uptime-details
feat: fe/uptime details
2025-01-02 09:57:30 -08:00
Gorkem Cetin (BWL) 2dbf324615 Update README.md 2025-01-02 08:20:49 -05:00
Alex Holliday 62d1d34937 better worker task handling, implement progress updates 2025-01-01 20:14:03 -08:00
Alex Holliday c11e412e81 fix init job queue method 2025-01-01 18:18:39 -08:00
Alex Holliday b332bed14e modify date format to ISO standard, remove toTimeStamp method 2025-01-01 17:27:12 -08:00
Alex Holliday e7cf127fde Fix tooltip and proptypes 2025-01-01 17:20:00 -08:00
Alex Holliday 69f7cf2ab1 fix check spread 2025-01-01 17:11:38 -08:00
Alex Holliday 86d11c7277 remove console.log 2025-01-01 17:11:26 -08:00
Alex Holliday dafdb03ce0 safe variable access 2025-01-01 17:06:49 -08:00
Alex Holliday 4c2a0fd2d3 Use initJobQueue method to initialize JobQueue, flush Redis DB on shutdown timeout 2025-01-01 13:33:01 -08:00
Alex Holliday e222b13b51 add separate queue for each monitor type 2025-01-01 13:32:18 -08:00
Alex Holliday 8fcbe43a9d Remove console log from hardware check module 2025-01-01 13:31:38 -08:00
Alex Holliday 48e8c29dd4 Add indices for PageSpeedCheck 2025-01-01 13:27:23 -08:00
Alex Holliday 6563cedf88 add jsdoc and validation 2025-01-01 09:08:07 -08:00
Alex Holliday c572096a50 safe data access 2025-01-01 09:02:21 -08:00
Alexander Holliday 67fe63c980 Merge pull request #1496 from renovate-bot/renovate/bullmq-5.x
fix(deps): update dependency bullmq to v5.34.6
2025-01-01 08:39:38 -08:00
Gorkem Cetin (BWL) ca258da7f0 Update README.md 2025-01-01 08:55:47 -05:00
Gorkem Cetin (BWL) 24f5c54fe5 Update README.md 2024-12-31 23:25:14 -05:00
Alex Holliday 180585806b Add last checked 2024-12-31 20:12:50 -08:00
Alex Holliday f176768b54 Refactor FE to use new query 2024-12-31 20:00:22 -08:00
Alex Holliday a3518e4462 add missing date range, add date range lookup 2024-12-31 20:00:02 -08:00
Alex Holliday cb425b2b3b Merge branch 'develop' into feat/be/hardware-details 2024-12-31 17:39:19 -08:00
Alex Holliday 2d9b4eaab6 write hardware monitor query 2024-12-31 17:39:02 -08:00
Alexander Holliday 350e560079 Merge pull request #1481 from bluewave-labs/fix/be/db-indices
fix: be/db indices
2024-12-31 17:38:29 -08:00
Alex Holliday 2a41642be7 add two mising date clauses to details query 2024-12-31 16:40:44 -08:00
Alex Holliday 58c416f909 use upchecksaggregate total checks instead of total checks 2024-12-31 16:40:16 -08:00
Alex Holliday e29b55408f Fixed color for upbarchart: 2024-12-31 16:39:50 -08:00
Alex Holliday 247bf44406 begin implementing aggregate query 2024-12-31 15:05:14 -08:00
Alex Holliday cf3305611f Add route and controller method for hardware details 2024-12-31 15:05:03 -08:00
Alex Holliday a466e91a45 remove old query 2024-12-31 13:46:56 -08:00
Alex Holliday 91b282e7d9 Refactor charts 2024-12-31 13:43:03 -08:00
Alex Holliday 17bd1f5286 Add temporary normalization functoins 2024-12-31 13:42:49 -08:00
Alex Holliday bbae5052c8 Add group checks to DB query 2024-12-31 13:42:25 -08:00
Gorkem Cetin (BWL) ee87a688ca Remove redundant pr template file 2024-12-31 16:34:55 -05:00
Gorkem Cetin (BWL) f8581a2da0 Create pull_request_template.md 2024-12-31 16:33:18 -05:00
Mend Renovate fabfbec329 fix(deps): update dependency bullmq to v5.34.6 2024-12-31 21:31:36 +00:00
Gorkem Cetin (BWL) 94e67f2377 Update grant_funding.md 2024-12-31 16:12:27 -05:00
Gorkem Cetin (BWL) 68ff079106 Fixes 2024-12-31 15:36:38 -05:00
Gorkem Cetin (BWL) e2c39bc293 Checkmate & Uprock grant funding call initial commit 2024-12-31 15:16:33 -05:00
Alex Holliday 7f601b1e11 rebuild query 2024-12-31 11:43:04 -08:00
Alexander Holliday 5567eec75d Merge pull request #1495 from renovate-bot/renovate/mongoose-8.x-lockfile
fix(deps): update dependency mongoose to v8.9.3
2024-12-31 10:12:14 -08:00
Alexander Holliday 11972c6c54 Merge pull request #1486 from mertssmnoglu/fix/server-lint-errors
fix(server-lint): Resolve lint errors in server-side test files
2024-12-31 10:09:49 -08:00
Alex Holliday 6f96e77fec modify query 2024-12-30 22:23:46 -08:00
Alex Holliday 3e995c82aa update response time gauge 2024-12-30 20:11:05 -08:00
Alex Holliday b5ceb5c4c1 Add timestamp method 2024-12-30 19:49:41 -08:00
Alex Holliday 853a28d316 migrate to new query 2024-12-30 19:49:28 -08:00
Alex Holliday d574faa7d8 Add groupUpChecks and groupDownChecks 2024-12-30 19:49:09 -08:00
Alex Holliday 107540b2a8 FE refactor 2024-12-30 16:07:28 -08:00
Alex Holliday e5181727fe Add netwokr request for uptime details 2024-12-30 14:58:20 -08:00
Alex Holliday 5391145a9a Add aggregate queries for uptime monitor stats 2024-12-30 14:57:19 -08:00
Alex Holliday 0551eb010d Add controller method for uptime details 2024-12-30 14:56:50 -08:00
Alex Holliday d9c583f972 Add a route for uptime details 2024-12-30 14:56:38 -08:00
Mend Renovate 566cd8538a fix(deps): update dependency mongoose to v8.9.3 2024-12-30 22:07:41 +00:00
Alexander Holliday 334e8c32f6 Merge pull request #1491 from rafeyrana/sidebar-button
Hamburger Icon button rounded
2024-12-30 10:21:48 -08:00
Rafey Rana 36ca55e2b6 code cleanup 2024-12-29 12:10:54 -05:00
Rafey Rana bf8fcadb4c Sidebar icon button rounded 2024-12-29 12:02:11 -05:00
Alexander Holliday 3e841b2e16 Merge pull request #1472 from aroy114/button
Red button mouse hover status has wrong color #1335
2024-12-29 08:59:59 -08:00
Alexander Holliday 811ccaeaf1 Merge pull request #1479 from bluewave-labs/feat/fe/tcp-port-monitoring
feat: fe/tcp port monitoring, resolves #1476
2024-12-29 08:58:05 -08:00
Alexander Holliday ba05405f82 Merge pull request #1478 from bluewave-labs/feat/be/tcp-port-monitoring
feat: be/tcp port monitoring, references #1476
2024-12-29 08:57:35 -08:00
Alexander Holliday 26f1c275df Merge pull request #1489 from aroy114/navbar
Clicking on settings shouldn't open the "Other" drawer, resolves #1485
2024-12-29 08:54:15 -08:00
Alexander Holliday 43b8a3cc6e Merge pull request #1487 from renovate-bot/renovate/fontsource-monorepo
fix(deps): update dependency @fontsource/roboto to v5.1.1
2024-12-29 08:48:34 -08:00
Anand Roy c2a178c99e fixed navbar links 2024-12-29 15:37:17 +00:00
Gorkem Cetin (BWL) d2ef1ab4bd I'll find a better design here with photos 2024-12-29 09:24:01 -05:00
Mend Renovate fee7997222 fix(deps): update dependency @fontsource/roboto to v5.1.1 2024-12-29 13:53:21 +00:00
Anand Roy 35697654b2 fixed variable 2024-12-29 13:37:23 +00:00
Mert Şişmanoğlu b9863e5eee chore(lint): Add chai globals to eslint config
- It will prevent ignore 'no-undef' errors for expect. We are globally registered the expect.
2024-12-29 14:41:42 +03:00
Mert Şişmanoğlu fcda60fe5b fix(test): Use 'npm run lint-fix' to fix 'mocha/no-mocha-arrows' like lint warnings
- ref: https://mochajs.org/#arrow-functions
2024-12-29 14:25:59 +03:00
Alex Holliday 0b5594f9c9 force index usage for getMonitorStatsById 2024-12-28 09:37:00 -08:00
Alex Holliday bdf23a45b7 Sync indices on startup 2024-12-28 09:35:34 -08:00
Alex Holliday ce3e7cf384 Add indices to HardwareCheck model 2024-12-28 09:35:00 -08:00
Alex Holliday 91e7229b23 Add indices to Check model 2024-12-28 09:34:27 -08:00
Gorkem Cetin (BWL) 319e6d6017 Add core maintainers 2024-12-28 00:43:27 -05:00
Alexander Holliday fab60f0da9 Merge pull request #1480 from bluewave-labs/hotfix/non-blocking-calls
hotfix: made adding jobs and scaling workers non-blocking
2024-12-27 14:02:18 -08:00
Alex Holliday d686a8de5e made adding jobs and scaling workers non-blocking 2024-12-27 14:00:47 -08:00
Alex Holliday bc748c57e5 Add port type to uptime queries 2024-12-27 11:17:25 -08:00
Alex Holliday ab27011b91 Add port option to create uptime monitors 2024-12-27 11:16:58 -08:00
Alex Holliday d27c44d498 Add validation for port to FE 2024-12-27 11:16:25 -08:00
Alex Holliday 004184b0f4 add port types to monitor operations on BE 2024-12-27 11:16:09 -08:00
Alex Holliday f155b8ceb8 Add validation and status messages 2024-12-27 10:49:54 -08:00
Alex Holliday 6ff1cd0b5d Add handling of ports to status service 2024-12-27 10:49:45 -08:00
Alex Holliday 9571af4dba Add port monitoring operation to network service 2024-12-27 10:49:31 -08:00
Alex Holliday 73994caa14 Add net module to network service 2024-12-27 10:49:06 -08:00
Alex Holliday 0a68611234 Add port type to Monitor 2024-12-27 10:48:44 -08:00
Alexander Holliday 955d3c6298 Merge pull request #1470 from bluewave-labs/feat/be/migrate-to-class
feat: be/migrate to class
2024-12-27 09:54:20 -08:00
Alexander Holliday bab0595834 Merge pull request #1477 from renovate-bot/renovate/material-ui-monorepo
fix(deps): update dependency @mui/x-data-grid to v7.23.5
2024-12-27 09:01:19 -08:00
Mend Renovate f32caeaea6 fix(deps): update dependency @mui/x-data-grid to v7.23.5 2024-12-27 16:57:37 +00:00
Alexander Holliday f37b5f4458 Merge pull request #1475 from bluewave-labs/hotfix/fe/details-certificiate
hotfix: fix certificate format
2024-12-26 12:27:13 -08:00
Alex Holliday fd21155209 fix certificate format 2024-12-26 12:26:06 -08:00
Alexander Holliday e2e155f1e6 Merge pull request #1473 from aroy114/certificate
Move certificate expiry to a card #1327
2024-12-26 12:18:39 -08:00
Anand Roy b708e7aa7d added year in the date 2024-12-26 19:16:24 +00:00
Alexander Holliday b7c5d75f31 Merge pull request #1471 from renovate-bot/renovate/bullmq-5.x
fix(deps): update dependency bullmq to v5.34.5
2024-12-26 07:30:56 -08:00
Mend Renovate 9182e2a3a3 fix(deps): update dependency bullmq to v5.34.5 2024-12-25 13:58:22 +00:00
Alex Holliday 83e489cfe5 Use service registry instead of adding services to request 2024-12-24 12:00:52 -08:00
Alex Holliday 139c1fd215 refactor middleware to use service registry 2024-12-24 12:00:36 -08:00
Alex Holliday 7f6e096491 refacgtor MongoDB to be class based 2024-12-24 11:59:18 -08:00
Alex Holliday 0861595d39 Refactor routers to be class based 2024-12-24 11:58:56 -08:00
Alex Holliday cc40bd6006 refactor controllers to be class based 2024-12-24 11:58:42 -08:00
Alex Holliday 8b0af64761 Add static name exports to all services 2024-12-24 11:58:26 -08:00
Alex Holliday 7c1442e32f Add a service registry 2024-12-24 11:56:08 -08:00
Alexander Holliday 2efd26da8d Merge pull request #1464 from renovate-bot/renovate/material-ui-monorepo
fix(deps): update material-ui monorepo
2024-12-24 07:13:18 -08:00
Alexander Holliday 169d58d0bd Merge pull request #1469 from renovate-bot/renovate/eslint-plugin-react-7.x-lockfile
chore(deps): update dependency eslint-plugin-react to v7.37.3
2024-12-24 07:13:05 -08:00
Mend Renovate 293bc12cef chore(deps): update dependency eslint-plugin-react to v7.37.3 2024-12-24 04:27:42 +00:00
Alexander Holliday 13d40e7cbb Merge pull request #1468 from bluewave-labs/revert-1467-hotfix/be/move-middleware
Revert "hotfix: move middleware to after JobQueue initialization"
2024-12-23 15:52:27 -08:00
Alexander Holliday bd81c49b1e Revert "hotfix: move middleware to after JobQueue initialization" 2024-12-23 15:52:16 -08:00
Alexander Holliday b2e2d08561 Merge pull request #1467 from bluewave-labs/hotfix/be/move-middleware
hotfix: move middleware to after JobQueue initialization
2024-12-23 15:17:41 -08:00
Alex Holliday fdb77bb073 move middleware to after JobQueue initialization 2024-12-23 15:16:52 -08:00
Alexander Holliday 6a469392d3 Merge pull request #1466 from bluewave-labs/hotfix/devops/docker-compose
fix test config docker-compose URL
2024-12-23 15:09:10 -08:00
Alex Holliday be45dba3df fix test config docker-compose URL 2024-12-23 15:08:36 -08:00
Alexander Holliday fdcd0df126 Merge pull request #1465 from bluewave-labs/develop
Develop -> Master
2024-12-23 15:01:19 -08:00
Alexander Holliday 5147a685e0 Merge pull request #1462 from bluewave-labs/fix/devops/restart-docker-images
fix: always restart docker images
2024-12-23 13:42:09 -08:00
Mend Renovate 3a4aa2f45f fix(deps): update material-ui monorepo 2024-12-23 18:43:38 +00:00
Alexander Holliday ad2af345e8 Merge pull request #1463 from bluewave-labs/hotfix/fe/theme
neuter -> neutral
2024-12-23 10:42:53 -08:00
Alex Holliday 10d91334d8 neuter -> neutral 2024-12-23 10:42:24 -08:00
Alex Holliday b2b9816b6b always restart docker images 2024-12-23 09:49:01 -08:00
Alexander Holliday ef6e97796f Merge pull request #1460 from bluewave-labs/fix/button-contrast
Fix: group buttons contrast text
2024-12-23 09:46:11 -08:00
Caio Cabral addd707790 neuter to neutral 2024-12-23 12:44:12 -05:00
Alexander Holliday f7c4841094 Merge pull request #1444 from bluewave-labs/fix/fe/uptime-skeleton-and-fallback
fix: add additional not loading flag to fallback conditional rendering, resolves #1426
2024-12-23 09:40:52 -08:00
Alex Holliday 1d038cff39 simplify skeleton loading 2024-12-23 09:36:53 -08:00
Alexander Holliday 4b1b9f2e66 Merge pull request #1461 from bluewave-labs/fix/sidebar
feat: adjustments to sidebar
2024-12-23 07:17:47 -08:00
Caio Cabral 3719644d5c feat: adjustments to sidebar 2024-12-23 09:51:41 -05:00
Caio Cabral c827c4404a ix: group buttons contrast text 2024-12-23 07:32:08 -05:00
Alexander Holliday 8b291ce903 Merge pull request #1458 from bluewave-labs/fix/row-hover
fix: row background color on hover
2024-12-22 22:53:20 -08:00
Alexander Holliday 4cd2ea1442 Merge pull request #1456 from mertssmnoglu/fix/server-old-name
Fix: Old name reference on the Server Side
2024-12-22 18:20:23 -08:00
Alexander Holliday 85a73fd08b Merge pull request #1457 from bluewave-labs/fix/response-chart-color
fix: pie chart colors and Metrics box that was overflowing
2024-12-22 18:16:22 -08:00
Alexander Holliday 0221e5b077 Merge pull request #1443 from bluewave-labs/fix/be/heap-size-and-dependency-bump
fix: add env var to increase node heap size, bump server dependencies
2024-12-22 17:28:01 -08:00
Alexander Holliday e294b6343a Merge branch 'develop' into fix/be/heap-size-and-dependency-bump 2024-12-22 17:27:52 -08:00
Alexander Holliday 639fd425d7 Merge pull request #1442 from bluewave-labs/fix/be/hardware-check-concurrency
fix: hardware-check concurrency issue
2024-12-22 17:24:51 -08:00
Alexander Holliday fbb01cd7af Merge pull request #1454 from renovate-bot/renovate/react-router-monorepo
fix(deps): update react-router monorepo to v6.28.1
2024-12-22 17:22:30 -08:00
Alexander Holliday 3c3194687a Merge pull request #1455 from renovate-bot/renovate/bullmq-5.x
fix(deps): update dependency bullmq to v5.34.4
2024-12-22 17:22:11 -08:00
Caio Cabral e1ea105872 fix: row background color on hover 2024-12-22 20:07:53 -05:00
Caio Cabral 49f675cf02 fix: pie chart colors and Metrics box that was overflowing 2024-12-22 19:52:40 -05:00
Mert Şişmanoğlu e84a7b4a2e fix: Update references from Bluewave Uptime to Checkmate in email templates and authController 2024-12-22 00:15:05 +03:00
Mert Şişmanoğlu 9f04e786e3 chore(openapi): Rename old bluewave uptime to checkmate in Server/openapi.json 2024-12-22 00:08:45 +03:00
Gorkem Cetin (BWL) 213c055fad Update README.md 2024-12-21 13:41:51 -05:00
Gorkem Cetin (BWL) c2b7d1a959 Update README.md 2024-12-21 13:41:35 -05:00
Mend Renovate 5ac77a96f0 fix(deps): update dependency bullmq to v5.34.4 2024-12-21 05:09:28 +00:00
Gorkem Cetin (BWL) 4707bda1b8 Additions & fixes for readme.md 2024-12-20 19:58:44 -05:00
Mend Renovate e431930585 fix(deps): update react-router monorepo to v6.28.1 2024-12-20 22:55:56 +00:00
Alexander Holliday d64aae3ff0 Merge pull request #1453 from bluewave-labs/dependabot/npm_and_yarn/Client/nanoid-3.3.8
chore(deps): bump nanoid from 3.3.7 to 3.3.8 in /Client
2024-12-20 14:55:42 -08:00
Alexander Holliday 6b1f892852 Merge pull request #1451 from bluewave-labs/fix/be/duplicate-success-msg
fix: remove duplicate msg, resolves #1446
2024-12-20 14:55:27 -08:00
Alexander Holliday ca722091b5 Merge pull request #1452 from mertssmnoglu/feat/server-toolchain
Update development toolchain of the `Server/`
2024-12-20 14:55:10 -08:00
Mert Şişmanoğlu d4a08214e8 chore(eslint): Add ecmaVersion and sourceType to 'languageOptions' 2024-12-21 00:56:04 +03:00
dependabot[bot] de25ad7623 chore(deps): bump nanoid from 3.3.7 to 3.3.8 in /Client
Bumps [nanoid](https://github.com/ai/nanoid) from 3.3.7 to 3.3.8.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.3.7...3.3.8)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-20 21:09:20 +00:00
Mert Şişmanoğlu a8a36dac1b chore(package): Add new package scripts for linting and formatting
- lint
- lint-fix
- format
- format-check
2024-12-20 23:49:54 +03:00
Mert Şişmanoğlu f9874d33fc chore(eslint): Import ESlint to the /Server
- Exec 'npm init @eslint/config@latest'
- Update configuration according to docs
2024-12-20 23:29:48 +03:00
Alex Holliday a593768c23 remove duplicate msg 2024-12-20 11:48:49 -08:00
Alexander Holliday 021011f83e Merge pull request #1409 from peterpardo/feat/dark-mode-switch
Feat: Add dark mode switch
2024-12-20 10:22:03 -08:00
Alexander Holliday 73855b0cd3 Merge pull request #1445 from renovate-bot/renovate/react-monorepo
chore(deps): update dependency @types/react to v18.3.18
2024-12-20 10:16:04 -08:00
Peter Carl Pardo 0c16a62131 feat: removed unused imports 2024-12-20 23:48:25 +08:00
Peter Carl Pardo b5a125b388 feat: added theme switch and forgot password label in login page 2024-12-20 23:38:55 +08:00
Peter Carl Pardo 3f7d3efe2b feat: removed forgot password label in email and password step 2024-12-20 23:38:16 +08:00
Peter Carl Pardo 9ef1c3572e feat: created ForgotPasswordLabel.jsx component 2024-12-20 23:37:58 +08:00
Peter Carl Pardo 4e85e60735 Merge branch 'develop' into feat/dark-mode-switch 2024-12-20 23:14:17 +08:00
Peter Carl Pardo b97e70f162 feat: moved theme switch stylings 2024-12-20 23:07:36 +08:00
Mend Renovate 65b183a9f2 chore(deps): update dependency @types/react to v18.3.18 2024-12-20 05:43:49 +00:00
Alex Holliday 98199f2e1c Add additional not loading flag to fallback conditional rendering 2024-12-19 12:03:09 -08:00
Alex Holliday 7d5df4b7b8 add env var to increase node heap size, bump server dependencies 2024-12-19 11:55:34 -08:00
Alex Holliday 914217058b use to resolve concurrency issue 2024-12-19 11:52:27 -08:00
Alexander Holliday 4a78adfefd Merge pull request #1441 from bluewave-labs/hotfix/fe/uptime-details
add missing import
2024-12-19 10:19:03 -08:00
Alex Holliday 0afbd527b3 add missing import 2024-12-19 10:18:25 -08:00
Alexander Holliday 4a8d5b73eb Merge pull request #1440 from Anand-Royy/tab-border
Tab bars are displayed upwards when hovered on them #1438
2024-12-19 09:45:28 -08:00
Alexander Holliday 34f3f3ddbf Merge pull request #1434 from bluewave-labs/fix/fe/infra-boxes
fix: fe/infra boxes, resolves #1427
2024-12-19 09:37:44 -08:00
Alex Holliday d42610caaa Merge branch 'develop' into fix/fe/infra-boxes 2024-12-19 09:37:05 -08:00
Alex Holliday 29a12da133 Remove console log 2024-12-19 09:31:21 -08:00
Alexander Holliday b02236c2e2 Merge pull request #1437 from renovate-bot/renovate/mongoose-8.x-lockfile
fix(deps): update dependency mongoose to v8.9.2
2024-12-19 09:29:08 -08:00
Alexander Holliday fe829c4d6a Merge pull request #1433 from bluewave-labs/fix/fe/refactor-config-box
fix: fe/refactor config box
2024-12-19 09:28:26 -08:00
Alexander Holliday a16ede06c8 Merge pull request #1435 from bluewave-labs/fix/fe/refactor-icon-box
fix: fe/refactor icon box
2024-12-19 09:28:08 -08:00
Alexander Holliday e9e1f04115 Merge pull request #1439 from renovate-bot/renovate/material-ui-monorepo
fix(deps): update material-ui monorepo to v7.23.3
2024-12-19 09:27:50 -08:00
Alexander Holliday 3fb70353da Merge pull request #1436 from bluewave-labs/fix/fe/protected-route-proptype
fix: protected route proptype
2024-12-19 09:27:33 -08:00
Anand Roy 4da2683404 fixed the tab border 2024-12-19 16:12:07 +00:00
Mend Renovate 77893a5fe3 fix(deps): update material-ui monorepo to v7.23.3 2024-12-19 15:48:38 +00:00
Peter Carl Pardo 76dc4ebb0c feat: removed import for easings functionality 2024-12-19 21:18:23 +08:00
Mend Renovate 862a8f05b7 fix(deps): update dependency mongoose to v8.9.2 2024-12-19 12:55:15 +00:00
Alex Holliday b70fc7e9c6 Fix protected route proptype 2024-12-18 14:26:15 -08:00
Alex Holliday 21b876a723 Use IconBox consistently 2024-12-18 14:11:11 -08:00
Alex Holliday 42f4cc8ddd Create reusable IconBox 2024-12-18 14:11:03 -08:00
Alex Holliday eebf438c13 Add JSdoc and proptypes 2024-12-18 13:46:51 -08:00
Alex Holliday e37413771e Use StatBox consistently 2024-12-18 13:45:02 -08:00
Alex Holliday aee3e6a604 refactor StatBox to reusable component 2024-12-18 13:44:48 -08:00
Alex Holliday b55aee8511 remove old config boxes 2024-12-18 12:20:18 -08:00
Alex Holliday c47ec6ce8b refactor create maintenance to use new ConfigBox 2024-12-18 12:20:09 -08:00
Alex Holliday bd5bc2617c Add a config box component 2024-12-18 12:19:21 -08:00
Alexander Holliday e5a7860ff0 Merge pull request #1414 from Anand-Royy/radio
Add a new uptime monitor > Radio buttons: unselected radio buttons are hard to see #1404 #1412
2024-12-18 10:23:10 -08:00
Alexander Holliday 6a1ebbccfe Merge pull request #1432 from Anand-Royy/reset-pass
Reset password link should be present in all pages #1403
2024-12-18 10:18:11 -08:00
Alexander Holliday bbb791579b Merge pull request #1431 from bluewave-labs/1378-settings-page-should-have-left-and-right-dividers
- Add divider for setting
2024-12-18 09:59:47 -08:00
Anand Roy 7f7423aed8 reset password link added 2024-12-18 17:46:44 +00:00
Shemy Gan 6e29e37241 - Add divider for setting 2024-12-18 10:42:41 -05:00
Alexander Holliday ff6c890eb0 Merge pull request #1429 from renovate-bot/renovate/bullmq-5.x
fix(deps): update dependency bullmq to v5.34.3
2024-12-18 06:39:46 -08:00
Mend Renovate 3cc7cf8dea fix(deps): update dependency bullmq to v5.34.3 2024-12-18 13:33:35 +00:00
Peter Carl Pardo 7c5c3507d8 feat: removed useEffect in toggling theme 2024-12-18 18:57:38 +08:00
Alexander Holliday d9d96031a4 Merge pull request #1424 from bluewave-labs/feat/fe/team-empty-view
feat: fe/team empty view, resolves #1400
2024-12-17 16:18:07 -08:00
Alexander Holliday 1d4f67a899 Merge pull request #1425 from bluewave-labs/fix/fe/remove-advanced-settings
fix: remove advanced status page, resolves #1379
2024-12-17 16:17:47 -08:00
Alex Holliday 1bc6717e72 remove advanced status page 2024-12-17 14:04:42 -08:00
Alex Holliday f880c2623c Supply empty message to basic table 2024-12-17 13:51:03 -08:00
Alex Holliday 1e6b339483 Add empty message to BasicTable component 2024-12-17 13:50:55 -08:00
Alexander Holliday 4ff4c95951 Merge pull request #1423 from bluewave-labs/hotfix/route-import
hotfix: login import
2024-12-17 13:40:33 -08:00
Alex Holliday f631fb16ac fix login import 2024-12-17 13:39:55 -08:00
Alexander Holliday d22ca5098a Merge pull request #1422 from bluewave-labs/fix/fe/login-route
fix: fe/login route
2024-12-17 13:11:55 -08:00
Alex Holliday 1f823fc672 remove old login page 2024-12-17 13:10:40 -08:00
Alex Holliday a507e1282e Fix links in login page 2024-12-17 13:10:28 -08:00
Alexander Holliday a194732152 Merge pull request #1411 from sha-linimoorthy/issue_#1402
Changed the avatar color to checkmate blue color
2024-12-17 10:05:40 -08:00
Alexander Holliday 7d1e7ccb94 Merge pull request #1419 from bluewave-labs/fix/fe/password-boot
fix: incorrect password on password change causes user to be logged out, resolves #1384
2024-12-17 10:03:18 -08:00
Alex Holliday a42e280e49 update comments 2024-12-17 10:02:56 -08:00
Peter Carl Pardo acefedd6e7 feat: add attribution for theme switch 2024-12-17 23:57:12 +08:00
Alexander Holliday ab2dec2a4f Merge pull request #1375 from bluewave-labs/fix/be/invite-route
fix: invite route
2024-12-17 07:30:15 -08:00
Alexander Holliday 5ebd6fd79a Merge pull request #1417 from bluewave-labs/fix/infra-monitor-empty-view
fix: infrasctrucutre details empty view message, resolves #1401
2024-12-17 07:29:48 -08:00
Alexander Holliday 2a3feb1c69 Merge pull request #1418 from bluewave-labs/fix/devops/dockerignore
fix: update dockeringore
2024-12-17 07:29:17 -08:00
Alexander Holliday 063ec5ffe6 Merge pull request #1420 from renovate-bot/renovate/material-ui-monorepo
fix(deps): update material-ui monorepo
2024-12-17 07:29:03 -08:00
Mend Renovate a9ed2158e9 fix(deps): update material-ui monorepo 2024-12-17 15:28:45 +00:00
Alexander Holliday d2daf54eb8 Merge pull request #1383 from bluewave-labs/fix/fe/monitor-pause-toast
fix: add pause toast to PageSpeed and Uptime configure pages, resolves #1380
2024-12-17 07:28:35 -08:00
Alexander Holliday f1b6e6fcc8 Merge pull request #1376 from bluewave-labs/fix/fe/sidebar-logo-navigation
fix: logo navigation, resolves #1320
2024-12-17 07:27:57 -08:00
Shalini 2d27eed9ba Used theme for the avatar BG instead of hard coding 2024-12-17 10:38:20 +05:30
Alex Holliday df11a457f1 401 -> 403 2024-12-16 18:20:41 -08:00
Alex Holliday d94ba5c524 update dockeringore 2024-12-16 17:58:58 -08:00
Alex Holliday 0aa46aa2c2 fix empty view message 2024-12-16 17:55:45 -08:00
Alexander Holliday d4a6df5df3 Merge pull request #1415 from renovate-bot/renovate/mongoose-8.x-lockfile
fix(deps): update dependency mongoose to v8.9.1
2024-12-16 16:53:46 -08:00
Mend Renovate 0022d839df fix(deps): update dependency mongoose to v8.9.1 2024-12-16 23:54:02 +00:00
Anand Roy dd5527931d modified radio button visibility 2024-12-16 18:59:29 +00:00
Alexander Holliday 1364004702 Merge pull request #1345 from bluewave-labs/1321-remove-continue-with-email-page-which-is-an-unnecessary-step
- Remove the login landing page
2024-12-16 10:49:15 -08:00
Alex Holliday 3c725814cf Merge branch 'develop' into 1321-remove-continue-with-email-page-which-is-an-unnecessary-step 2024-12-17 02:47:44 +08:00
Alexander Holliday abc88aa6c6 Merge pull request #1413 from renovate-bot/renovate/react-monorepo
chore(deps): update dependency @types/react to v18.3.17
2024-12-16 09:36:56 -08:00
Peter Carl Pardo 663f2334d1 feat: added dark mode functionality 2024-12-17 01:24:15 +08:00
Mend Renovate 69ef798f60 chore(deps): update dependency @types/react to v18.3.17 2024-12-16 16:57:03 +00:00
Peter Carl Pardo 0e8b82be97 Merge branch 'develop' into feat/dark-mode-switch 2024-12-16 22:35:16 +08:00
Gorkem Cetin (BWL) 37f74271aa 2500 * 2024-12-16 09:24:39 -05:00
Shalini M e30b64bed3 Changed the avatar color to checkmate blue color 2024-12-16 17:08:27 +05:30
Peter Carl Pardo e6faf96984 cleanup 2024-12-16 14:28:58 +08:00
Peter Carl Pardo c565cc7362 Merge branch 'develop' into feat/dark-mode-switch 2024-12-16 14:18:25 +08:00
Peter Carl Pardo cdcefb1106 feat: add theme switch in sidebar 2024-12-16 14:16:58 +08:00
Peter Carl Pardo 3ab8b1241e feat: Add theme switch in login page 2024-12-16 14:05:56 +08:00
Gorkem Cetin (BWL) 265af61100 Add a warning line in pull_request_template.md 2024-12-15 15:09:33 -05:00
Gorkem Cetin (BWL) 7cbeb75186 Update README.md 2024-12-15 14:45:57 -05:00
Peter Carl Pardo a97b2f0887 feat: cleanup 2024-12-15 23:52:07 +08:00
Peter Carl Pardo efa106f732 feat: add comment for temporary theme logic 2024-12-15 23:51:53 +08:00
Peter Carl Pardo dd488fecb4 feat: cleanup 2024-12-15 23:50:48 +08:00
Peter Carl Pardo 80dee67c08 Merge branch 'develop' into feat/dark-mode-switch 2024-12-15 23:49:05 +08:00
Peter Carl Pardo 6d6a13a833 feat: fix sun beams icon bug 2024-12-15 23:46:16 +08:00
Peter Carl Pardo 55225ad221 feat: initial dark theme switch component 2024-12-15 23:11:50 +08:00
Alexander Holliday 592cc0d615 Merge pull request #1369 from bluewave-labs/refactor/routes
Refactor/routes
2024-12-14 23:06:30 -08:00
Alexander Holliday 3c960d93db Merge pull request #1385 from renovate-bot/renovate/nodemon-3.x
chore(deps): update dependency nodemon to v3.1.9
2024-12-14 23:03:26 -08:00
Alexander Holliday 3c1601e75c Merge pull request #1389 from renovate-bot/renovate/bullmq-5.x
Update dependency bullmq to v5.34.2
2024-12-14 23:03:07 -08:00
Alexander Holliday 066ad535c5 Merge pull request #1391 from renovate-bot/renovate/mongoose-8.x-lockfile
Update dependency mongoose to v8.9.0
2024-12-14 23:02:50 -08:00
Alexander Holliday 00636d862b Merge pull request #1393 from bluewave-labs/dependabot/npm_and_yarn/Server/nanoid-3.3.8
chore(deps): bump nanoid from 3.3.7 to 3.3.8 in /Server
2024-12-14 23:02:32 -08:00
Alexander Holliday dd5b0e2db4 Merge pull request #1394 from mertssmnoglu/chore/renovate-add-label
chore(renovate): Auto label assign on Renovate PRs
2024-12-14 23:02:06 -08:00
Gorkem Cetin (BWL) a0289d1893 Update README.md 2024-12-14 20:55:51 -05:00
Gorkem Cetin (BWL) cd2076ff16 Update: stress testing for 1000+ servers at the same time 2024-12-14 20:50:03 -05:00
Mend Renovate 1b0e7ddd50 fix(deps): update dependency bullmq to v5.34.2 2024-12-14 21:10:32 +00:00
Gorkem Cetin (BWL) c6ce627495 Update README.md 2024-12-14 11:53:53 -05:00
Gorkem Cetin (BWL) 425358e1e3 Thank you for 2000 stars! 2024-12-14 10:12:22 -05:00
Gorkem Cetin (BWL) 09644dfcfe Add links to 2 documents for contributors 2024-12-13 22:45:50 -05:00
Gorkem Cetin (BWL) 0476644f44 Update for name changes 2024-12-13 22:43:54 -05:00
Gorkem Cetin (BWL) ab0d1ab469 Update README.md 2024-12-13 21:04:21 -05:00
Gorkem Cetin (BWL) 528bc0b1e0 Update pull_request_template.md 2024-12-13 19:04:29 -05:00
Caio Cabral 7d7c616a73 fix: function not called 2024-12-13 17:56:37 -05:00
Mert Şişmanoğlu 2628f2ae32 chore(renovate): Add 'labels' to renovate.json for adding 'dependencies' label on automated PRs 2024-12-14 00:27:26 +03:00
dependabot[bot] 946e74aeab chore(deps): bump nanoid from 3.3.7 to 3.3.8 in /Server
Bumps [nanoid](https://github.com/ai/nanoid) from 3.3.7 to 3.3.8.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.3.7...3.3.8)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-13 21:15:02 +00:00
Gorkem Cetin (BWL) 2e970f6f84 Update README.md 2024-12-13 15:18:23 -05:00
Gorkem Cetin (BWL) 0cfb08d03a Mention good-first-issue 2024-12-13 13:20:33 -05:00
Mend Renovate fb127f5ba2 fix(deps): update dependency mongoose to v8.9.0 2024-12-13 17:31:01 +00:00
Gorkem Cetin (BWL) 7bef37f8c8 Add coolify option 2024-12-13 11:11:40 -05:00
Shemy Gan c72334253d - Refactor out/Rename the StepOne and StepTwo components 2024-12-13 10:50:36 -05:00
Mend Renovate b2f8961bac chore(deps): update dependency nodemon to v3.1.9 2024-12-13 13:19:47 +00:00
Alex Holliday 4b47d43cd1 Add pause toast to PageSpeed and Uptime configure pages 2024-12-13 13:12:38 +08:00
Gorkem Cetin (BWL) 1cf7df1ac6 Merge pull request #1382 from bluewave-labs/gorkem-bwl-patch-1
Fix wording
2024-12-12 23:53:09 -05:00
Gorkem Cetin (BWL) abfd279707 Fix wording 2024-12-12 23:52:35 -05:00
Alex Holliday 35dcf19169 clicking on logo redirects to 2024-12-13 11:52:49 +08:00
Alex Holliday daf5ccb342 Fix invite route 2024-12-13 11:45:41 +08:00
Alexander Holliday fd1232d72e Merge pull request #1374 from bluewave-labs/fix/monitor-fallback-link
fix: fallback link
2024-12-12 19:20:50 -08:00
Alex Holliday b651d09769 Fix fallback link 2024-12-13 11:20:09 +08:00
Alexander Holliday ba5f58683e Merge pull request #1344 from peterpardo/fix/update-average-response-time-chart-color
fix: update red palette colors
2024-12-12 17:54:38 -08:00
Gorkem Cetin (BWL) ab0c509c92 Update README.md 2024-12-12 20:23:21 -05:00
Gorkem Cetin (BWL) 265f2a7e4a Update README.md 2024-12-12 20:17:18 -05:00
Alexander Holliday 1f823c120b Merge pull request #1371 from renovate-bot/renovate/material-ui-monorepo
fix(deps): update material-ui monorepo to v7.23.2
2024-12-12 15:25:40 -08:00
Alexander Holliday 3aeb1cea1c Merge pull request #1372 from renovate-bot/renovate/recharts-2.x
fix(deps): update dependency recharts to v2.15.0
2024-12-12 15:25:28 -08:00
Mend Renovate fd89cbca1d fix(deps): update dependency recharts to v2.15.0 2024-12-12 22:43:46 +00:00
Caio Cabral a8a8664fc6 refactor: simplify protected route component 2024-12-12 14:43:57 -05:00
Mend Renovate 1aa05eb438 fix(deps): update material-ui monorepo to v7.23.2 2024-12-12 19:05:54 +00:00
Gorkem Cetin (BWL) 4f2a1b7342 Update README.md 2024-12-12 14:03:26 -05:00
Peter Carl Pardo 017eab53ff Merge branch 'develop' into fix/update-average-response-time-chart-color 2024-12-13 00:47:59 +08:00
Peter Carl Pardo 1a856ed463 fix: Update chart color for "Poor" category 2024-12-13 00:47:09 +08:00
Caio Marcellus Cabral 476595e891 Merge pull request #1370 from bluewave-labs/hotFix/monitors-uptime
hotfix: imports
2024-12-12 10:43:57 -05:00
Caio Cabral dc46a0c2b2 hotfix: imports 2024-12-12 10:23:36 -05:00
Peter Carl Pardo c3298147b6 Revert "fix: update red palette colors"
This reverts commit 79dd781386.
2024-12-12 22:39:42 +08:00
Caio Cabral ee4ef15874 resolving conflicts 2024-12-12 09:20:24 -05:00
Caio Cabral 788ffc92c8 resolving conflicts 2024-12-12 09:15:39 -05:00
Caio Cabral cce1ed610c chore: delete console 2024-12-12 09:11:08 -05:00
Caio Cabral 910080820b refactor: removed withAdminProps HOC and changed for custom hook reducing component wrapping 2024-12-12 09:07:11 -05:00
Caio Cabral a70e96f1f3 refactor: avoid repetition of the protected routes component 2024-12-12 08:24:10 -05:00
Alexander Holliday 820c6780dd Merge pull request #1367 from tom-doerr/patch-1
Update README.md
2024-12-11 23:10:35 -08:00
Tom Dörr 7d33ac11b5 Update README.md 2024-12-12 06:33:32 +01:00
Gorkem Cetin (BWL) 52234531db Update pull_request_template.md 2024-12-12 00:17:25 -05:00
Gorkem Cetin (BWL) 2145be5160 Minor update to pull_request_template.md 2024-12-12 00:13:10 -05:00
Caio Cabral 66dc6dbc1a refactor: extracting routes to own component 2024-12-12 00:09:41 -05:00
Alexander Holliday 79c4cfc2e8 Merge pull request #1310 from bluewave-labs/fix/fe/create-infra-refactor
fix: fe/create infra refactor
2024-12-11 20:59:32 -08:00
Alexander Holliday d46e6c7cba Merge pull request #1338 from bluewave-labs/fix/fe/monitors-rename
fix:  Monitors -> Uptime resovles #1333
2024-12-11 20:59:08 -08:00
Alexander Holliday a7ed349af4 Merge pull request #1364 from mertssmnoglu/demo-url-change
fix: update demo server URLs to 'checkmate-demo.bluewavelabs.ca'
2024-12-11 20:58:39 -08:00
Alex Holliday e33091f182 remove 2024-12-12 12:42:32 +08:00
Alex Holliday 3840b3ec2d remove log statement 2024-12-12 12:38:43 +08:00
Alexander Holliday 33ed54191c Merge branch 'develop' into fix/fe/monitors-rename 2024-12-11 20:35:52 -08:00
Alexander Holliday 5579e8a339 Merge pull request #1303 from bluewave-labs/fix/fe/create-pagespeed-refactor
fix: Refactor Create PageSpeed
2024-12-11 20:34:07 -08:00
Alexander Holliday 6cdd839d6a Merge branch 'develop' into fix/fe/create-pagespeed-refactor 2024-12-11 20:33:57 -08:00
Alex Holliday fff8528092 Merge branch 'develop' into fix/fe/monitors-rename 2024-12-12 12:27:10 +08:00
Alex Holliday f57b9a0fa1 remove daterange from dependency array 2024-12-12 12:26:24 +08:00
Alexander Holliday c251f5cba9 Merge pull request #1362 from INDHU1402/fix/fe/update-text
Changed text from "Usage" to "usage" in UI
2024-12-11 20:19:53 -08:00
Alexander Holliday 49209e8cc5 Merge pull request #1366 from bluewave-labs/refactor/rever-infrastructure
Refactor/rever infrastructure
2024-12-11 19:27:11 -08:00
Caio Cabral 24d8d3e8d5 Merge branch 'develop' of https://github.com/bluewave-labs/bluewave-uptime into refactor/rever-infrastructure 2024-12-11 22:08:42 -05:00
Alexander Holliday 48d46253b3 Merge pull request #1311 from bluewave-labs/fix/be/delete-hardware-checks
add deleteHardwareChecksByMonitorId
2024-12-11 18:49:25 -08:00
Caio Cabral 5910bdb9c5 feat: adding same sx to buttons on monitors 2024-12-11 21:18:09 -05:00
Alexander Holliday 903b98b2e7 Merge pull request #1365 from bluewave-labs/refactor/rever-infrastructure
refactor: reverting unused function in infrastructure page
2024-12-11 18:08:37 -08:00
Caio Cabral 8bf254ee49 refactor: reverting unused function in infrastructure page 2024-12-11 21:01:27 -05:00
Alexander Holliday 80e9fa478b Merge pull request #1363 from renovate-bot/renovate/material-ui-monorepo
fix(deps): update material-ui monorepo
2024-12-11 17:57:27 -08:00
Alexander Holliday 6379e614ee Merge pull request #1354 from bluewave-labs/fix/fe/update-pause-count
fix: FE monitor pause count resolves #1325
2024-12-11 17:56:23 -08:00
Alexander Holliday bd2b8488ed Merge pull request #1359 from renovate-bot/renovate/react-monorepo
chore(deps): update dependency @types/react-dom to v18.3.5
2024-12-11 17:42:02 -08:00
Alexander Holliday 04907bcb19 Merge pull request #1357 from bluewave-labs/fix/fe/remove-console
Remove console log statement
2024-12-11 17:41:45 -08:00
Mert Şişmanoğlu b41fe1e70e fix: update demo server URLs to 'checkmate-demo.bluewavelabs.ca' 2024-12-12 00:31:27 +03:00
Shemy Gan 76bfb7717a - Remove onBack from stepOne
- Set step to 0 index
2024-12-11 11:10:19 -05:00
Shemy Gan 7a57d2734a Merge branch 'develop' into 1321-remove-continue-with-email-page-which-is-an-unnecessary-step 2024-12-11 11:04:35 -05:00
Shemy Gan ba8cc04f2d - Remove unused import 2024-12-11 11:03:58 -05:00
Mend Renovate 99c1543825 fix(deps): update material-ui monorepo 2024-12-11 15:39:05 +00:00
Indhu c11726c920 Changed text from Usage --> usage in UI 2024-12-11 15:03:43 +05:30
Mend Renovate bd88079876 chore(deps): update dependency @types/react-dom to v18.3.5 2024-12-11 03:11:43 +00:00
Alexander Holliday 76d0d1b486 Merge pull request #1358 from renovate-bot/renovate/react-monorepo
chore(deps): update dependency @types/react to v18.3.16
2024-12-10 19:11:13 -08:00
Mend Renovate 8bf9c13527 chore(deps): update dependency @types/react to v18.3.16 2024-12-11 03:03:39 +00:00
Alexander Holliday 567ff071ed Merge pull request #1355 from renovate-bot/renovate/reduxjs-toolkit-2.x
fix(deps): update dependency @reduxjs/toolkit to v2.5.0
2024-12-10 19:02:44 -08:00
Alex Holliday d993277463 Remove console log statement 2024-12-11 11:00:50 +08:00
Mend Renovate d17dd8f8b6 fix(deps): update dependency @reduxjs/toolkit to v2.5.0 2024-12-11 02:52:14 +00:00
Alexander Holliday d93b4551b4 Merge pull request #1336 from bluewave-labs/fix/fe/infra-button-vertical-spacing
Fix/fe/infra button vertical spacing, resolves #1332, resolves #1334
2024-12-10 18:51:40 -08:00
Alex Holliday d5acfd9e9e Add update trigger for pause state 2024-12-11 10:46:54 +08:00
Alexander Holliday 3350485d5e Merge pull request #1339 from renovate-bot/renovate/bullmq-5.x
fix(deps): update dependency bullmq to v5.34.0
2024-12-10 18:29:36 -08:00
Alexander Holliday ed075e5f05 Merge pull request #1341 from peterpardo/fix/adjust-search-bar-height
Fix: Adjust Search Bar height
2024-12-10 18:29:22 -08:00
Alexander Holliday f32f994036 Merge pull request #1347 from bluewave-labs/1324-monitor-page-pending-label-should-be-gray-instead-of-yellow-which-is-same-as-paused
- Change to grey  color for pending status
2024-12-10 17:52:50 -08:00
Alexander Holliday 796a66a43c Merge pull request #1348 from bluewave-labs/1231-remove-unusedunimplemented-configuration
- Remove text from configuration
2024-12-10 17:52:06 -08:00
Alexander Holliday 538c5c8d02 Merge pull request #1352 from renovate-bot/renovate/react-monorepo
chore(deps): update dependency @types/react-dom to v18.3.4
2024-12-10 17:51:41 -08:00
Alexander Holliday 0182c37fc7 Merge pull request #1353 from renovate-bot/renovate/react-redux-9.x
fix(deps): update dependency react-redux to v9.2.0
2024-12-10 17:51:25 -08:00
Mend Renovate a78d595c9f fix(deps): update dependency react-redux to v9.2.0 2024-12-11 01:16:49 +00:00
Mend Renovate 4f45be6dfb chore(deps): update dependency @types/react-dom to v18.3.4 2024-12-11 01:16:43 +00:00
Alexander Holliday 6499f70e20 Merge pull request #1349 from renovate-bot/renovate/c8-10.x
chore(deps): update dependency c8 to v10.1.3
2024-12-10 15:12:56 -08:00
Alexander Holliday ab0f934ac6 Merge pull request #1351 from renovate-bot/renovate/react-monorepo
chore(deps): update dependency @types/react to v18.3.15
2024-12-10 15:12:45 -08:00
Alexander Holliday cad881ad10 Merge pull request #1343 from bluewave-labs/1330-remove-disabled-options-in-monitors-page-speed-sections-as-they-are-not-ready-yet
- Remove disabled notifications
2024-12-10 15:09:24 -08:00
Alexander Holliday 1fe5c8aec7 Merge pull request #1329 from bluewave-labs/gorkem-bwl-patch-1
Editting -> Editing
2024-12-10 15:08:53 -08:00
Mend Renovate d52844eb53 chore(deps): update dependency @types/react to v18.3.15 2024-12-10 22:08:21 +00:00
Mend Renovate c04ce25eb0 chore(deps): update dependency c8 to v10.1.3 2024-12-10 20:00:59 +00:00
Shemy Gan 9c48ffe977 - Remove text from configuration 2024-12-10 14:53:07 -05:00
Shemy Gan 1d56c5b674 - Change to grey color for pending status 2024-12-10 14:41:06 -05:00
Shemy Gan cb7f596b0d - Remove the login landing page
- Remove the back button as it is not needed anymore
- Adjust layout due to the removal of backbutton and remove the propTypes requirement for onBack function
2024-12-10 11:21:59 -05:00
Mend Renovate 5e22401df4 fix(deps): update dependency bullmq to v5.34.0 2024-12-10 16:08:52 +00:00
Peter Carl Pardo 79dd781386 fix: update red palette colors 2024-12-11 00:03:36 +08:00
Shemy Gan b02044ae7f - Remove disabled notifications 2024-12-10 10:16:04 -05:00
Peter Carl Pardo f26c0a0575 fix: removing padding Y of search bar 2024-12-10 20:45:45 +08:00
Gorkem Cetin (BWL) 151500def6 Add screenshots 2024-12-10 01:31:40 -05:00
Alex Holliday 9c793da0f0 Monitors -> Uptime 2024-12-10 14:23:46 +08:00
Alex Holliday 184c0093bd Add monitor count to PageSpeed page 2024-12-10 14:00:45 +08:00
Alex Holliday 1c655dd35a Add matching header to infrastructure page 2024-12-10 14:00:24 +08:00
Gorkem Cetin (BWL) 92494d58f6 Editting -> Editing 2024-12-10 00:23:00 -05:00
Alexander Holliday ad9bda444b Merge pull request #1315 from RishistavRay/feat/aside-viewport-height
Fix Sidebar height to occupy viewport height
2024-12-09 19:45:03 -08:00
Alex Holliday 2226a2f1cf Update jsdocs 2024-12-10 11:32:06 +08:00
Alex Holliday 06cb22e4d8 Use name instead of extra param 2024-12-10 11:30:38 +08:00
Alex Holliday f64e4d04ef Add checkbox name and fieldname props to CustomThreshold 2024-12-10 11:30:25 +08:00
Alex Holliday de149503ae Expose name prop in Checkbox component 2024-12-10 11:29:57 +08:00
Alex Holliday b306a4ac7b Add name to all inputs, use event.target.name instead of passing extra variable 2024-12-10 11:17:17 +08:00
Alexander Holliday 49e57c5c9c Merge pull request #1312 from renovate-bot/renovate/bullmq-5.x
fix(deps): update dependency bullmq to v5.33.0
2024-12-09 16:03:09 -08:00
Alexander Holliday 7757216d5d Merge pull request #1313 from renovate-bot/renovate/emotion-monorepo
fix(deps): update emotion monorepo to v11.14.0
2024-12-09 16:02:56 -08:00
Alexander Holliday 05d87a9339 Merge pull request #1314 from renovate-bot/renovate/react-monorepo
chore(deps): update dependency @types/react-dom to v18.3.3
2024-12-09 16:02:42 -08:00
Alexander Holliday 912b9f102c Merge pull request #1288 from saksham-malhotra-27/feat-design-1275-Current_password_BG_color
feat #1275: design corrected  : Current Password Field BG color
2024-12-09 16:02:18 -08:00
Alexander Holliday 0a72be7965 Merge pull request #1285 from Shaikh-Mudassir/Mudass
Added a gap between label and inputs #1270
2024-12-09 16:01:47 -08:00
Rishistav 1622e48fc0 Fix Sidebar height to occupy viewport height 2024-12-09 16:55:11 -05:00
Gorkem Cetin (BWL) 97b565a6ad Update README.md 2024-12-09 16:20:18 -05:00
Mend Renovate dc81a0e51b chore(deps): update dependency @types/react-dom to v18.3.3 2024-12-09 20:38:33 +00:00
Mend Renovate d649c53249 fix(deps): update emotion monorepo to v11.14.0 2024-12-09 10:06:34 +00:00
Mend Renovate 7e9383d0eb fix(deps): update dependency bullmq to v5.33.0 2024-12-09 10:06:23 +00:00
Alex Holliday f42bdddb7a add deleteHardwareChecksByMonitorId 2024-12-09 14:01:10 +08:00
Alex Holliday 3e21eb0a12 refactor create infrastructure 2024-12-09 13:35:48 +08:00
Alex Holliday dd487c714f simplify custom threshold 2024-12-09 13:35:21 +08:00
Alex Holliday 9b76bd566a Update monitor and infrastrucutre validation to allow for port numbers 2024-12-09 13:35:00 +08:00
Alexander Holliday 1102d77bfa Merge pull request #1301 from bluewave-labs/fix/fe/create-infra-breadcrumbs
fix: links in create infra monitor page, resolves #1294
2024-12-08 17:44:07 -08:00
Alexander Holliday 412518390f Merge pull request #1309 from renovate-bot/renovate/bullmq-5.x
fix(deps): update dependency bullmq to v5.32.0
2024-12-08 17:42:32 -08:00
Mend Renovate d970ecb3a5 fix(deps): update dependency bullmq to v5.32.0 2024-12-08 18:01:50 +00:00
Alexander Holliday 02fd9d9f0b Merge pull request #1299 from renovate-bot/renovate/react-monorepo
chore(deps): update react monorepo
2024-12-07 17:41:09 -08:00
Alexander Holliday 754cdc53bd Merge pull request #1293 from gauravnadkarni/fix/alignment-required-for-jwt-section
fixed issue with the alignment in JWT section
2024-12-07 17:40:52 -08:00
Alexander Holliday cfcd35b282 Merge pull request #1300 from renovate-bot/renovate/bullmq-5.x
fix(deps): update dependency bullmq to v5.31.2
2024-12-07 17:29:15 -08:00
Shaikh Mohd Mudassir Hussain 9e9d561ea0 Comments were removed and spacing was added to all the stacks issue #1270 2024-12-06 01:47:47 -07:00
Alex Holliday f148e2d9ad Refactor Create PageSpeed 2024-12-06 14:25:11 +08:00
Alex Holliday b4cce16931 fix links in create infra monitor page 2024-12-06 13:18:59 +08:00
Mend Renovate 707a13c3c5 fix(deps): update dependency bullmq to v5.31.2 2024-12-06 05:01:27 +00:00
Mend Renovate a912b6752c chore(deps): update react monorepo 2024-12-06 05:01:19 +00:00
Gorkem Cetin (BWL) 958904e6d5 Update README.md 2024-12-05 22:40:40 -05:00
Alexander Holliday 0c8171ae24 Merge pull request #1297 from renovate-bot/renovate/express-4.x-lockfile
fix(deps): update dependency express to v4.21.2
2024-12-05 18:48:01 -08:00
Alexander Holliday af77c7a84b Merge pull request #1298 from renovate-bot/renovate/mongoose-8.x-lockfile
fix(deps): update dependency mongoose to v8.8.4
2024-12-05 18:47:46 -08:00
Mend Renovate a1ee49e718 fix(deps): update dependency mongoose to v8.8.4 2024-12-06 02:00:30 +00:00
Mend Renovate 6f781b9456 fix(deps): update dependency express to v4.21.2 2024-12-06 02:00:22 +00:00
Alexander Holliday 6fd1c0d465 Merge pull request #1296 from renovate-bot/renovate/react-monorepo
chore(deps): update dependency eslint-plugin-react-hooks to v5.1.0
2024-12-05 17:59:36 -08:00
Alexander Holliday 59f16bf76d Merge pull request #1295 from renovate-bot/renovate/material-ui-monorepo
fix(deps): update material-ui monorepo to v7.23.1
2024-12-05 17:59:19 -08:00
Mend Renovate 471e4fcee9 chore(deps): update dependency eslint-plugin-react-hooks to v5.1.0 2024-12-05 18:44:50 +00:00
Mend Renovate b034288c7f fix(deps): update material-ui monorepo to v7.23.1 2024-12-05 18:44:43 +00:00
Gaurav 996dc36986 added a space before proptype 2024-12-05 17:46:55 +05:30
Gaurav d188efa9ee fixed issue with the alignment in JWT section 2024-12-05 17:29:12 +05:30
Alexander Holliday f4c62dd65c Merge pull request #1292 from bluewave-labs/fix/devops/production
fix: Fix API url
2024-12-04 18:59:22 -08:00
Alex Holliday 5478f64b69 Fix API url 2024-12-05 10:24:50 +08:00
Saksham 09a704775a feat: design corrected #1275 : Current Password Field BG color:updated-removed comments 2024-12-05 07:30:28 +05:30
Saksham e19b9858a1 feat: design corrected #1275 : Current Password Field BG color 2024-12-05 07:20:38 +05:30
Shaikh Mohd Mudassir Hussain ecd1282c4d Added a gap between label and inputs #1270 2024-12-04 15:27:02 -07:00
279 changed files with 13788 additions and 11152 deletions

No files matched your search

+1 -1
View File
@@ -1,3 +1,3 @@
Client/node_modules/
Server/node_modules/
*.env
Server/*.env
@@ -1,14 +0,0 @@
## Describe your changes
Provide a brief description of the changes you’ve made and their purpose.
## Issue number
Mention the issue number(s) this PR addresses (e.g., #123).
## Please ensure all items are checked off before requesting a review:
- [ ] I have performed a self-review of my code.
- [ ] I have included the issue # in the PR.
- [ ] I have labelled the PR correctly.
- [ ] My PR is granular and targeted to one specific feature only.
+22
View File
@@ -0,0 +1,22 @@
**(when sending your PR, only remove this line. Do not remove any lines below. Check all the relevant items before sending your PR)**
## Describe your changes
Briefly describe the changes you made and their purpose.
## Issue number
Mention the issue number(s) this PR addresses (e.g., #123).
## Please ensure all items are checked off before requesting a review:
- [ ] I deployed the application locally.
- [ ] I have performed a self-review and testing of my code.
- [ ] I have included the issue # in the PR.
- [ ] I have labelled the PR correctly.
- [ ] The issue I am working on is assigned to me.
- [ ] I didn't use any hardcoded values (otherwise it will not scale, and will make it difficult to maintain consistency across the application).
- [ ] I made sure font sizes, color choices etc are all referenced from the theme.
- [ ] My PR is granular and targeted to one specific feature.
- [ ] I took a screenshot or a video and attached to this PR if there is a UI change.
+3 -3
View File
@@ -1,9 +1,9 @@
# Contributing to Uptime Manager application
# Contributing to Checkmate
First off, thanks for taking the time to contribute! ❤️
All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how the Uptime Manager project handles them. Please make sure to read the relevant section before making your contribution. It will make it much easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉
All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how the Checkmate project handles them. Please make sure to read the relevant section before making your contribution. It will make it much easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉
> And if you like the project, but don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about:
> - Star the project
@@ -77,7 +77,7 @@ Once it's filed:
This section guides you through submitting an enhancement suggestion for the application, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community understand your enhancements and find related suggestions.
- Make sure that you are using the latest version.
- Make sure you run the code locally. The Uptime Manager [documentation](https://bluewavelabs.gitbook.io/uptime-manager) covers it.
- Make sure you run the code locally. The Checkmate [documentation](https://bluewavelabs.gitbook.io/checkmate) covers it.
- Find out if the functionality is already covered, maybe by an individual configuration.
- Perform a [search](/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library.
+643 -488
View File
File diff suppressed because it is too large. Load diff
+8 -8
View File
@@ -14,24 +14,24 @@
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@fontsource/roboto": "^5.0.13",
"@mui/icons-material": "6.1.10",
"@mui/lab": "6.0.0-beta.18",
"@mui/material": "6.1.10",
"@mui/icons-material": "6.4.1",
"@mui/lab": "6.0.0-beta.24",
"@mui/material": "6.4.1",
"@mui/x-charts": "^7.5.1",
"@mui/x-data-grid": "7.23.0",
"@mui/x-date-pickers": "7.23.0",
"@reduxjs/toolkit": "2.4.0",
"@mui/x-data-grid": "7.24.0",
"@mui/x-date-pickers": "7.24.0",
"@reduxjs/toolkit": "2.5.0",
"axios": "^1.7.4",
"dayjs": "1.11.13",
"joi": "17.13.3",
"jwt-decode": "^4.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "9.1.2",
"react-redux": "9.2.0",
"react-router": "^6.23.0",
"react-router-dom": "^6.23.1",
"react-toastify": "^10.0.5",
"recharts": "2.14.1",
"recharts": "2.15.0",
"redux-persist": "6.0.0",
"vite-plugin-svgr": "^4.2.0"
},
+48 -3
View File
@@ -1,4 +1,49 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="32" height="32" fill="#1570EF"/>
<path d="M21.8849 14.3622H19.2287C19.1529 13.9266 19.0133 13.5407 18.8097 13.2045C18.6061 12.8636 18.3527 12.5748 18.0497 12.3381C17.7467 12.1013 17.401 11.9238 17.0128 11.8054C16.6293 11.6823 16.215 11.6207 15.7699 11.6207C14.9792 11.6207 14.2784 11.8196 13.6676 12.2173C13.0568 12.6103 12.5786 13.188 12.233 13.9503C11.8873 14.7079 11.7145 15.6335 11.7145 16.7273C11.7145 17.84 11.8873 18.7775 12.233 19.5398C12.5833 20.2973 13.0616 20.8703 13.6676 21.2585C14.2784 21.642 14.9768 21.8338 15.7628 21.8338C16.1984 21.8338 16.6056 21.777 16.9844 21.6634C17.3679 21.545 17.7112 21.3722 18.0142 21.1449C18.322 20.9176 18.58 20.6383 18.7884 20.3068C19.0014 19.9754 19.1482 19.5966 19.2287 19.1705L21.8849 19.1847C21.7855 19.8759 21.5701 20.5246 21.2386 21.1307C20.9119 21.7367 20.4834 22.2718 19.9531 22.7358C19.4228 23.1951 18.8026 23.5549 18.0923 23.8153C17.3821 24.071 16.5938 24.1989 15.7273 24.1989C14.4489 24.1989 13.3078 23.9029 12.304 23.3111C11.3002 22.7192 10.5095 21.8646 9.93182 20.7472C9.35417 19.6297 9.06534 18.2898 9.06534 16.7273C9.06534 15.16 9.35653 13.8201 9.93892 12.7074C10.5213 11.59 11.3144 10.7353 12.3182 10.1435C13.322 9.55161 14.4583 9.25568 15.7273 9.25568C16.5369 9.25568 17.2898 9.36932 17.9858 9.59659C18.6818 9.82386 19.3021 10.1577 19.8466 10.598C20.3911 11.0336 20.8385 11.5687 21.1889 12.2031C21.544 12.8329 21.776 13.5526 21.8849 14.3622Z" fill="white"/>
<svg width="32" height="32" viewBox="0 0 600 600" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M218.252 79.2667L218.045 78.4143C218.712 77.617 219.642 77.5282 220.615 77.4361C224.829 77.0374 243.307 76.7077 246.676 77.418C248.299 77.76 249.661 78.5248 250.932 79.5734C254.845 82.8003 259.018 88.0302 262.403 91.9284C263.57 93.2734 264.805 95.1623 266.183 96.2584C268.203 99.7185 274.532 101.602 278.167 102.623C278.397 102.688 278.631 102.741 278.863 102.797C274.983 106.185 272.016 110.321 268.272 113.745C272.574 124.188 286.689 184.953 289.397 187.986C290.165 188.212 290.291 188.048 291.045 187.739C290.627 188.446 290.337 189.3 289.588 189.656L288.963 188.533C286.703 188.347 273.044 195.497 269.836 196.808C267.39 197.808 264.851 198.458 262.414 199.405C261.197 196.797 260.413 193.923 259.329 191.244C254.43 179.142 250.091 166.843 245.71 154.552C240.241 139.206 234.226 124.026 228.882 108.642C226.389 101.466 223.549 94.5009 220.953 87.3749C220.026 84.8301 219.72 81.4795 218.252 79.2667Z" fill="#3158B7"/>
<path d="M218.252 79.2667L218.045 78.4143C218.712 77.617 219.642 77.5282 220.615 77.4361C224.829 77.0374 243.307 76.7077 246.676 77.418C248.299 77.76 249.661 78.5248 250.932 79.5734C254.845 82.8003 259.018 88.0302 262.403 91.9284C263.57 93.2734 264.805 95.1623 266.183 96.2584C268.203 99.7185 274.532 101.602 278.167 102.623C278.397 102.688 278.631 102.741 278.863 102.797C274.983 106.185 272.016 110.321 268.272 113.745C266.08 111.774 263.588 110.159 261.208 108.427C256.477 104.982 251.692 101.649 246.906 98.282L233.54 88.9767C228.729 85.5908 223.823 81.3267 218.252 79.2667Z" fill="#73E4FA"/>
<path d="M299.595 19.4005C302.177 17.8183 305.226 16.8946 307.955 15.5556C310.387 14.3626 312.673 12.8604 315.15 11.7619C315.624 11.5519 315.886 11.4697 316.398 11.4844C315.732 13.7848 316.431 26.6622 316.441 30.2652C316.474 42.2192 316.084 54.2432 316.385 66.1867C316.416 67.4343 316.377 68.6301 316.847 69.8026C316.457 79.0631 316.718 88.4086 316.651 97.6808L316.423 172.062C316.376 177.75 316.385 183.438 316.448 189.125C316.469 192.585 316.902 196.888 316.126 200.243C316.055 199.033 315.728 197.334 316.025 196.183C315.86 197.435 315.463 198.762 315.391 200.002C315.283 201.847 315.798 203.719 315.391 205.559C314.431 204.554 313.43 203.906 312.592 202.729C308.228 196.602 310.556 194.07 301.963 190.683C299.704 189.793 293.054 186.88 291.045 187.739C290.291 188.048 290.165 188.212 289.397 187.986C286.689 184.953 272.574 124.188 268.272 113.745C272.016 110.321 274.983 106.185 278.863 102.797C278.631 102.741 278.397 102.688 278.167 102.623C274.532 101.602 268.203 99.7184 266.183 96.2583C269.435 97.3504 272.901 97.5882 276.304 97.6583C280.306 97.7408 283.403 97.3026 286.383 94.3544C288.086 92.669 289.516 90.6569 291.136 88.8839C293.303 86.512 296.041 84.1801 297.633 81.3777C300.012 77.1924 299.115 62.4967 299.219 57.0841C296.538 56.7086 293.912 56.806 291.218 56.6995C288.184 56.5796 285.396 56.0507 283.294 53.6585C280.886 50.9188 280.696 47.3466 281.035 43.8756C282.423 29.6657 295.122 37.239 298.804 33.8184C299.712 32.9739 299.469 21.618 299.595 19.4005Z" fill="#3D7ECD"/>
<path d="M362.288 269.421L362.773 270.062C364.332 274.37 365.193 279.073 366.359 283.516C368.419 291.371 370.61 299.194 372.793 307.015L395.769 388.039L411.304 442.863L415.766 458.213C416.528 460.836 417.569 463.467 417.96 466.175C417.89 466.842 418.03 466.869 417.485 467.324C413.349 466.406 409.11 465.92 404.942 465.147C396.892 463.653 388.881 461.984 380.817 460.552C380.516 459.221 380.489 457.712 380.305 456.347C379.896 453.327 379.359 450.33 378.694 447.356C376.712 438.738 374.16 430.227 372.075 421.626L351.327 334.35C349.071 324.725 347.019 314.981 344.607 305.405C342.561 297.284 340.078 289.201 338.622 280.946C339.912 280.649 340.991 280.35 342.142 279.684C345.072 278.136 348.264 277.064 351.221 275.536C354.9 273.636 358.345 270.649 362.288 269.421Z" fill="#73E4FA"/>
<path d="M316.398 11.4845C316.949 11.6168 317.496 11.7682 318.021 11.982C320.527 13.0022 322.595 14.8975 325.025 16.0401C326.641 16.7993 328.506 17.0278 330.066 17.9189C330.615 18.2326 331.175 18.6401 331.516 19.1833C333.638 22.5617 329.941 33.143 334.144 34.5595C335.071 34.872 336.309 34.7269 337.278 34.6636C342.517 34.3216 346.04 34.3235 349.065 39.1827C350.891 42.117 351.841 45.3207 350.982 48.7564C350.164 52.0314 347.831 55.4635 344.902 57.2399C344.309 57.5993 343.657 57.8308 343.003 58.0488C342.382 58.2541 341.729 58.4831 341.176 58.8402C339.34 60.0257 337.705 61.6472 335.85 62.8819C335.083 63.3916 333.536 63.9343 333.114 64.7163C333.015 64.8989 332.938 65.0921 332.849 65.2797C331.994 71.778 330.942 74.7954 335.242 80.4525C338.105 84.2196 345.676 93.407 349.248 96.0711C350.329 96.8768 351.548 97.3899 352.858 97.674C354.638 97.679 356.567 97.8353 358.319 97.4932C366.986 95.8002 372.945 85.6789 379.155 80.1412C380.557 78.8916 382.117 77.8947 383.974 77.4926C387.385 76.754 391.296 77.2429 394.773 77.2627C400.744 77.2966 406.631 77.1509 412.603 77.4939C413.066 77.8038 413.08 77.6663 413.16 78.1487C413.385 79.5036 412.832 81.0938 412.41 82.377C409.805 90.2864 406.49 98.1119 403.607 105.936L387.525 150.29C383.617 161.269 379.569 172.195 375.381 183.07C373.017 189.241 370.826 195.663 367.756 201.518C366.906 200.846 366.105 200.384 365.115 199.946C357.118 196.547 349.468 191.947 341.757 187.926C341.211 187.423 341.102 187.207 340.301 187.318C338.422 187.579 336.346 188.419 334.502 188.929C330.434 190.055 321.796 191.764 319.469 195.652C318.572 197.151 317.692 200.22 316.337 201.245C316.225 200.886 316.145 200.621 316.126 200.243C316.902 196.888 316.469 192.585 316.448 189.125C316.385 183.438 316.377 177.75 316.423 172.062L316.651 97.6809C316.718 88.4087 316.457 79.0632 316.847 69.8027C316.377 68.6302 316.416 67.4344 316.385 66.1868C316.084 54.2433 316.474 42.2193 316.441 30.2653C316.431 26.6623 315.732 13.7849 316.398 11.4845Z" fill="#73E4FA"/>
<path d="M332.127 57.4141C335.751 57.5106 339.378 57.8905 343.003 58.0488C342.382 58.2541 341.729 58.4831 341.176 58.8402C339.34 60.0257 337.705 61.6472 335.85 62.8819C335.083 63.3916 333.536 63.9343 333.114 64.7163C333.015 64.8989 332.938 65.0921 332.849 65.2797C332.684 62.6459 332.422 60.0356 332.127 57.4141Z" fill="black"/>
<path d="M352.858 97.674C354.638 97.679 356.567 97.8353 358.319 97.4932C366.986 95.8002 372.945 85.6789 379.155 80.1412C380.557 78.8916 382.117 77.8947 383.974 77.4926C387.385 76.754 391.296 77.2429 394.773 77.2627C400.744 77.2966 406.631 77.1509 412.603 77.4939C412.123 77.5931 411.776 77.6589 411.35 77.9133C409.284 79.1482 407.188 81.3404 405.081 82.7802C394.559 89.9716 384.283 97.4873 373.787 104.715C371.042 106.605 368.398 108.696 365.587 110.481C363.016 106.042 360.879 102.667 356.684 99.576C355.908 99.004 354.963 98.1848 354.042 97.8844C353.777 97.7979 353.467 97.7915 353.199 97.7482C353.084 97.7297 352.972 97.6988 352.858 97.674Z" fill="#4F91E0"/>
<path d="M316.847 69.8027C321.242 72.4105 326.84 79.7356 330.518 83.5726C335.063 88.3145 340.305 92.4625 345.078 96.9857C348.754 100.47 359.102 112.197 362.531 113.979C362.884 116.416 347.596 173.131 345.411 179.979C344.657 182.345 343.764 185.35 342.254 187.338C342.099 187.543 341.927 187.734 341.757 187.926C341.211 187.423 341.102 187.207 340.301 187.318C338.422 187.579 336.346 188.419 334.502 188.929C330.434 190.055 321.796 191.764 319.469 195.652C318.572 197.151 317.692 200.22 316.337 201.245C316.225 200.886 316.145 200.621 316.126 200.243C316.902 196.888 316.469 192.585 316.448 189.125C316.385 183.438 316.376 177.75 316.423 172.062L316.651 97.6809C316.718 88.4087 316.457 79.0632 316.847 69.8027Z" fill="#5BBFFC"/>
<path d="M316.126 200.243C316.145 200.621 316.225 200.886 316.337 201.245C317.692 200.22 318.572 197.151 319.469 195.652C321.796 191.764 330.434 190.055 334.502 188.929C336.346 188.419 338.422 187.579 340.301 187.318C341.102 187.207 341.211 187.423 341.757 187.926C349.468 191.947 357.118 196.547 365.115 199.946C366.105 200.384 366.906 200.846 367.756 201.518C367.27 202.637 366.763 203.747 366.234 204.848C365.063 207.595 364.344 210.137 363.459 212.952C367.613 213.364 371.797 213.297 375.966 213.194C386.171 213.341 399.134 211.56 407.006 219.438C408.262 220.694 409.583 222.077 410.315 223.707C411.447 228.7 410.939 234.09 410.692 239.173C410.661 239.285 410.628 239.397 410.599 239.511C410.317 240.612 410.264 241.816 409.76 242.846C408.269 245.89 403.918 248.67 401.099 250.509L397.101 250.535C392.229 251.156 386.936 250.764 382.02 250.742C380.258 252.242 379.572 253.4 379.015 255.621C375.623 257.067 372.349 258.186 368.836 259.31C366.357 260.996 362.191 264.345 361.869 267.495C361.803 268.145 361.893 268.578 362.175 269.155L362.288 269.421C358.345 270.649 354.9 273.636 351.221 275.536C348.264 277.064 345.072 278.136 342.142 279.684C340.991 280.35 339.912 280.649 338.622 280.946L338.374 280.965L337.631 281.175C336.996 280.978 336.122 280.707 335.64 280.22C335.013 279.586 332.112 273.818 330.826 272.131C327.237 267.42 322.458 264.909 316.638 264.129L316.192 264.037C314.993 265.535 316.083 270.149 315.579 272.213C315.076 269.916 316.559 267.352 315.272 265.334C314.562 264.951 314 264.949 313.234 265.15C310.643 265.828 308.244 267.144 305.601 267.663L305.359 267.78C304.526 268.174 303.726 268.445 302.836 268.679L302.422 268.516C302.97 267.994 303.581 267.707 304.249 267.371C303.346 267.089 302.592 267.405 301.795 267.828C300.798 268.358 299.724 269.109 298.638 269.418C296.45 270.04 293.748 268.782 292.771 271.723C291.539 275.429 292.836 276.598 289.342 279.245L289.264 279.106L289.746 279.665C289.922 279.642 290.098 279.617 290.274 279.596C290.788 279.534 291.072 279.562 291.552 279.784C283.761 280.886 276.08 271.943 269.481 268.727C269.532 267.685 269.414 267.036 268.848 266.155C266.793 262.951 261.551 259.21 258.011 257.869C256.773 257.4 255.382 257.17 254.087 256.899C250.725 256.497 247.482 255.358 244.173 254.665C241.651 254.137 239.029 253.962 236.466 253.728C236.969 252.907 236.631 251.749 236.978 250.742C234.444 250.731 231.884 250.523 229.351 250.403C226.903 249.086 224.175 246.485 222.644 244.15C220.166 240.368 220.112 229.455 221.195 225.05C221.962 221.93 223.946 219.508 226.648 217.845C227.525 217.021 228.391 216.267 229.547 215.874C232.606 214.832 236.233 214.827 239.433 214.547C243.327 214.206 247.238 213.733 251.141 213.538C256.502 213.269 261.883 213.36 267.25 213.257C267.293 213.167 267.345 213.08 267.378 212.986C268.103 210.948 263.421 201.839 262.414 199.405C264.851 198.458 267.39 197.808 269.836 196.808C273.044 195.497 286.703 188.347 288.963 188.533L289.588 189.656C290.337 189.3 290.627 188.446 291.045 187.739C293.054 186.88 299.704 189.793 301.963 190.683C310.556 194.07 308.228 196.602 312.592 202.729C313.43 203.906 314.431 204.554 315.391 205.559C315.798 203.719 315.283 201.847 315.391 200.002C315.463 198.762 315.86 197.435 316.025 196.183C315.728 197.334 316.055 199.033 316.126 200.243Z" fill="#73E4FA"/>
<path d="M226.648 217.845C227.525 217.021 228.391 216.267 229.547 215.874C232.606 214.832 236.233 214.827 239.433 214.547C243.327 214.206 247.238 213.733 251.141 213.538C256.502 213.269 261.883 213.36 267.25 213.257C273.5 213.971 279.834 213.445 286.108 213.605C286.028 213.626 285.948 213.648 285.867 213.666C283.762 214.153 281.367 213.997 279.208 214.037C272.251 214.165 265.326 213.83 258.375 213.88C253.031 213.919 247.886 214.67 242.578 215.017C240.895 215.127 236.024 214.805 235.01 216.105C234.814 216.356 234.778 216.499 234.672 216.773C235.26 217.358 235.736 217.319 236.534 217.341C240.179 217.439 243.921 217.212 247.585 217.238C254.057 217.283 260.516 217.54 266.99 217.551C270.978 217.557 278.548 217.235 282.283 217.919C273.611 218.558 264.721 217.948 256.023 218.03C249.71 217.873 243.396 217.847 237.081 217.954C233.732 217.991 229.946 218.398 226.648 217.845Z" fill="#4F91E0"/>
<path d="M321 196.5C321.019 196.878 320.86 194.141 320.972 194.5C322.327 193.475 318.572 197.151 319.469 195.652C321.796 191.764 330.434 190.055 334.502 188.929C336.346 188.419 338.422 187.579 340.301 187.318C341.102 187.207 341.211 187.423 341.757 187.926C349.468 191.947 357.118 196.547 365.115 199.946C366.105 200.384 366.906 200.846 367.756 201.518C367.27 202.637 366.763 203.747 366.234 204.848C365.063 207.595 364.344 210.137 363.459 212.952L361.76 212.951C346.809 213.807 331.759 213.014 316.787 213.074L315.391 205.559C315.798 203.719 315.283 201.847 315.391 200.002C315.463 198.762 315.835 196.752 316 195.5C315.5 195 323.374 191.757 321 196.5Z" fill="#3158B7"/>
<path d="M291.045 187.739C293.054 186.88 299.704 189.793 301.963 190.683C310.556 194.07 305.5 191.5 312.5 194.5C314 195 315.54 193.995 316.5 195L316.787 213.074C306.586 212.996 296.304 213.296 286.108 213.605C279.834 213.445 273.5 213.971 267.25 213.257C267.293 213.167 267.345 213.08 267.378 212.986C268.103 210.948 263.421 201.839 262.414 199.405C264.851 198.458 267.39 197.808 269.836 196.808C273.044 195.497 286.703 188.347 288.963 188.533L289.5 188.5C290.249 188.144 290.627 188.446 291.045 187.739Z" fill="#15337C"/>
<path d="M316.158 217.961C333.626 218.066 351.372 216.648 368.745 218.663C369.093 229.235 368.662 239.88 368.76 250.467L387.46 250.44C390.609 250.437 393.982 250.193 397.101 250.535C392.229 251.156 386.936 250.764 382.02 250.742L316.217 250.777C316.034 247.101 316.2 243.334 316.205 239.649C316.23 232.42 316.215 225.19 316.158 217.961Z" fill="#5BBFFC"/>
<path d="M229.351 250.403C226.903 249.086 224.175 246.485 222.644 244.15C220.166 240.368 220.112 229.455 221.195 225.05C221.962 221.93 223.946 219.508 226.648 217.845C229.946 218.398 233.732 217.991 237.081 217.954C243.396 217.847 249.71 217.873 256.023 218.03C264.721 217.948 273.611 218.558 282.283 217.919L316.158 217.961C316.215 225.19 316.23 232.42 316.205 239.649C316.2 243.334 316.034 247.101 316.217 250.777L382.02 250.742C380.258 252.242 379.572 253.4 379.015 255.621C375.623 257.067 372.349 258.186 368.836 259.31C366.357 260.996 362.191 264.345 361.869 267.495C361.803 268.145 361.893 268.578 362.175 269.155L362.288 269.421C358.345 270.649 354.9 273.636 351.221 275.536C348.264 277.064 345.072 278.136 342.142 279.684C340.991 280.35 339.912 280.649 338.622 280.946L338.374 280.965L337.631 281.175C336.996 280.978 336.122 280.707 335.64 280.22C335.013 279.586 332.112 273.818 330.826 272.131C327.237 267.42 322.458 264.909 316.638 264.129L316.192 264.037C314.993 265.535 316.083 270.149 315.579 272.213C315.076 269.916 316.559 267.352 315.272 265.334C314.562 264.951 314 264.949 313.234 265.15C310.643 265.828 308.244 267.144 305.601 267.663L305.359 267.78C304.526 268.174 303.726 268.445 302.836 268.679L302.422 268.516C302.97 267.994 303.581 267.707 304.249 267.371C303.346 267.089 302.592 267.405 301.795 267.828C300.798 268.358 299.724 269.109 298.638 269.418C296.45 270.04 293.748 268.782 292.771 271.723C291.539 275.429 292.836 276.598 289.342 279.245L289.264 279.106L289.746 279.665C289.922 279.642 290.098 279.617 290.274 279.596C290.788 279.534 291.072 279.562 291.552 279.784C283.761 280.886 276.08 271.943 269.481 268.727C269.532 267.685 269.414 267.036 268.848 266.155C266.793 262.951 261.551 259.21 258.011 257.869C256.773 257.4 255.382 257.17 254.087 256.899C250.725 256.497 247.482 255.358 244.173 254.665C241.651 254.137 239.029 253.962 236.466 253.728C236.969 252.907 236.631 251.749 236.978 250.742C234.444 250.731 231.884 250.523 229.351 250.403Z" fill="#3D7ECD"/>
<path d="M229.5 250.5C227.052 249.183 224.175 246.485 222.644 244.15C220.166 240.368 220.112 229.455 221.195 225.05C221.962 221.93 223.946 219.508 226.648 217.845C229.946 218.398 233.732 217.991 237.081 217.954C243.396 217.847 249.71 217.873 256.023 218.03C258.057 218.243 260.081 218.239 262.124 218.245C262.036 228.982 261.779 239.713 261.935 250.452C267.117 250.445 282.096 249.873 286.095 250.724C281.121 250.989 276.017 250.759 271.029 250.75C261.584 250.723 252.14 250.748 242.696 250.824C240.816 250.676 238.867 250.759 236.978 250.742C234.444 250.731 232.033 250.62 229.5 250.5Z" fill="#3158B7"/>
<path d="M242.696 250.824C252.14 250.748 261.584 250.723 271.029 250.75C276.017 250.759 281.121 250.989 286.095 250.724C296.126 250.796 306.188 250.976 316.217 250.777L382.02 250.742C380.258 252.242 379.572 253.4 379.015 255.621C375.623 257.067 372.349 258.186 368.836 259.31C366.357 260.996 362.191 264.345 361.869 267.495C361.803 268.145 361.893 268.578 362.175 269.155L362.288 269.421C358.345 270.649 354.9 273.636 351.221 275.536C348.264 277.064 345.072 278.136 342.142 279.684C340.991 280.35 339.912 280.649 338.622 280.946L338.374 280.965L337.631 281.175C336.996 280.978 336.122 280.707 335.64 280.22C335.013 279.586 332.112 273.818 330.826 272.131C327.237 267.42 322.458 264.909 316.638 264.129L316.192 264.037C314.993 265.535 316.083 270.149 315.579 272.213C315.076 269.916 316.559 267.352 315.272 265.334C314.562 264.951 314 264.949 313.234 265.15C310.643 265.828 308.244 267.144 305.601 267.663L305.359 267.78C304.526 268.174 303.726 268.445 302.836 268.679L302.422 268.516C302.97 267.994 303.581 267.707 304.249 267.371C303.346 267.089 302.592 267.405 301.795 267.828C300.798 268.358 299.724 269.109 298.638 269.418C296.45 270.04 293.748 268.782 292.771 271.723C291.539 275.429 292.836 276.598 289.342 279.245L289.264 279.106L289.746 279.665C289.922 279.642 290.098 279.617 290.274 279.596C290.788 279.534 291.072 279.562 291.552 279.784C283.761 280.886 276.08 271.943 269.481 268.727C269.532 267.685 269.414 267.036 268.848 266.155C266.793 262.951 261.551 259.21 258.011 257.869C256.773 257.4 255.382 257.17 254.087 256.899C250.725 256.497 247.482 255.358 244.173 254.665C241.651 254.137 239.029 253.962 236.466 253.728C236.969 252.907 236.631 251.749 236.978 250.742C238.867 250.759 240.816 250.676 242.696 250.824Z" fill="url(#paint0_linear_4222_6997)"/>
<path d="M236.978 250.742C238.867 250.759 240.816 250.676 242.696 250.824C242.881 250.874 243.066 250.928 243.252 250.974C245.298 251.479 247.92 251.212 250.051 251.224C248.443 251.53 247.073 251.291 245.943 252.702C245.85 253.55 245.913 253.633 246.31 254.399C248.336 255.702 252.778 255.161 254.087 256.899C250.725 256.497 247.482 255.358 244.173 254.665C241.651 254.137 239.029 253.962 236.466 253.728C236.969 252.907 236.631 251.749 236.978 250.742Z" fill="#15337C"/>
<path d="M315.839 263.948C315.537 262.501 315.708 261.013 315.229 259.629C314.46 258.966 313.976 259.049 312.984 258.933C313.102 258.884 313.213 258.814 313.338 258.787C315.769 258.246 325.519 258.983 328.801 258.983L357.499 259.017C359.803 259.006 366.156 258.505 368.097 258.96C368.485 259.051 368.546 259.119 368.836 259.31C366.357 260.996 362.191 264.345 361.869 267.495C361.803 268.145 361.893 268.578 362.175 269.155L362.288 269.421C358.345 270.649 354.9 273.636 351.221 275.536C348.264 277.064 345.072 278.136 342.142 279.684C340.991 280.35 339.912 280.649 338.622 280.946L338.374 280.965L337.631 281.175C336.996 280.978 336.122 280.707 335.64 280.22C335.013 279.586 332.112 273.818 330.826 272.131C327.237 267.42 322.458 264.909 316.638 264.129L316.192 264.037C314.993 265.535 316.083 270.149 315.579 272.213C315.076 269.916 316.559 267.352 315.272 265.334C314.562 264.951 314 264.949 313.234 265.15C310.643 265.828 308.244 267.144 305.601 267.663C307.941 265.704 312.848 264.583 315.839 263.948Z" fill="url(#paint1_linear_4222_6997)"/>
<path d="M334.406 268.36C340.718 267.927 347.148 267.514 353.456 268.191C355.699 268.431 360.368 269.793 362.175 269.155L362.288 269.421C358.345 270.649 354.9 273.636 351.221 275.536C348.264 277.064 345.072 278.136 342.142 279.684C340.991 280.35 339.912 280.649 338.622 280.946L338.374 280.965C338.013 279.917 337.843 278.75 337.605 277.665C336.155 274.684 336.233 271.392 334.406 268.36Z" fill="#4F91E0"/>
<path d="M337.605 277.665C338.101 277.244 338.105 277.263 338.759 277.253C340.026 277.925 341.077 278.732 342.142 279.684C340.991 280.35 339.912 280.649 338.622 280.946L338.374 280.965C338.013 279.917 337.843 278.75 337.605 277.665Z" fill="#5BBFFC"/>
<path d="M162.994 506.672C163.51 506.136 164.063 505.686 164.643 505.225C167.325 506.032 178.659 505.485 182.142 505.49L221.176 505.555C226.755 505.582 232.376 505.443 237.949 505.628C269.102 506.183 300.329 505.731 331.491 505.742L372.011 505.764C380.199 505.77 388.457 505.962 396.635 505.618C396.442 517.711 396.022 529.884 396.433 541.975C409.395 542.082 422.357 542.091 435.32 542.002C441.062 542.005 447.41 541.469 453.076 542.23C457.801 542.254 462.724 542.135 467.419 542.679C465.179 543.78 463.234 548.572 462.043 550.796V551.298L462.794 551.648C454.827 551.784 446.872 552.172 438.929 552.813C436.511 552.995 434.06 552.553 431.644 552.413C428.22 552.214 424.779 552.237 421.352 552.25C402.933 552.322 384.531 552.526 366.11 552.346L227.563 552.274C215.642 552.277 203.672 552.344 191.757 552.13C182.178 551.958 172.491 551.389 162.923 551.866C164.053 549.046 163.345 544.905 162.658 542.024C162.018 536.512 162.277 530.737 162.288 525.186C162.299 519.081 161.931 512.699 162.994 506.672Z" fill="#15337C"/>
<path d="M162.994 506.672C163.51 506.136 164.063 505.686 164.643 505.225C167.325 506.032 178.659 505.485 182.142 505.49L221.176 505.555C226.755 505.582 232.376 505.443 237.949 505.628C237.694 517.722 237.219 529.932 237.571 542.021L396.433 541.975C409.395 542.082 422.357 542.091 435.32 542.002C441.062 542.005 447.41 541.469 453.076 542.23C445.432 542.447 437.752 542.287 430.104 542.288L384.066 542.238L236.646 542.27L181.968 542.237C175.536 542.231 169.083 541.914 162.658 542.024C162.018 536.512 162.277 530.737 162.288 525.186C162.299 519.081 161.931 512.699 162.994 506.672Z" fill="#3158B7"/>
<path d="M237.949 505.628C269.102 506.183 300.329 505.731 331.491 505.742L372.011 505.764C380.199 505.77 388.457 505.962 396.635 505.618C396.442 517.711 396.022 529.884 396.433 541.975L237.571 542.021C237.219 529.932 237.694 517.722 237.949 505.628Z" fill="#3D7ECD"/>
<path d="M316.173 450.214L316.356 450.171C327.599 451.012 338.68 453.515 349.747 455.526C360.085 457.318 370.442 458.993 380.817 460.552C388.881 461.984 396.892 463.653 404.942 465.147C409.11 465.92 413.349 466.406 417.485 467.324C418.03 466.869 417.89 466.842 417.96 466.175C418.868 471.139 419.01 474.417 418.882 479.505C424.212 480.991 428.904 484.71 433.752 487.295L467.07 504.914C467.988 505.502 468.749 505.944 468.955 507.111C469.514 510.282 469.469 539.486 468.739 541.824C468.292 542.267 468.021 542.487 467.419 542.679C462.724 542.135 457.801 542.254 453.076 542.23C447.41 541.469 441.062 542.005 435.32 542.002C422.357 542.091 409.395 542.082 396.433 541.975C396.022 529.884 396.442 517.711 396.635 505.618C388.457 505.962 380.199 505.77 372.011 505.764L331.491 505.742C300.329 505.731 269.102 506.183 237.949 505.628C232.376 505.443 226.755 505.582 221.176 505.555L182.142 505.49C178.659 505.485 167.325 506.032 164.643 505.225C166.684 503.613 169.31 502.543 171.632 501.378C176.276 499.048 181.007 496.831 185.604 494.419C192.121 490.999 198.566 487.372 204.999 483.795C207.005 482.679 208.919 481.328 210.961 480.297C211.664 479.943 212.437 479.729 213.192 479.519C213.447 477.939 213.28 476.121 213.292 474.511C213.314 471.755 213.495 469.182 213.961 466.462C214.147 466.81 214.01 466.597 214.452 467.044C227.245 465.759 239.827 462.451 252.483 460.143C260.461 458.819 268.511 457.946 276.475 456.519L300.283 452.407C305.401 451.522 310.98 450.069 316.173 450.214Z" fill="#73E4FA"/>
<path d="M316.173 450.214L316.356 450.171C327.599 451.012 338.68 453.515 349.747 455.526C360.085 457.318 370.442 458.993 380.817 460.552C388.881 461.984 396.892 463.653 404.942 465.147C409.11 465.92 413.349 466.406 417.485 467.324C418.03 466.869 417.89 466.842 417.96 466.175C418.868 471.139 419.01 474.417 418.882 479.505C407.542 478.716 395.89 479.365 384.514 479.436C370.443 479.525 356.374 479.202 342.302 479.225L326.141 479.166C322.94 479.164 319.619 479.396 316.441 479.069C316.286 469.43 316.022 459.862 316.173 450.214Z" fill="#3D7ECD"/>
<path d="M467.07 504.914C467.988 505.502 468.749 505.944 468.955 507.111C469.514 510.282 469.469 539.486 468.739 541.824C468.292 542.267 468.021 542.487 467.419 542.679C462.724 542.135 457.801 542.254 453.076 542.23C447.41 541.469 441.062 542.005 435.32 542.002C422.357 542.091 409.395 542.082 396.433 541.975C396.022 529.884 396.442 517.711 396.635 505.618C399.774 505.933 403.063 505.785 406.221 505.786L422.299 505.784C435.31 505.785 448.476 506.228 461.464 505.68C463.339 505.6 465.314 505.634 467.07 504.914Z" fill="#5BBFFC"/>
<path d="M252.483 460.143C260.461 458.819 268.511 457.946 276.475 456.519L300.283 452.407C305.401 451.522 310.98 450.069 316.173 450.214C316.022 459.862 316.286 469.43 316.441 479.069C313.41 479.108 310.372 479.425 307.371 479.847C292.468 483.6 277.556 488.899 263.025 493.906C257.708 495.738 251.924 497.128 246.801 499.408C243.673 500.8 240.545 502.657 238.239 505.216L351.838 505.253L382.181 505.431C386.77 505.429 392.191 504.724 396.635 505.618C388.457 505.962 380.199 505.77 372.011 505.764L331.491 505.742C300.329 505.731 269.102 506.183 237.949 505.628C232.376 505.443 226.755 505.582 221.176 505.555L182.142 505.49C178.659 505.485 167.325 506.032 164.643 505.225C166.684 503.613 169.31 502.543 171.632 501.378C176.276 499.048 181.007 496.831 185.604 494.419C192.121 490.999 198.566 487.372 204.999 483.795C207.005 482.679 208.919 481.328 210.961 480.297C211.664 479.943 212.437 479.729 213.192 479.519C213.447 477.939 213.28 476.121 213.292 474.511C213.314 471.755 213.495 469.182 213.961 466.462C214.147 466.81 214.01 466.597 214.452 467.044C227.245 465.759 239.827 462.451 252.483 460.143Z" fill="url(#paint2_linear_4222_6997)"/>
<path d="M252.483 460.143C260.461 458.819 268.511 457.946 276.475 456.519L300.283 452.407C305.401 451.522 310.98 450.069 316.173 450.214C316.022 459.862 316.286 469.43 316.441 479.069C313.41 479.108 310.372 479.425 307.371 479.847C280.73 478.905 253.933 479.206 227.271 479.298C222.578 479.314 217.879 479.538 213.192 479.519C213.447 477.939 213.28 476.121 213.292 474.511C213.314 471.755 213.495 469.182 213.961 466.462C214.147 466.81 214.01 466.597 214.452 467.044C227.245 465.759 239.827 462.451 252.483 460.143Z" fill="#15337C"/>
<path d="M305.601 267.663C308.244 267.144 310.643 265.828 313.234 265.15C314 264.949 314.562 264.951 315.272 265.334C316.559 267.352 315.076 269.916 315.579 272.213C316.083 270.149 314.993 265.535 316.192 264.037L316.638 264.129C322.458 264.909 327.237 267.42 330.826 272.131C332.112 273.818 335.013 279.586 335.64 280.22C336.122 280.707 336.996 280.978 337.631 281.175L338.374 280.965L338.622 280.946C340.078 289.201 342.561 297.284 344.607 305.405C347.019 314.981 349.071 324.725 351.327 334.35L372.075 421.626C374.16 430.227 376.712 438.738 378.694 447.356C379.359 450.33 379.896 453.327 380.305 456.347C380.489 457.712 380.516 459.221 380.817 460.552C370.442 458.993 360.085 457.318 349.747 455.526C338.68 453.515 327.599 451.012 316.356 450.171L316.173 450.214C310.98 450.069 305.401 451.522 300.283 452.407L276.475 456.519C268.511 457.946 260.461 458.819 252.483 460.143C239.827 462.451 227.245 465.759 214.452 467.044C214.01 466.597 214.147 466.81 213.961 466.462C217.335 455.731 219.76 444.734 222.73 433.889L237.945 379.405L269.481 268.727C276.08 271.943 283.761 280.886 291.552 279.784C291.072 279.562 290.788 279.534 290.274 279.596C290.098 279.617 289.922 279.642 289.746 279.665L289.264 279.106L289.342 279.245C292.836 276.598 291.539 275.429 292.771 271.723C293.748 268.782 296.45 270.04 298.638 269.418C299.724 269.109 300.798 268.358 301.795 267.828C302.592 267.405 303.346 267.089 304.249 267.371C303.581 267.707 302.97 267.994 302.422 268.516L302.836 268.679C303.726 268.445 304.526 268.174 305.359 267.78L305.601 267.663Z" fill="#3D7ECD"/>
<path d="M213.961 466.462C217.335 455.731 219.76 444.734 222.73 433.889L237.945 379.405L269.481 268.727C276.08 271.943 283.761 280.886 291.552 279.784C294.318 279.786 297.526 280.103 300.239 279.671C302.32 279.34 293.35 281.165 295.5 281C292.765 282.129 294.815 282.321 291.94 282.461C291.8 282.468 291.66 282.481 291.519 282.489C291.435 282.494 291.35 282.496 291.266 282.5C290.329 287.67 289.473 292.882 288.349 298.015L265.21 399.097L256.243 437.602C255.364 441.302 250.844 457.078 251.4 459.744C251.766 459.902 252.097 460.037 252.483 460.143C239.827 462.451 227.245 465.759 214.452 467.044C214.01 466.597 214.147 466.81 213.961 466.462Z" fill="#3158B7"/>
<path d="M316.638 264.129C322.458 264.909 327.237 267.42 330.826 272.131C332.112 273.818 335.013 279.586 335.64 280.22C336.122 280.707 336.996 280.978 337.631 281.175L338.374 280.965L338.622 280.946C340.078 289.201 342.561 297.284 344.607 305.405C347.019 314.981 349.071 324.725 351.327 334.35L372.075 421.626C374.16 430.227 376.712 438.738 378.694 447.356C379.359 450.33 379.896 453.327 380.305 456.347C380.489 457.712 380.516 459.221 380.817 460.552C370.442 458.993 360.085 457.318 349.747 455.526C338.68 453.515 327.599 451.012 316.356 450.171L316.124 267.318C316.132 266.225 316.016 265.039 316.638 264.129Z" fill="#5BBFFC"/>
<path d="M162.602 542.012C222.681 542.667 282.903 542.133 343 542.146L421.144 542.172C436.934 542.179 452.86 542.406 468.631 542C468.259 556.284 468.631 576.5 466.168 585H164.5C162.602 574 162.11 556.297 162.602 542.012Z" fill="#2E57AB"/>
<path d="M263.193 261.724L235.532 253.517L232.5 252.5L229.5 250.5L402 250L377.5 261.724L364 275.5L339.134 284H289.345L269.731 271.69L263.193 261.724Z" fill="#2950B6"/>
<defs>
<linearGradient id="paint0_linear_4222_6997" x1="313.33" y1="251.065" x2="313.326" y2="279.202" gradientUnits="userSpaceOnUse">
<stop stop-color="#122869"/>
<stop offset="1" stop-color="#2950B6"/>
</linearGradient>
<linearGradient id="paint1_linear_4222_6997" x1="342.025" y1="256.698" x2="339.592" y2="280.614" gradientUnits="userSpaceOnUse">
<stop stop-color="#3052A7"/>
<stop offset="1" stop-color="#2671CE"/>
</linearGradient>
<linearGradient id="paint2_linear_4222_6997" x1="233.914" y1="479.018" x2="235.081" y2="504.885" gradientUnits="userSpaceOnUse">
<stop stop-color="#486DC9"/>
<stop offset="1" stop-color="#599BEA"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 29 KiB

+4 -195
View File
@@ -1,37 +1,8 @@
import { useEffect } from "react";
import { Routes, Route, Navigate } from "react-router-dom";
import { useSelector } from "react-redux";
import { useDispatch } from "react-redux";
import "react-toastify/dist/ReactToastify.css";
import { ToastContainer } from "react-toastify";
import NotFound from "./Pages/NotFound";
import Login from "./Pages/Auth/Login";
import Register from "./Pages/Auth/Register/Register";
import Account from "./Pages/Account";
import Monitors from "./Pages/Monitors/Home";
import CreateMonitor from "./Pages/Monitors/CreateMonitor";
import CreateInfrastructureMonitor from "./Pages/Infrastructure/CreateMonitor";
import Incidents from "./Pages/Incidents";
import Status from "./Pages/Status";
import Integrations from "./Pages/Integrations";
import Settings from "./Pages/Settings";
import ForgotPassword from "./Pages/Auth/ForgotPassword";
import CheckEmail from "./Pages/Auth/CheckEmail";
import SetNewPassword from "./Pages/Auth/SetNewPassword";
import NewPasswordConfirmed from "./Pages/Auth/NewPasswordConfirmed";
import ProtectedRoute from "./Components/ProtectedRoute";
import Details from "./Pages/Monitors/Details";
import AdvancedSettings from "./Pages/AdvancedSettings";
import Maintenance from "./Pages/Maintenance";
import Configure from "./Pages/Monitors/Configure";
import PageSpeed from "./Pages/PageSpeed";
import CreatePageSpeed from "./Pages/PageSpeed/CreatePageSpeed";
import CreateNewMaintenanceWindow from "./Pages/Maintenance/CreateMaintenance";
import PageSpeedDetails from "./Pages/PageSpeed/Details";
import PageSpeedConfigure from "./Pages/PageSpeed/Configure";
import HomeLayout from "./Components/Layouts/HomeLayout";
import withAdminCheck from "./Components/HOC/withAdminCheck";
import withAdminProp from "./Components/HOC/withAdminProp";
import { ThemeProvider } from "@emotion/react";
import lightTheme from "./Utils/Theme/lightTheme";
import darkTheme from "./Utils/Theme/darkTheme";
@@ -39,18 +10,9 @@ import { CssBaseline, GlobalStyles } from "@mui/material";
import { getAppSettings } from "./Features/Settings/settingsSlice";
import { logger } from "./Utils/Logger"; // Import the logger
import { networkService } from "./main";
import { Infrastructure } from "./Pages/Infrastructure";
import InfrastructureDetails from "./Pages/Infrastructure/Details";
import { Routes } from "./Routes";
function App() {
const AdminCheckedRegister = withAdminCheck(Register);
const MonitorsWithAdminProp = withAdminProp(Monitors);
const MonitorDetailsWithAdminProp = withAdminProp(Details);
const PageSpeedWithAdminProp = withAdminProp(PageSpeed);
const PageSpeedDetailsWithAdminProp = withAdminProp(PageSpeedDetails);
const MaintenanceWithAdminProp = withAdminProp(Maintenance);
const SettingsWithAdminProp = withAdminProp(Settings);
const AdvancedSettingsWithAdminProp = withAdminProp(AdvancedSettings);
const InfrastructureDetailsWithAdminProp = withAdminProp(InfrastructureDetails);
const mode = useSelector((state) => state.ui.mode);
const { authToken } = useSelector((state) => state.auth);
const dispatch = useDispatch();
@@ -78,165 +40,12 @@ function App() {
return {
body: {
backgroundImage: `radial-gradient(circle, ${palette.gradient.color1}, ${palette.gradient.color2}, ${palette.gradient.color3}, ${palette.gradient.color4}, ${palette.gradient.color5})`,
color: palette.primary.contrastText,
},
};
}}
/>
{/* Extract Routes to Routes */}
<Routes>
<Route
exact
path="/"
element={<HomeLayout />}
>
<Route
exact
path="/"
element={<Navigate to="/monitors" />}
/>
<Route
path="/monitors"
element={<ProtectedRoute Component={MonitorsWithAdminProp} />}
/>
<Route
path="/monitors/create/:monitorId?"
element={<ProtectedRoute Component={CreateMonitor} />}
/>
<Route
path="/monitors/:monitorId/"
element={<ProtectedRoute Component={MonitorDetailsWithAdminProp} />}
/>
<Route
path="/monitors/configure/:monitorId/"
element={<ProtectedRoute Component={Configure} />}
/>
<Route
path="pagespeed"
element={<ProtectedRoute Component={PageSpeedWithAdminProp} />}
/>
<Route
path="pagespeed/create"
element={<ProtectedRoute Component={CreatePageSpeed} />}
/>
<Route
path="pagespeed/:monitorId"
element={<ProtectedRoute Component={PageSpeedDetailsWithAdminProp} />}
/>
<Route
path="pagespeed/configure/:monitorId"
element={<ProtectedRoute Component={PageSpeedConfigure} />}
/>
<Route
path="infrastructure"
element={<ProtectedRoute Component={Infrastructure} />}
/>
<Route
path="infrastructure/create"
element={<ProtectedRoute Component={CreateInfrastructureMonitor} />}
/>
<Route
path="infrastructure/:monitorId"
element={<ProtectedRoute Component={InfrastructureDetailsWithAdminProp} />}
/>
<Route
path="incidents/:monitorId?"
element={<ProtectedRoute Component={Incidents} />}
/>
<Route
path="status"
element={<ProtectedRoute Component={Status} />}
/>
<Route
path="integrations"
element={<ProtectedRoute Component={Integrations} />}
/>
<Route
path="maintenance"
element={<ProtectedRoute Component={MaintenanceWithAdminProp} />}
/>
<Route
path="/maintenance/create/:maintenanceWindowId?"
element={<CreateNewMaintenanceWindow />}
/>
<Route
path="settings"
element={<ProtectedRoute Component={SettingsWithAdminProp} />}
/>
<Route
path="advanced-settings"
element={<ProtectedRoute Component={AdvancedSettingsWithAdminProp} />}
/>
<Route
path="account/profile"
element={
<ProtectedRoute
Component={Account}
open="profile"
/>
}
/>
<Route
path="account/password"
element={
<ProtectedRoute
Component={Account}
open="password"
/>
}
/>
<Route
path="account/team"
element={
<ProtectedRoute
Component={Account}
open="team"
/>
}
/>
</Route>
<Route
exact
path="/login"
element={<Login />}
/>
<Route
exact
path="/register"
element={<AdminCheckedRegister />}
/>
<Route
exact
path="/register/:token"
element={<Register />}
/>
<Route
path="*"
element={<NotFound />}
/>
<Route
path="/forgot-password"
element={<ForgotPassword />}
/>
<Route
path="/check-email"
element={<CheckEmail />}
/>
<Route
path="/set-new-password/:token"
element={<SetNewPassword />}
/>
<Route
path="/new-password-confirmed"
element={<NewPasswordConfirmed />}
/>
</Routes>
<Routes />
<ToastContainer />
</ThemeProvider>
);
+6 -3
View File
@@ -32,9 +32,12 @@ const icons = {
const Alert = ({ variant, title, body, isToast, hasIcon = true, onClick }) => {
const theme = useTheme();
/* TODO
This needs fixing. text bg and border not necessarilly exist. Probably text becomes contrastText. border becomes contrastText. bg becomes dark.
Check possible variants, see implementation in light and dark theme, and adjust */
const { text, bg, border } = theme.palette[variant];
Do we need other variants for alert?
*/
const text = theme.palette.secondary.contrastText;
const bg = theme.palette.secondary.main;
const border = theme.palette.secondary.contrastText;
const icon = icons[variant];
return (
+8 -2
View File
@@ -1,5 +1,6 @@
import PropTypes from "prop-types";
import { Box, Stack } from "@mui/material";
import { Box, Stack, useTheme } from "@mui/material";
// import useUtils from "../../Pages/Uptime/utils";
/**
* A component that renders a pulsating dot with a specified color.
@@ -15,6 +16,11 @@ import { Box, Stack } from "@mui/material";
*/
const PulseDot = ({ color }) => {
const theme = useTheme();
// const { statusToTheme } = useUtils();
/* TODO refactor so it gets status and gets theme color. Then uses theme.palette.[themeColor].lowContrast */
/* const themeColor = statusToTheme[status]; */
return (
<Stack
width="26px"
@@ -44,7 +50,7 @@ const PulseDot = ({ color }) => {
width: "7px",
height: "7px",
borderRadius: "50%",
backgroundColor: "white",
backgroundColor: theme.palette.accent.contrastText,
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
+8 -25
View File
@@ -2,28 +2,7 @@ import { Avatar as MuiAvatar } from "@mui/material";
import PropTypes from "prop-types";
import { useSelector } from "react-redux";
import { useEffect, useState } from "react";
/**
* Generates a color based on the input string.
* @param {string} string - The input string to generate the color from.
* @returns {string}
*/
const stringToColor = (string) => {
let hash = 0;
let i;
for (i = 0; i < string.length; i += 1) {
hash = string.charCodeAt(i) + ((hash << 5) - hash);
}
let color = "#";
for (i = 0; i < 3; i += 1) {
const value = (hash >> (i * 8)) & 0xff;
color += `00${value.toString(16)}`.slice(-2);
}
return color;
};
import { useTheme } from "@emotion/react";
/**
* @component
* @param {Object} props
@@ -38,6 +17,7 @@ const stringToColor = (string) => {
const Avatar = ({ src, small, sx }) => {
const { user } = useSelector((state) => state.auth);
const theme = useTheme();
const style = small ? { width: 32, height: 32 } : { width: 64, height: 64 };
const border = small ? 1 : 3;
@@ -52,13 +32,16 @@ const Avatar = ({ src, small, sx }) => {
return (
<MuiAvatar
alt={`${user?.firstName} ${user?.lastName}`}
/* TODO What is the /static/images/avatar/2.jpg ?*/
src={src ? src : user?.avatarImage ? image : "/static/images/avatar/2.jpg"}
sx={{
fontSize: small ? "16px" : "22px",
color: "white",
fontWeight: 400,
backgroundColor: stringToColor(`${user?.firstName} ${user?.lastName}`),
color: theme.palette.accent.contrastText,
backgroundColor: theme.palette.accent.main, // Same BG color as checkmate BG in sidebar
display: "inline-flex",
/*
TODO not sure what this is for*/
"&::before": {
content: `""`,
position: "absolute",
@@ -74,7 +57,7 @@ const Avatar = ({ src, small, sx }) => {
}}
>
{user.firstName?.charAt(0)}
{user.lastName?.charAt(0)}
{user.lastName?.charAt(0) || ""}
</MuiAvatar>
);
};
-130
View File
@@ -1,130 +0,0 @@
.MuiTable-root .host {
width: fit-content;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.MuiTable-root .host span {
font-size: 11px;
}
.MuiTable-root .label {
line-height: 1;
border-radius: var(--env-var-radius-2);
padding: 7px;
font-size: var(--env-var-font-size-small-plus);
}
.MuiPaper-root:has(table.MuiTable-root) {
box-shadow: none;
}
.MuiTable-root .MuiTableBody-root .MuiTableRow-root:last-child .MuiTableCell-root {
border: none;
}
.MuiTable-root .MuiTableHead-root .MuiTableCell-root,
.MuiTable-root .MuiTableBody-root .MuiTableCell-root {
font-size: var(--env-var-font-size-medium);
}
.MuiTable-root .MuiTableHead-root .MuiTableCell-root {
padding: var(--env-var-spacing-1);
font-weight: 500;
}
.MuiTable-root .MuiTableHead-root span {
display: inline-block;
height: 17px;
width: 20px;
overflow: hidden;
margin-bottom: -3px;
margin-left: 3px;
}
.MuiTable-root .MuiTableHead-root span svg {
width: 20px;
height: 20px;
}
.MuiTable-root .MuiTableBody-root .MuiTableCell-root {
padding: var(--env-var-spacing-1);
}
.MuiTable-root .MuiTableBody-root .MuiTableRow-root {
height: 50px;
}
.MuiPaper-root + .MuiPagination-root {
border-radius: var(--env-var-radius-1);
padding: var(--env-var-spacing-1-plus) var(--env-var-spacing-2);
}
.MuiPaper-root + .MuiPagination-root ul {
justify-content: center;
}
.MuiPaper-root + .MuiPagination-root button {
font-size: var(--env-var-font-size-medium);
font-weight: 500;
}
.MuiPaper-root + .MuiPagination-root ul li:first-child {
margin-right: auto;
}
.MuiPaper-root + .MuiPagination-root ul li:last-child {
margin-left: auto;
}
.MuiPaper-root + .MuiPagination-root ul li:first-child button {
padding: 0 var(--env-var-spacing-1) 0 var(--env-var-spacing-1-plus);
}
.MuiPaper-root + .MuiPagination-root ul li:last-child button {
padding: 0 var(--env-var-spacing-1-plus) 0 var(--env-var-spacing-1);
}
.MuiPaper-root + .MuiPagination-root ul li:first-child button::after,
.MuiPaper-root + .MuiPagination-root ul li:last-child button::before {
position: relative;
display: inline-block;
}
.MuiPaper-root + .MuiPagination-root ul li:first-child button::after {
content: "Previous";
margin-left: 15px;
}
.MuiPaper-root + .MuiPagination-root ul li:last-child button::before {
content: "Next";
margin-right: 15px;
}
.MuiPaper-root + .MuiPagination-root div.MuiPaginationItem-root {
user-select: none;
}
.MuiTablePagination-root p {
font-weight: 500;
font-size: var(--env-var-font-size-small-plus);
}
.MuiTablePagination-root .MuiTablePagination-select.MuiSelect-select {
text-align: left;
text-align-last: left;
}
.MuiTablePagination-root button {
min-width: 0;
padding: 4px;
margin-left: 5px;
}
.MuiTablePagination-root svg {
width: 22px;
height: 22px;
}
.MuiTablePagination-root .MuiSelect-icon {
width: 16px;
height: 16px;
top: 50%;
right: 8%;
transform: translateY(-50%);
}
.MuiTablePagination-root button.Mui-disabled {
opacity: 0.4;
}
.table-container .MuiTable-root .MuiTableHead-root .MuiTableCell-root {
text-transform: uppercase;
opacity: 0.8;
font-size: var(--env-var-font-size-small-plus);
font-weight: 400;
}
.monitors .MuiTableCell-root:not(:first-of-type):not(:last-of-type),
.monitors .MuiTableCell-root:not(:first-of-type):not(:last-of-type) {
padding-left: var(--env-var-spacing-1);
padding-right: var(--env-var-spacing-1);
}
+47 -9
View File
@@ -1,3 +1,8 @@
/* TODO
Should we delete this?
*/
import PropTypes from "prop-types";
import { useState, useEffect } from "react";
import { useTheme } from "@emotion/react";
@@ -115,7 +120,7 @@ TablePaginationActions.propTypes = {
* @param {boolean} [props.paginated=false] - Flag to enable pagination.
* @param {boolean} [props.reversed=false] - Flag to enable reverse order.
* @param {number} props.rowsPerPage- Number of rows per page (table).
*
* @param {string} props.emptyMessage - Message to display when there is no data.
* @example
* const data = {
* cols: [
@@ -149,7 +154,7 @@ TablePaginationActions.propTypes = {
* <BasicTable data={data} rows={rows} paginated={true} />
*/
const BasicTable = ({ data, paginated, reversed, table }) => {
const BasicTable = ({ data, paginated, reversed, table, emptyMessage = "No data" }) => {
const DEFAULT_ROWS_PER_PAGE = 5;
const theme = useTheme();
const dispatch = useDispatch();
@@ -201,7 +206,27 @@ const BasicTable = ({ data, paginated, reversed, table }) => {
return (
<>
<TableContainer component={Paper}>
<Table>
<Table
sx={{
"&.MuiTable-root :is(.MuiTableHead-root, .MuiTableBody-root) :is(th, td)": {
paddingLeft: theme.spacing(12),
},
"& th": {
backgroundColor: theme.palette.secondary.main,
color: theme.palette.secondary.contrastText,
/*
TODO all general styling for th should live here
*/
},
"& td": {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastTextSecondary,
/*
TODO all general styling for td should live here
*/
},
}}
>
<TableHead>
<TableRow>
{data.cols.map((col) => (
@@ -216,7 +241,9 @@ const BasicTable = ({ data, paginated, reversed, table }) => {
sx={{
cursor: row.handleClick ? "pointer" : "default",
"&:hover": {
backgroundColor: theme.palette.background.accent,
filter: "brightness(.75)",
opacity: 0.75,
transition: "filter 0.3s ease, opacity 0.3s ease",
},
}}
key={row.id}
@@ -228,6 +255,16 @@ const BasicTable = ({ data, paginated, reversed, table }) => {
</TableRow>
);
})}
{displayData.length === 0 && (
<TableRow>
<TableCell
sx={{ textAlign: "center" }}
colSpan={data.cols.length}
>
{emptyMessage}
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</TableContainer>
@@ -239,7 +276,7 @@ const BasicTable = ({ data, paginated, reversed, table }) => {
px={theme.spacing(4)}
sx={{
"& p": {
color: theme.palette.text.tertiary,
color: theme.palette.primary.contrastTextTertiary,
},
}}
>
@@ -286,21 +323,21 @@ const BasicTable = ({ data, paginated, reversed, table }) => {
minWidth: theme.spacing(20),
textAlign: "left",
"&.Mui-focused > div": {
backgroundColor: theme.palette.background.main,
backgroundColor: theme.palette.primary.main,
},
},
},
}}
sx={{
mt: theme.spacing(6),
color: theme.palette.text.secondary,
color: theme.palette.primary.contrastTextSecondary,
"& svg path": {
stroke: theme.palette.text.tertiary,
stroke: theme.palette.primary.contrastTextTertiary,
strokeWidth: 1.3,
},
"& .MuiSelect-select": {
border: 1,
borderColor: theme.palette.border.light,
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
},
}}
@@ -317,6 +354,7 @@ BasicTable.propTypes = {
reversed: PropTypes.bool,
rowPerPage: PropTypes.number,
table: PropTypes.string,
emptyMessage: PropTypes.string,
};
export default BasicTable;
+2 -2
View File
@@ -1,9 +1,9 @@
.MuiBreadcrumbs-root {
height: 34px;
min-height: 34px;
}
.MuiBreadcrumbs-root svg {
width: 16px;
height: 16px;
min-height: 16px;
}
.MuiBreadcrumbs-root .MuiBreadcrumbs-li a {
font-size: var(--env-var-font-size-medium);
+7 -4
View File
@@ -28,13 +28,16 @@ const Breadcrumbs = ({ list }) => {
px={theme.spacing(2)}
py={theme.spacing(3.5)}
width="fit-content"
backgroundColor={theme.palette.background.fill}
backgroundColor={theme.palette.secondary.main}
borderRadius={theme.shape.borderRadius}
lineHeight="18px"
sx={{
"& .MuiBreadcrumbs-li a": {
transition: "background-color 0.2s ease-in-out, color 0.2s ease-in-out",
},
"& .MuiBreadcrumbs-li:not(:last-of-type):hover a": {
backgroundColor: theme.palette.other.fill,
opacity: 1,
backgroundColor: theme.palette.secondary.contrastText,
color: theme.palette.secondary.main,
},
}}
>
@@ -52,7 +55,7 @@ const Breadcrumbs = ({ list }) => {
opacity: 0.8,
textTransform: "capitalize",
"&, &:hover": {
color: theme.palette.text.tertiary,
color: theme.palette.secondary.contrastText,
},
}}
>
@@ -149,7 +149,7 @@ const CustomAreaChart = ({
/>
<CartesianGrid
stroke={theme.palette.border.light}
stroke={theme.palette.primary.lowContrast}
strokeWidth={1}
strokeOpacity={1}
fill="transparent"
@@ -180,7 +180,7 @@ const CustomAreaChart = ({
})}
{customTooltip ? (
<Tooltip
cursor={{ stroke: theme.palette.border.light }}
cursor={{ stroke: theme.palette.primary.lowContrast }}
content={customTooltip}
wrapperStyle={{ pointerEvents: "none" }}
/>
+19 -14
View File
@@ -39,12 +39,15 @@ const BarChart = ({ checks = [] }) => {
>
{checks.map((check, index) =>
check === "placeholder" ? (
/* TODO what is the purpose of this box? */
// CAIO_REVIEW the purpose of this box is to make sure there are always at least 25 bars
// even if there are less than 25 checks
<Box
key={`${check}-${index}`}
position="relative"
width={theme.spacing(4.5)}
height="100%"
backgroundColor={theme.palette.border.light}
backgroundColor={theme.palette.primary.lowContrast}
sx={{
borderRadius: theme.spacing(1.5),
}}
@@ -67,8 +70,8 @@ const BarChart = ({ checks = [] }) => {
height={theme.spacing(4)}
backgroundColor={
check.status
? theme.palette.success.main
: theme.palette.error.contrastText
? theme.palette.success.lowContrast
: theme.palette.error.lowContrast
}
sx={{ borderRadius: "50%" }}
/>
@@ -116,20 +119,22 @@ const BarChart = ({ checks = [] }) => {
"& .MuiTooltip-tooltip": {
backgroundColor: theme.palette.secondary.main,
border: 1,
borderColor: theme.palette.border.dark,
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
boxShadow: theme.shape.boxShadow,
px: theme.spacing(4),
py: theme.spacing(2),
py: theme.spacing(3),
},
"& .MuiTooltip-tooltip p": {
/* TODO Font size should point to theme */
fontSize: 12,
color: theme.palette.text.tertiary,
color: theme.palette.secondary.contrastText,
fontWeight: 500,
},
"& .MuiTooltip-tooltip span": {
/* TODO Font size should point to theme */
fontSize: 11,
color: theme.palette.text.tertiary,
color: theme.palette.secondary.contrastText,
fontWeight: 600,
},
},
@@ -140,16 +145,14 @@ const BarChart = ({ checks = [] }) => {
position="relative"
width="9px"
height="100%"
backgroundColor={
check.status
? theme.palette.success.contrastText
: theme.palette.error.dark
}
backgroundColor={theme.palette.primary.lowContrast} // CAIO_REVIEW
sx={{
borderRadius: theme.spacing(1.5),
/*
TODO this doesn't seem to be used
"&:hover > .MuiBox-root": {
filter: "brightness(0.8)",
},
}, */
}}
>
<Box
@@ -158,7 +161,9 @@ const BarChart = ({ checks = [] }) => {
width="100%"
height={`${animate ? check.responseTime : 0}%`}
backgroundColor={
check.status ? theme.palette.success.main : theme.palette.error.main
check.status
? theme.palette.success.lowContrast
: theme.palette.error.lowContrast
}
sx={{
borderRadius: theme.spacing(1.5),
@@ -54,8 +54,8 @@ const CustomGauge = ({ progress = 0, radius = 70, strokeWidth = 15, threshold =
const fillColor =
progressWithinRange > threshold
? theme.palette.percentage.uptimePoor
: theme.palette.primary.main;
? theme.palette.error.lowContrast // CAIO_REVIEW
: theme.palette.accent.main; // CAIO_REVIEW
return (
<Box
@@ -70,7 +70,7 @@ const CustomGauge = ({ progress = 0, radius = 70, strokeWidth = 15, threshold =
>
<circle
className="radial-chart-base"
stroke={theme.palette.background.fill}
stroke={theme.palette.primary.lowContrast} // CAIO_REVIEW
strokeWidth={strokeWidth}
fill="none"
cx={totalSize / 2} // Center the circle
@@ -1,111 +0,0 @@
import { RadialBarChart, RadialBar, ResponsiveContainer } from "recharts";
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
/* TODO delete component */
const MINIMUM_VALUE = 0;
const MAXIMUM_VALUE = 100;
const CHART_MAXIMUM_DATA = {
value: MAXIMUM_VALUE,
fill: "transparent",
};
const PROGRESS_THRESHOLD = 50;
const DEFAULT_WIDTH = 60;
const RADIUS_SIZE = "90%";
const START_ANGLE = 90;
const CHART_RANGE = 360;
const RADIAL_BAR_CHART_PROPS = {
innerRadius: RADIUS_SIZE,
outerRadius: RADIUS_SIZE,
barSize: 6,
startAngle: START_ANGLE,
endAngle: START_ANGLE - CHART_RANGE,
margin: { top: 0, right: 0, bottom: 0, left: 0 },
};
const RADIAL_BAR_PROPS = {
dataKey: "value",
cornerRadius: 8,
};
Gauge.propTypes = {
progressValue: PropTypes.number.isRequired,
width: PropTypes.number,
};
/**
* A circular gauge component that displays a progress value and text.
* The gauge fills based on the progress value and changes color at a 50% threshold.
*
* @component
* @param {Object} props - Component props
* @param {number} props.progressValue - The value to display in the gauge (0-100)
* @param {number} props.width - Width of the gauge container in pixels
* @returns {JSX.Element} A circular gauge chart with progress value and text
*
* @example
* <Gauge
* progressValue={75}
* width={200}
* />
*/
function Gauge({ progressValue, width = DEFAULT_WIDTH }) {
const theme = useTheme();
const myProgressValue = Math.max(MINIMUM_VALUE, Math.min(progressValue, MAXIMUM_VALUE));
const chartColor =
progressValue > PROGRESS_THRESHOLD
? theme.palette.primary.main
: theme.palette.percentage.uptimePoor;
const chartData = [
CHART_MAXIMUM_DATA,
{
value: myProgressValue,
fill: chartColor,
},
];
return (
<ResponsiveContainer
aspect={1}
width={width}
style={{ marginInline: "auto" }}
>
<RadialBarChart
{...RADIAL_BAR_CHART_PROPS}
data={chartData}
>
<RadialBar
{...RADIAL_BAR_PROPS}
fill={
progressValue > PROGRESS_THRESHOLD
? theme.palette.primary.main
: theme.palette.percentage.uptimePoor
}
background={{ fill: theme.palette.background.fill }}
label={{
position: "center",
content: () => (
<text
x="50%"
y="50%"
textAnchor="middle"
dominantBaseline="middle"
style={{
...theme.typography.body2,
fill: theme.typography.body2.color,
}}
>
{`${myProgressValue}%`}
</text>
),
}}
/>
</RadialBarChart>
</ResponsiveContainer>
);
}
export { Gauge };
@@ -13,20 +13,27 @@ import { useTheme } from "@emotion/react";
import { useMemo, useState } from "react";
import { useSelector } from "react-redux";
import { formatDateWithTz } from "../../../Utils/timeUtils";
import {
tooltipDateFormatLookup,
tickDateFormatLookup,
} from "../Utils/chartUtilFunctions";
import "./index.css";
const CustomToolTip = ({ active, payload, label }) => {
const CustomToolTip = ({ active, payload, label, dateRange }) => {
const format = tooltipDateFormatLookup(dateRange);
const uiTimezone = useSelector((state) => state.ui.timezone);
const theme = useTheme();
if (active && payload && payload.length) {
const responseTime = payload[0]?.payload?.originalAvgResponseTime
? payload[0]?.payload?.originalAvgResponseTime
: (payload[0]?.payload?.avgResponseTime ?? 0);
return (
<Box
className="area-tooltip"
sx={{
backgroundColor: theme.palette.background.main,
backgroundColor: theme.palette.primary.main,
border: 1,
borderColor: theme.palette.border.dark,
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
py: theme.spacing(2),
px: theme.spacing(4),
@@ -34,12 +41,12 @@ const CustomToolTip = ({ active, payload, label }) => {
>
<Typography
sx={{
color: theme.palette.text.tertiary,
color: theme.palette.primary.contrastTextTertiary,
fontSize: 12,
fontWeight: 500,
}}
>
{formatDateWithTz(label, "ddd, MMMM D, YYYY, h:mm A", uiTimezone)}
{formatDateWithTz(label, format, uiTimezone)}
</Typography>
<Box mt={theme.spacing(1)}>
<Box
@@ -56,7 +63,7 @@ const CustomToolTip = ({ active, payload, label }) => {
ml={theme.spacing(3)}
sx={{
"& span": {
color: theme.palette.text.tertiary,
color: theme.palette.primary.contrastTextTertiary,
fontSize: 11,
fontWeight: 500,
},
@@ -69,7 +76,7 @@ const CustomToolTip = ({ active, payload, label }) => {
Response Time
</Typography>{" "}
<Typography component="span">
{payload[0].payload.originalResponseTime}
{Math.floor(responseTime)}
<Typography
component="span"
sx={{ opacity: 0.8 }}
@@ -87,23 +94,35 @@ const CustomToolTip = ({ active, payload, label }) => {
return null;
};
const CustomTick = ({ x, y, payload, index }) => {
CustomToolTip.propTypes = {
active: PropTypes.bool,
payload: PropTypes.arrayOf(
PropTypes.shape({
value: PropTypes.number,
payload: PropTypes.shape({
_id: PropTypes.string,
avgResponseTime: PropTypes.number,
originalAvgResponseTime: PropTypes.number,
}),
})
),
label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
dateRange: PropTypes.string,
};
const CustomTick = ({ x, y, payload, dateRange }) => {
const format = tickDateFormatLookup(dateRange);
const theme = useTheme();
const uiTimezone = useSelector((state) => state.ui.timezone);
// Render nothing for the first tick
if (index === 0) return null;
return (
<Text
x={x}
y={y + 10}
textAnchor="middle"
fill={theme.palette.text.tertiary}
fill={theme.palette.primary.contrastTextTertiary}
fontSize={11}
fontWeight={400}
>
{formatDateWithTz(payload?.value, "h:mm a", uiTimezone)}
{formatDateWithTz(payload?.value, format, uiTimezone)}
</Text>
);
};
@@ -113,13 +132,13 @@ CustomTick.propTypes = {
y: PropTypes.number,
payload: PropTypes.object,
index: PropTypes.number,
dateRange: PropTypes.string,
};
const MonitorDetailsAreaChart = ({ checks }) => {
const MonitorDetailsAreaChart = ({ checks, dateRange }) => {
const theme = useTheme();
const memoizedChecks = useMemo(() => checks, [checks[0]]);
const [isHovered, setIsHovered] = useState(false);
return (
<ResponsiveContainer
width="100%"
@@ -140,7 +159,7 @@ const MonitorDetailsAreaChart = ({ checks }) => {
onMouseLeave={() => setIsHovered(false)}
>
<CartesianGrid
stroke={theme.palette.border.light}
stroke={theme.palette.primary.lowContrast}
strokeWidth={1}
strokeOpacity={1}
fill="transparent"
@@ -156,38 +175,36 @@ const MonitorDetailsAreaChart = ({ checks }) => {
>
<stop
offset="0%"
stopColor={theme.palette.primary.main}
stopColor={theme.palette.accent.main}
stopOpacity={0.8}
/>
<stop
offset="100%"
stopColor={theme.palette.primary.light}
stopColor={theme.palette.accent.light}
stopOpacity={0}
/>
</linearGradient>
</defs>
<XAxis
stroke={theme.palette.border.dark}
dataKey="createdAt"
tick={<CustomTick />}
minTickGap={0}
stroke={theme.palette.primary.lowContrast}
dataKey="_id"
tick={<CustomTick dateRange={dateRange} />}
axisLine={false}
tickLine={false}
height={20}
interval="equidistantPreserveStart"
/>
<Tooltip
cursor={{ stroke: theme.palette.border.light }}
content={<CustomToolTip />}
cursor={{ stroke: theme.palette.primary.lowContrast }}
content={<CustomToolTip dateRange={dateRange} />}
wrapperStyle={{ pointerEvents: "none" }}
/>
<Area
type="monotone"
dataKey="responseTime"
stroke={theme.palette.primary.main}
dataKey="avgResponseTime"
stroke={theme.palette.accent.main} // CAIO_REVIEW
fill="url(#colorUv)"
strokeWidth={isHovered ? 2.5 : 1.5}
activeDot={{ stroke: theme.palette.background.main, r: 5 }}
activeDot={{ stroke: theme.palette.accent.main, r: 5 }} // CAIO_REVIEW
/>
</AreaChart>
</ResponsiveContainer>
@@ -196,17 +213,7 @@ const MonitorDetailsAreaChart = ({ checks }) => {
MonitorDetailsAreaChart.propTypes = {
checks: PropTypes.array,
dateRange: PropTypes.string,
};
CustomToolTip.propTypes = {
active: PropTypes.bool,
payload: PropTypes.arrayOf(
PropTypes.shape({
payload: PropTypes.shape({
originalResponseTime: PropTypes.number.isRequired,
}).isRequired,
})
),
label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
};
export default MonitorDetailsAreaChart;
@@ -0,0 +1,25 @@
export const tooltipDateFormatLookup = (dateRange) => {
const dateFormatLookup = {
day: "ddd. MMMM D, YYYY, hh:mm A",
week: "ddd. MMMM D, YYYY, hh:mm A",
month: "ddd. MMMM D, YYYY",
};
const format = dateFormatLookup[dateRange];
if (format === undefined) {
return "";
}
return format;
};
export const tickDateFormatLookup = (dateRange) => {
const tickFormatLookup = {
day: "h:mm A",
week: "MM/D, h:mm A",
month: "ddd. M/D",
};
const format = tickFormatLookup[dateRange];
if (format === undefined) {
return "";
}
return format;
};
@@ -4,7 +4,7 @@ import { useTheme } from "@mui/material";
import { Text } from "recharts";
import { formatDateWithTz } from "../../../Utils/timeUtils";
import { Box, Stack, Typography } from "@mui/material";
import { tickDateFormatLookup, tooltipDateFormatLookup } from "./chartUtilFunctions";
/**
* Custom tick component for rendering time with timezone.
*
@@ -15,20 +15,20 @@ import { Box, Stack, Typography } from "@mui/material";
* @param {number} props.index - The index of the tick.
* @returns {JSX.Element} The rendered tick component.
*/
export const TzTick = ({ x, y, payload, index }) => {
export const TzTick = ({ x, y, payload, index, dateRange }) => {
const theme = useTheme();
const uiTimezone = useSelector((state) => state.ui.timezone);
const format = tickDateFormatLookup(dateRange);
return (
<Text
x={x}
y={y + 10}
textAnchor="middle"
fill={theme.palette.text.tertiary}
fill={theme.palette.primary.contrastTextTertiary}
fontSize={11}
fontWeight={400}
>
{formatDateWithTz(payload?.value, "h:mm a", uiTimezone)}
{formatDateWithTz(payload?.value, format, uiTimezone)}
</Text>
);
};
@@ -38,6 +38,7 @@ TzTick.propTypes = {
y: PropTypes.number,
payload: PropTypes.object,
index: PropTypes.number,
dateRange: PropTypes.string,
};
/**
@@ -58,7 +59,7 @@ export const PercentTick = ({ x, y, payload, index }) => {
x={x - 20}
y={y}
textAnchor="middle"
fill={theme.palette.text.tertiary}
fill={theme.palette.primary.contrastTextTertiary}
fontSize={11}
fontWeight={400}
>
@@ -110,18 +111,21 @@ export const InfrastructureTooltip = ({
yIdx = -1,
yLabel,
dotColor,
dateRange,
}) => {
const uiTimezone = useSelector((state) => state.ui.timezone);
const theme = useTheme();
const format = tooltipDateFormatLookup(dateRange);
if (active && payload && payload.length) {
const [hardwareType, metric] = yKey.split(".");
return (
<Box
className="area-tooltip"
sx={{
backgroundColor: theme.palette.background.main,
backgroundColor: theme.palette.primary.main,
border: 1,
borderColor: theme.palette.border.dark,
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
py: theme.spacing(2),
px: theme.spacing(4),
@@ -129,12 +133,12 @@ export const InfrastructureTooltip = ({
>
<Typography
sx={{
color: theme.palette.text.tertiary,
color: theme.palette.primary.contrastTextTertiary,
fontSize: 12,
fontWeight: 500,
}}
>
{formatDateWithTz(label, "ddd, MMMM D, YYYY, h:mm A", uiTimezone)}
{formatDateWithTz(label, format, uiTimezone)}
</Typography>
<Box mt={theme.spacing(1)}>
<Box
@@ -151,7 +155,7 @@ export const InfrastructureTooltip = ({
ml={theme.spacing(3)}
sx={{
"& span": {
color: theme.palette.text.tertiary,
color: theme.palette.primary.contrastTextTertiary,
fontSize: 11,
fontWeight: 500,
},
@@ -163,7 +167,7 @@ export const InfrastructureTooltip = ({
>
{yIdx >= 0
? `${yLabel} ${getFormattedPercentage(payload[0].payload[hardwareType][yIdx][metric])}`
: `${yLabel} ${getFormattedPercentage(payload[0].payload[hardwareType][metric])}`}
: `${yLabel} ${getFormattedPercentage(payload[0].payload[yKey])}`}
</Typography>
</Stack>
</Box>
@@ -186,11 +190,20 @@ InfrastructureTooltip.propTypes = {
yIdx: PropTypes.number,
yLabel: PropTypes.string,
dotColor: PropTypes.string,
dateRange: PropTypes.string,
};
export const TemperatureTooltip = ({ active, payload, label, keys, dotColor }) => {
export const TemperatureTooltip = ({
active,
payload,
label,
keys,
dotColor,
dateRange,
}) => {
const uiTimezone = useSelector((state) => state.ui.timezone);
const theme = useTheme();
const format = tooltipDateFormatLookup(dateRange);
const formatCoreKey = (key) => {
return key.replace(/^core(\d+)$/, "Core $1");
};
@@ -199,9 +212,9 @@ export const TemperatureTooltip = ({ active, payload, label, keys, dotColor }) =
<Box
className="area-tooltip"
sx={{
backgroundColor: theme.palette.background.main,
backgroundColor: theme.palette.primary.main,
border: 1,
borderColor: theme.palette.border.dark,
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
py: theme.spacing(2),
px: theme.spacing(4),
@@ -209,12 +222,12 @@ export const TemperatureTooltip = ({ active, payload, label, keys, dotColor }) =
>
<Typography
sx={{
color: theme.palette.text.tertiary,
color: theme.palette.primary.contrastTextTertiary,
fontSize: 12,
fontWeight: 500,
}}
>
{formatDateWithTz(label, "ddd, MMMM D, YYYY, h:mm A", uiTimezone)}
{formatDateWithTz(label, format, uiTimezone)}
</Typography>
<Stack direction="column">
@@ -228,7 +241,7 @@ export const TemperatureTooltip = ({ active, payload, label, keys, dotColor }) =
ml={theme.spacing(3)}
sx={{
"& span": {
color: theme.palette.text.tertiary,
color: theme.palette.primary.contrastTextTertiary,
fontSize: 11,
fontWeight: 500,
},
@@ -274,4 +287,6 @@ TemperatureTooltip.propTypes = {
PropTypes.string,
PropTypes.number,
]),
dotColor: PropTypes.string,
dateRange: PropTypes.string,
};
+4 -1
View File
@@ -49,7 +49,10 @@ const Check = ({ text, noHighlightText, variant = "info", outlined = false }) =>
<Typography
component="span"
sx={{
color: variant === "info" ? theme.palette.text.tertiary : colors[variant],
color:
variant === "info"
? theme.palette.primary.contrastTextTertiary
: colors[variant],
opacity: 0.8,
}}
>
@@ -1,12 +1,15 @@
import { Stack, styled } from "@mui/material";
export const ConfigBox = styled(Stack)(({ theme }) => ({
const ConfigBox = styled(Stack)(({ theme }) => ({
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
gap: theme.spacing(20),
backgroundColor: theme.palette.primary.main,
border: 1,
borderStyle: "solid",
borderColor: theme.palette.border.light,
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.spacing(2),
backgroundColor: theme.palette.background.main,
"& > *": {
paddingTop: theme.spacing(12),
paddingBottom: theme.spacing(18),
@@ -15,7 +18,7 @@ export const ConfigBox = styled(Stack)(({ theme }) => ({
flex: 0.7,
borderRight: 1,
borderRightStyle: "solid",
borderRightColor: theme.palette.border.light,
borderRightColor: theme.palette.primary.lowContrast,
paddingRight: theme.spacing(15),
paddingLeft: theme.spacing(15),
},
@@ -24,15 +27,12 @@ export const ConfigBox = styled(Stack)(({ theme }) => ({
paddingRight: theme.spacing(20),
paddingLeft: theme.spacing(18),
},
"& h2": {
color: theme.palette.text.secondary,
fontSize: 15,
fontWeight: 600,
},
"& h3, & p": {
color: theme.palette.text.tertiary,
"& h1, & h2": {
color: theme.palette.primary.contrastTextSecondary,
},
"& p": {
fontSize: 13,
color: theme.palette.primary.contrastTextTertiary,
},
}));
export default ConfigBox;
@@ -22,9 +22,9 @@ const GenericDialog = ({ title, description, open, onClose, theme, children }) =
left: "50%",
transform: "translate(-50%, -50%)",
width: 400,
bgcolor: theme.palette.background.main,
bgcolor: theme.palette.primary.main,
border: 1,
borderColor: theme.palette.border.light,
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
boxShadow: 24,
p: theme.spacing(15),
@@ -37,7 +37,7 @@ const GenericDialog = ({ title, description, open, onClose, theme, children }) =
id={titleId}
component="h2"
fontSize={16}
color={theme.palette.text.primary}
color={theme.palette.primary.contrastText}
fontWeight={600}
>
{title}
@@ -45,7 +45,7 @@ const GenericDialog = ({ title, description, open, onClose, theme, children }) =
{description && (
<Typography
id={descriptionId}
color={theme.palette.text.tertiary}
color={theme.palette.primary.contrastTextTertiary}
>
{description}
</Typography>
+1 -1
View File
@@ -12,7 +12,7 @@
width: max-content;
}
[class*="fallback__"] button.MuiButtonBase-root {
height: 34px;
min-height: 34px;
}
[class*="fallback__"] .check span.MuiTypography-root,
[class*="fallback__"] button.MuiButtonBase-root {
+3 -3
View File
@@ -40,7 +40,7 @@ const Fallback = ({ title, checks, link = "/", isAdmin, vowelStart = false }) =>
className="background-pattern-svg"
sx={{
"& svg g g:last-of-type path": {
stroke: theme.palette.border.light,
stroke: theme.palette.primary.lowContrast,
},
}}
>
@@ -54,7 +54,7 @@ const Fallback = ({ title, checks, link = "/", isAdmin, vowelStart = false }) =>
<Typography
component="h1"
marginY={theme.spacing(4)}
color={theme.palette.text.tertiary}
color={theme.palette.primary.contrastTextTertiary}
>
{vowelStart ? "An" : "A"} {title} is used to:
</Typography>
@@ -70,7 +70,7 @@ const Fallback = ({ title, checks, link = "/", isAdmin, vowelStart = false }) =>
{isAdmin && (
<Button
variant="contained"
color="primary"
color="accent"
sx={{ alignSelf: "center" }}
onClick={() => navigate(link)}
>
@@ -1,25 +0,0 @@
import { useSelector } from "react-redux";
const withAdminProp = (WrappedComponent) => {
const WithAdminProp = (props) => {
const { user } = useSelector((state) => state.auth);
const isAdmin =
(user?.role?.includes("admin") ?? false) ||
(user?.role?.includes("superadmin") ?? false);
return (
<WrappedComponent
{...props}
isAdmin={isAdmin}
/>
);
};
const wrappedComponentName =
WrappedComponent.displayName || WrappedComponent.name || "Component";
WithAdminProp.displayName = `WithAdminProp(${wrappedComponentName})`;
return WithAdminProp;
};
export default withAdminProp;
+4 -1
View File
@@ -6,16 +6,18 @@ import PropTypes from "prop-types";
*
* @param {Object} props - The properties passed to the Heading component.
* @param {('h1'|'h2'|'h3')} props.component - The heading level for the component.
* @param {Object} props.style - Custom styles to apply to the heading.
* @param {string} props.children - The content to display inside the heading.
* @returns {JSX.Element} The Typography component with specified heading properties.
*/
function Heading({ component, children }) {
function Heading({ component, style, children }) {
return (
<Typography
component={component}
variant="h2"
fontWeight={600}
style={style}
>
{children}
</Typography>
@@ -24,6 +26,7 @@ function Heading({ component, children }) {
Heading.propTypes = {
component: PropTypes.oneOf(["h1", "h2", "h3"]).isRequired,
style: PropTypes.object,
children: PropTypes.string.isRequired,
};
export { Heading };
@@ -16,7 +16,7 @@ import { BaseLabel } from "../Label";
const DEFAULT_CODE = 9999; // Default code for unknown status
const handleStatusCode = (status) => {
if (status >= 100 && status < 600) {
if (status) {
return status;
}
return DEFAULT_CODE;
@@ -30,32 +30,28 @@ const HttpStatusLabel = ({ status, customStyles }) => {
const theme = useTheme();
const colors = {
400: {
dotColor: theme.palette.warning.main,
bgColor: theme.palette.warning.dark,
borderColor: theme.palette.warning.light,
color: theme.palette.warning.main,
borderColor: theme.palette.warning.lowContrast,
},
500: {
dotColor: theme.palette.error.main,
bgColor: theme.palette.error.dark,
borderColor: theme.palette.error.light,
color: theme.palette.error.main,
borderColor: theme.palette.error.lowContrast,
},
default: {
dotColor: theme.palette.unresolved.main,
bgColor: theme.palette.unresolved.bg,
borderColor: theme.palette.unresolved.light,
color: theme.palette.primary.contrastText,
borderColor: theme.palette.primary.contrastText,
},
};
const statusCode = handleStatusCode(status);
const { borderColor, bgColor, dotColor } =
const { borderColor, color } =
colors[getRoundedStatusCode(statusCode)] || colors.default;
return (
<BaseLabel
label={String(statusCode)}
styles={{
color: dotColor,
backgroundColor: bgColor,
color: color,
borderColor: borderColor,
...customStyles,
}}
+77
View File
@@ -0,0 +1,77 @@
import { Box, styled } from "@mui/material";
import PropTypes from "prop-types";
/**
* IconBox - A styled box component for rendering icons with consistent sizing and styling
*
* @component
* @param {Object} [props] - Configuration options for the IconBox
* @param {number} [props.height=34] - Height of the icon box
* @param {number} [props.width=34] - Width of the icon box
* @param {number} [props.minWidth=34] - Minimum width of the icon box
* @param {number} [props.borderRadius=4] - Border radius of the icon box
* @param {number} [props.svgWidth=20] - Width of the SVG icon
* @param {number} [props.svgHeight=20] - Height of the SVG icon
*
* @example
* // Basic usage
* <IconBox>
* <SomeIcon />
* </IconBox>
*
* @example
* // Customized usage
* <IconBox
* height={40}
* width={40}
* svgWidth={24}
* svgHeight={24}
* >
* <CustomIcon />
* </IconBox>
*
* @returns {React.ReactElement} A styled box containing an icon
*/
const IconBox = styled(Box)(
({
theme,
height = 34,
width = 34,
minWidth = 34,
borderRadius = 4,
svgWidth = 20,
svgHeight = 20,
}) => ({
height: height,
minWidth: minWidth,
width: width,
position: "relative",
border: 1,
borderStyle: "solid",
borderColor: theme.palette.primary.lowContrast,
borderRadius: borderRadius,
backgroundColor: theme.palette.tertiary.main,
"& svg": {
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: svgWidth,
height: svgHeight,
"& path": {
stroke: theme.palette.primary.contrastTextTertiary,
},
},
})
);
IconBox.propTypes = {
height: PropTypes.number,
width: PropTypes.number,
minWidth: PropTypes.number,
borderRadius: PropTypes.number,
svgWidth: PropTypes.number,
svgHeight: PropTypes.number,
};
export default IconBox;
+39 -20
View File
@@ -3,33 +3,50 @@ 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 "./index.css";
/**
* @param {Object} props
* @param {string} props.id - The id attribute for the checkbox input.
* @param {string} props.label - The label to display next to the checkbox.
* @param {('small' | 'medium' | 'large')} - The size of the checkbox.
* @param {boolean} props.isChecked - Whether the checkbox is checked or not.
* @param {string} [props.value] - The value of the checkbox input.
* @param {function} [props.onChange] - The function to call when the checkbox value changes.
* @param {boolean} [props.isDisabled] - Whether the checkbox is disabled or not.
* Checkbox Component
*
* @returns {JSX.Element}
* A customized checkbox component using Material-UI that supports custom sizing,
* disabled states, and custom icons.
*
* @component
* @param {Object} props - Component properties
* @param {string} props.id - Unique identifier for the checkbox input
* @param {string} [props.name] - Optional name attribute for the checkbox
* @param {(string|React.ReactNode)} props.label - Label text or node for the checkbox
* @param {('small'|'medium'|'large')} [props.size='medium'] - Size of the checkbox icon
* @param {boolean} props.isChecked - Current checked state of the checkbox
* @param {string} [props.value] - Optional value associated with the checkbox
* @param {Function} [props.onChange] - Callback function triggered when checkbox state changes
* @param {boolean} [props.isDisabled] - Determines if the checkbox is disabled
*
* @returns {React.ReactElement} Rendered Checkbox component
*
* @example
* // Basic usage
* <Checkbox
* id="checkbox-id"
* label="Ping monitoring"
* isChecked={checks.type === "ping"}
* value="ping"
* onChange={handleChange}
* id="terms-checkbox"
* label="I agree to terms"
* isChecked={agreed}
* onChange={handleAgree}
* />
*
* @example
* // With custom size and disabled state
* <Checkbox
* id="advanced-checkbox"
* label="Advanced Option"
* size="large"
* isChecked={isAdvanced}
* isDisabled={!canModify}
* onChange={handleAdvancedToggle}
* />
*/
const Checkbox = ({
id,
name,
label,
size = "medium",
isChecked,
@@ -46,6 +63,7 @@ const Checkbox = ({
control={
<MuiCheckbox
checked={isDisabled ? false : isChecked}
name={name}
value={value}
onChange={onChange}
icon={<CheckboxOutline />}
@@ -54,7 +72,7 @@ const Checkbox = ({
"aria-label": "controlled checkbox",
id: id,
}}
sx={{
sx={{
"&:hover": { backgroundColor: "transparent" },
"& svg": { width: sizes[size], height: sizes[size] },
alignSelf: "flex-start",
@@ -72,14 +90,14 @@ const Checkbox = ({
mr: theme.spacing(6),
},
"&:not(:has(.Mui-disabled)):hover": {
backgroundColor: theme.palette.background.accent,
backgroundColor: theme.palette.tertiary.main,
},
"& span.MuiTypography-root": {
fontSize: 13,
color: theme.palette.text.tertiary,
color: theme.palette.primary.contrastTextTertiary,
},
".MuiFormControlLabel-label.Mui-disabled": {
color: theme.palette.text.tertiary,
color: theme.palette.primary.contrastTextTertiary,
opacity: 0.25,
},
}}
@@ -89,6 +107,7 @@ const Checkbox = ({
Checkbox.propTypes = {
id: PropTypes.string.isRequired,
name: PropTypes.string,
label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired,
size: PropTypes.oneOf(["small", "medium", "large"]),
isChecked: PropTypes.bool.isRequired,
+5 -5
View File
@@ -39,7 +39,7 @@ const ImageField = ({ id, src, loading, onChange }) => {
borderRadius: theme.shape.borderRadius,
borderColor: isDragging
? theme.palette.primary.main
: theme.palette.border.light,
: theme.palette.primary.lowContrast,
borderWidth: "2px",
transition: "0.2s",
"&:hover": {
@@ -86,7 +86,7 @@ const ImageField = ({ id, src, loading, onChange }) => {
sx={{
pointerEvents: "none",
borderRadius: theme.shape.borderRadius,
border: `solid ${theme.shape.borderThick}px ${theme.palette.border.light}`,
border: `solid ${theme.shape.borderThick}px ${theme.palette.primary.lowContrast}`,
boxShadow: theme.shape.boxShadow,
}}
>
@@ -94,7 +94,7 @@ const ImageField = ({ id, src, loading, onChange }) => {
</IconButton>
<Typography
component="h2"
color={theme.palette.text.tertiary}
color={theme.palette.primary.contrastTextTertiary}
>
<Typography
component="span"
@@ -108,7 +108,7 @@ const ImageField = ({ id, src, loading, onChange }) => {
</Typography>
<Typography
component="p"
color={theme.palette.text.tertiary}
color={theme.palette.primary.contrastTextTertiary}
sx={{ opacity: 0.6 }}
>
(maximum size: 3MB)
@@ -117,7 +117,7 @@ const ImageField = ({ id, src, loading, onChange }) => {
</Box>
<Typography
component="p"
color={theme.palette.text.tertiary}
color={theme.palette.primary.contrastTextTertiary}
sx={{ opacity: 0.6 }}
>
Supported formats: JPG, PNG
+5 -2
View File
@@ -42,6 +42,9 @@ const Radio = (props) => {
width: 16,
height: 16,
boxShadow: `inset 0 0 0 1px ${theme.palette.secondary.main}`,
"&:not(.Mui-checked)": {
boxShadow: `inset 0 0 0 1px ${theme.palette.primary.contrastText}70`, // Use theme text color for the outline
},
mt: theme.spacing(0.5),
}}
/>
@@ -53,7 +56,7 @@ const Radio = (props) => {
<Typography
component="h6"
mt={theme.spacing(1)}
color={theme.palette.text.secondary}
color={theme.palette.primary.contrastTextSecondary}
>
{props.desc}
</Typography>
@@ -66,7 +69,7 @@ const Radio = (props) => {
m: theme.spacing(-2.5),
borderRadius: theme.shape.borderRadius,
"&:hover": {
backgroundColor: theme.palette.background.accent,
backgroundColor: theme.palette.tertiary.main,
},
"& .MuiButtonBase-root": {
p: 0,
+34 -8
View File
@@ -27,7 +27,7 @@ const SearchAdornment = () => {
width: 16,
height: 16,
"& path": {
stroke: theme.palette.text.tertiary,
stroke: theme.palette.primary.contrastTextTertiary,
strokeWidth: 1.2,
},
},
@@ -40,6 +40,7 @@ const SearchAdornment = () => {
//TODO keep search state inside of component
const Search = ({
label,
id,
options,
filteredBy,
@@ -55,7 +56,6 @@ const Search = ({
disabled,
}) => {
const theme = useTheme();
return (
<Autocomplete
multiple={multiple}
@@ -76,6 +76,14 @@ const Search = ({
getOptionLabel={(option) => option[filteredBy]}
renderInput={(params) => (
<Stack>
<Typography
component="h3"
fontSize={"var(--env-var-font-size-medium)"}
color={theme.palette.primary.contrastTextSecondary}
fontWeight={500}
>
{label}
</Typography>
<TextField
{...params}
error={Boolean(error)}
@@ -86,13 +94,24 @@ const Search = ({
}}
sx={{
"& fieldset": {
borderColor: theme.palette.border.light,
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
},
"& .MuiOutlinedInput-root:hover:not(:has(input:focus)):not(:has(textarea:focus)) fieldset":
{
borderColor: theme.palette.border.light,
borderColor: theme.palette.primary.lowContrast,
},
"& .MuiOutlinedInput-root": {
paddingY: 0,
},
"& .MuiAutocomplete-tag": {
// CAIO_REVIEW
color: theme.palette.primary.contrastText,
backgroundColor: theme.palette.primary.lowContrast,
},
"& .MuiChip-deleteIcon": {
color: theme.palette.primary.contrastText, // CAIO_REVIEW
},
}}
/>
{error && (
@@ -120,6 +139,7 @@ const Search = ({
}
return filtered;
}}
getOptionKey={(option) => option.id}
renderOption={(props, option) => {
const { key, ...optionProps } = props;
return (
@@ -130,7 +150,7 @@ const Search = ({
option.noOptions
? {
pointerEvents: "none",
backgroundColor: theme.palette.background.main,
backgroundColor: theme.palette.primary.main,
}
: {}
}
@@ -143,16 +163,22 @@ const Search = ({
popper: {
keepMounted: true,
sx: {
"& ul": { p: 2 },
"& ul": { p: 2, backgroundColor: theme.palette.primary.main },
"& li.MuiAutocomplete-option": {
color: theme.palette.text.secondary,
color: theme.palette.primary.contrastTextSecondary,
px: 4,
borderRadius: theme.shape.borderRadius,
},
"& .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true'], & .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true'].Mui-focused, & .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true']:hover":
{
backgroundColor: theme.palette.background.fill,
backgroundColor: theme.palette.primary.lowContrast,
color: "red",
},
"& li.MuiAutocomplete-option:hover:not([aria-selected='true'])": {
color: theme.palette.primary.main,
backgroundColor: theme.palette.primary.contrastTextSecondary,
},
"& .MuiAutocomplete-noOptions": {
px: theme.spacing(6),
py: theme.spacing(5),
@@ -1,6 +1,6 @@
.select-wrapper .select-component > .MuiSelect-select {
padding: 0 10px;
height: 34px;
min-height: 34px;
display: flex;
align-items: center;
line-height: 1;
@@ -49,24 +49,25 @@ const Select = ({
onBlur,
sx,
name = "",
labelControlSpacing = 2,
}) => {
const theme = useTheme();
const itemStyles = {
fontSize: "var(--env-var-font-size-medium)",
color: theme.palette.text.tertiary,
color: theme.palette.primary.contrastTextTertiary,
borderRadius: theme.shape.borderRadius,
margin: theme.spacing(2),
};
return (
<Stack
gap={theme.spacing(2)}
gap={theme.spacing(labelControlSpacing)}
className="select-wrapper"
>
{label && (
<Typography
component="h3"
color={theme.palette.text.secondary}
color={theme.palette.primary.contrastTextSecondary}
fontWeight={500}
fontSize={13}
>
@@ -88,13 +89,13 @@ const Select = ({
minWidth: "125px",
"& fieldset": {
borderRadius: theme.shape.borderRadius,
borderColor: theme.palette.border.dark,
borderColor: theme.palette.primary.lowContrast,
},
"&:not(.Mui-focused):hover fieldset": {
borderColor: theme.palette.border.dark,
borderColor: theme.palette.primary.lowContrast,
},
"& svg path": {
fill: theme.palette.other.icon,
fill: theme.palette.primary.contrastTextTertiary,
},
...sx,
}}
@@ -144,6 +145,7 @@ Select.propTypes = {
onChange: PropTypes.func.isRequired,
onBlur: PropTypes.func,
sx: PropTypes.object,
labelControlSpacing: PropTypes.number,
};
export default Select;
@@ -12,15 +12,15 @@ export const HttpAdornment = ({ https }) => {
alignItems="center"
height="100%"
sx={{
borderRight: `solid 1px ${theme.palette.border.dark}`,
backgroundColor: theme.palette.background.accent,
borderRight: `solid 1px ${theme.palette.primary.lowContrast}`,
backgroundColor: theme.palette.tertiary.main,
pl: theme.spacing(6),
}}
>
<Typography
component="h5"
paddingRight={"var(--env-var-spacing-1-minus)"}
color={theme.palette.text.secondary}
color={theme.palette.primary.contrastTextSecondary}
sx={{ lineHeight: 1, opacity: 0.8 }}
>
{https ? "https" : "http"}://
@@ -41,7 +41,7 @@ export const PasswordEndAdornment = ({ fieldType, setFieldType }) => {
aria-label="toggle password visibility"
onClick={() => setFieldType(fieldType === "password" ? "text" : "password")}
sx={{
color: theme.palette.border.dark,
color: theme.palette.primary.lowContrast,
padding: theme.spacing(1),
"&:focus-visible": {
outline: `2px solid ${theme.palette.primary.main}`,
@@ -6,6 +6,19 @@ import PropTypes from "prop-types";
const getSx = (theme, type, maxWidth) => {
const sx = {
maxWidth: maxWidth,
"& .MuiOutlinedInput-root ": {
"&:hover .MuiOutlinedInput-notchedOutline": {
borderColor: theme.palette.primary.contrastText, // Adjust hover border color
},
"&.Mui-focused .MuiOutlinedInput-notchedOutline": {
borderColor: theme.palette.accent.main, // Adjust focus border color
},
"&.Mui-disabled .MuiOutlinedInput-notchedOutline": {
borderColor: theme.palette.primary.contrastText, // CAIO_REVIEW
opacity: 0.5,
},
},
"& .MuiFormHelperText-root": {
position: "absolute",
bottom: `-${theme.spacing(24)}`,
@@ -101,7 +114,7 @@ const TextInput = forwardRef(
<Typography
component="h3"
fontSize={"var(--env-var-font-size-medium)"}
color={theme.palette.text.secondary}
color={theme.palette.primary.contrastTextSecondary}
fontWeight={500}
>
{label}
+16 -37
View File
@@ -24,15 +24,15 @@ const BaseLabel = ({ label, styles, children }) => {
// Grab the default borderRadius from the theme to match button style
const { borderRadius } = theme.shape;
// Calculate padding for the label to mimic button. Appears to scale correctly, not 100% sure though.
const padding = theme.spacing(1 * 0.75, 2);
const padding = theme.spacing(3, 5);
return (
<Box
className="label"
sx={{
borderRadius: borderRadius,
border: `1px solid ${theme.palette.text.tertiary}`,
color: theme.palette.text.tertiary,
border: `1px solid ${theme.palette.primary.lowContrast}`,
color: theme.palette.primary.contrastText,
padding: padding,
...styles,
}}
@@ -87,7 +87,7 @@ const ColoredLabel = ({ label, color }) => {
const theme = useTheme();
// If an invalid color is passed, default to the labelGray color
if (typeof color !== "string" || !/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(color)) {
color = theme.palette.border.light;
color = theme.palette.primary.lowContrast;
}
// Calculate lighter shades for border and bg
@@ -122,53 +122,32 @@ ColoredLabel.propTypes = {
* <StatusLabel status="up" text="Active" />
*/
const statusToTheme = {
up: "success",
down: "error",
paused: "warning",
pending: "warning",
"cannot resolve": "error",
};
const StatusLabel = ({ status, text, customStyles }) => {
const theme = useTheme();
const colors = {
up: {
dotColor: theme.palette.success.main,
bgColor: theme.palette.success.contrastText /* dark */,
borderColor: theme.palette.success.main /* light */,
},
down: {
dotColor: theme.palette.error.main,
bgColor: theme.palette.error.dark,
borderColor: theme.palette.error.light,
},
paused: {
dotColor: theme.palette.warning.main,
bgColor: theme.palette.warning.dark,
borderColor: theme.palette.warning.light,
},
pending: {
dotColor: theme.palette.warning.main,
bgColor: theme.palette.warning.dark,
borderColor: theme.palette.warning.light,
},
"cannot resolve": {
dotColor: theme.palette.unresolved.main,
bgColor: theme.palette.unresolved.bg,
borderColor: theme.palette.unresolved.light,
},
};
// Look up the color for the status
const { borderColor, bgColor, dotColor } = colors[status];
const themeColor = statusToTheme[status];
return (
<BaseLabel
label={text}
styles={{
color: dotColor,
backgroundColor: bgColor,
borderColor: borderColor,
color: theme.palette[themeColor].main,
borderColor: theme.palette[themeColor].lowContrast,
...customStyles,
}}
>
<Box
width={7}
height={7}
bgcolor={dotColor}
bgcolor={theme.palette[themeColor].lowContrast}
borderRadius="50%"
marginRight="5px"
/>
+5 -5
View File
@@ -16,24 +16,24 @@ const Link = ({ level, label, url }) => {
const levelConfig = {
primary: {},
secondary: {
color: theme.palette.text.secondary,
color: theme.palette.primary.contrastTextSecondary,
sx: {
":hover": {
color: theme.palette.text.secondary,
color: theme.palette.primary.contrastTextSecondary,
},
},
},
tertiary: {
color: theme.palette.text.tertiary,
color: theme.palette.primary.contrastTextTertiary,
sx: {
textDecoration: "underline",
textDecorationStyle: "dashed",
textDecorationColor: theme.palette.primary.main,
textUnderlineOffset: "1px",
":hover": {
color: theme.palette.text.tertiary,
color: theme.palette.primary.contrastTextTertiary,
textDecorationColor: theme.palette.primary.main,
backgroundColor: theme.palette.background.fill,
backgroundColor: theme.palette.primary.lowContrast,
},
},
},
+8 -9
View File
@@ -1,4 +1,3 @@
import React from "react";
import { useTheme } from "@emotion/react";
import PropTypes from "prop-types";
import { Box, IconButton, LinearProgress, Stack, Typography } from "@mui/material";
@@ -30,11 +29,11 @@ const ProgressUpload = ({ icon, label, size, progress = 0, onClick, error }) =>
height: "fit-content",
borderRadius: theme.shape.borderRadius,
border: 1,
borderColor: theme.palette.border.light,
backgroundColor: theme.palette.background.fill,
borderColor: theme.palette.primary.lowContrast,
backgroundColor: theme.palette.primary.lowContrast,
"&:has(.input-error)": {
borderColor: theme.palette.error.main,
backgroundColor: theme.palette.error.dark,
backgroundColor: theme.palette.error.lowContrast,
py: theme.spacing(4),
px: theme.spacing(8),
"& > .MuiStack-root > svg": {
@@ -60,9 +59,9 @@ const ProgressUpload = ({ icon, label, size, progress = 0, onClick, error }) =>
height: 30,
minWidth: 30,
border: 1,
borderColor: theme.palette.border.dark,
borderColor: theme.palette.primary.lowContrast,
borderRadius: 2,
backgroundColor: theme.palette.background.main,
backgroundColor: theme.palette.primary.main,
"& svg": {
position: "absolute",
top: "50%",
@@ -71,7 +70,7 @@ const ProgressUpload = ({ icon, label, size, progress = 0, onClick, error }) =>
width: 23,
height: 23,
"& path": {
fill: theme.palette.other.icon,
fill: theme.palette.primary.contrastTextTertiary,
},
},
}}
@@ -90,7 +89,7 @@ const ProgressUpload = ({ icon, label, size, progress = 0, onClick, error }) =>
{error}
</Typography>
) : (
<Box color={theme.palette.text.tertiary}>
<Box color={theme.palette.primary.contrastTextTertiary}>
<Typography
component="h2"
mb={theme.spacing(1.5)}
@@ -148,7 +147,7 @@ const ProgressUpload = ({ icon, label, size, progress = 0, onClick, error }) =>
height: "10px",
borderRadius: theme.shape.borderRadius,
maxWidth: "500px",
backgroundColor: theme.palette.border.light,
backgroundColor: theme.palette.primary.lowContrast,
}}
/>
</Box>
@@ -7,9 +7,9 @@ import PropTypes from "prop-types";
const CustomStepIcon = (props) => {
const { completed, active } = props;
return completed ? (
<CheckCircle color="primary" />
<CheckCircle color="accent" />
) : (
<RadioButtonCheckedIcon color={active ? "primary" : "disabled"} />
<RadioButtonCheckedIcon color={active ? "accent" : "disabled"} />
);
};
@@ -3,21 +3,20 @@ import { useSelector } from "react-redux";
import PropTypes from "prop-types";
/**
* ProtectedRoute is a higher-order component that wraps the `Route` component from `react-router-dom`
* to create a protected route. It uses Redux to check if the user is authenticated. If the user is
* authenticated, it renders the component passed to it; otherwise, it redirects the user to the login page.
* ProtectedRoute is a wrapper component that ensures only authenticated users
* can access the wrapped content. It checks authentication status (e.g., from Redux or Context).
* If the user is authenticated, it renders the children; otherwise, it redirects to the login page.
*
* @param {Object} props - The props passed to the ProtectedRoute component.
* @param {React.ComponentType} props.Component - The component to render if the user is authenticated.
* @param {Object} rest - The remaining props passed to the Route component.
* @returns {React.ReactElement} A Route component that conditionally renders the passed Component or redirects to the login page.
* @param {React.ReactNode} props.children - The children to render if the user is authenticated.
* @returns {React.ReactElement} The children wrapped in a protected route or a redirect to the login page.
*/
const ProtectedRoute = ({ Component, ...rest }) => {
const ProtectedRoute = ({ children }) => {
const authState = useSelector((state) => state.auth);
return authState.authToken ? (
<Component {...rest} />
children
) : (
<Navigate
to="/login"
@@ -27,7 +26,7 @@ const ProtectedRoute = ({ Component, ...rest }) => {
};
ProtectedRoute.propTypes = {
Component: PropTypes.elementType.isRequired,
children: PropTypes.element.isRequired,
};
export default ProtectedRoute;
+1
View File
@@ -1,3 +1,4 @@
/* TODO */
aside .MuiList-root svg {
width: 20px;
height: 20px;
+154 -86
View File
@@ -21,13 +21,13 @@ import { useDispatch, useSelector } from "react-redux";
import { clearAuthState } from "../../Features/Auth/authSlice";
import { toggleSidebar } from "../../Features/UI/uiSlice";
import { clearUptimeMonitorState } from "../../Features/UptimeMonitors/uptimeMonitorsSlice";
import ThemeSwitch from "../ThemeSwitch";
import Avatar from "../Avatar";
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 LogoutSvg from "../../assets/icons/logout.svg?react";
import Support from "../../assets/icons/support.svg?react";
import Dashboard from "../../assets/icons/dashboard.svg?react";
import Account from "../../assets/icons/user-edit.svg?react";
import Maintenance from "../../assets/icons/maintenance.svg?react";
import Monitors from "../../assets/icons/monitors.svg?react";
@@ -43,11 +43,12 @@ import DotsVertical from "../../assets/icons/dots-vertical.svg?react";
import ChangeLog from "../../assets/icons/changeLog.svg?react";
import Docs from "../../assets/icons/docs.svg?react";
import Folder from "../../assets/icons/folder.svg?react";
import ChatBubbleOutlineRoundedIcon from "@mui/icons-material/ChatBubbleOutlineRounded";
import "./index.css";
const menu = [
{ name: "Monitors", path: "monitors", icon: <Monitors /> },
{ name: "Uptime", path: "uptime", icon: <Monitors /> },
{ name: "Pagespeed", path: "pagespeed", icon: <PageSpeed /> },
{ name: "Infrastructure", path: "infrastructure", icon: <Integrations /> },
{ name: "Incidents", path: "incidents", icon: <Incidents /> },
@@ -63,20 +64,31 @@ const menu = [
{ name: "Team", path: "account/team", icon: <TeamSvg /> },
],
},
{
name: "Settings",
icon: <Settings />,
path: "settings",
},
{
name: "Other",
icon: <Folder />,
nested: [
{ name: "Settings", path: "settings", icon: <Settings /> },
{ name: "Support", path: "support", icon: <Support /> },
{
name: "Discussions",
path: "discussions",
icon: <ChatBubbleOutlineRoundedIcon />,
},
{ name: "Docs", path: "docs", icon: <Docs /> },
{ name: "Changelog", path: "changelog", icon: <ChangeLog /> },
],
},
];
/* TODO this could be a key in nested Path would be the link */
const URL_MAP = {
support: "https://github.com/bluewave-labs/bluewave-uptime/issues",
support: "https://discord.com/invite/NAb6H3UTjK",
discussions: "https://github.com/bluewave-labs/checkmate/discussions",
docs: "https://bluewavelabs.gitbook.io/checkmate",
changelog: "https://github.com/bluewave-labs/bluewave-uptime/releases",
};
@@ -86,7 +98,7 @@ const PATH_MAP = {
pagespeed: "Dashboard",
infrastructure: "Dashboard",
account: "Account",
settings: "Other",
settings: "Settings",
};
/**
@@ -144,46 +156,110 @@ function Sidebar() {
}
}, [location]);
/* TODO refactor this, there are a some ternaries and comments in the return */
const iconColor = theme.palette.primary.contrastTextTertiary;
/* TODO refactor this, there are a some ternaries and comments in the return */
return (
<Stack
component="aside"
className={collapsed ? "collapsed" : "expanded"}
/* TODO general padding should be here */
py={theme.spacing(6)}
gap={theme.spacing(6)}
/* TODO set all style in this sx if possible (when general)
This is the top lever for styles
*/
sx={{
border: 1,
borderColor: theme.palette.border.light,
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
backgroundColor: theme.palette.background.main,
"& .selected-path, & .MuiListItemButton-root:hover": {
backgroundColor: theme.palette.background.accent,
"& :is(p, span, .MuiListSubheader-root)": {
/*
Text color for unselected menu items and menu headings
Secondary contrast text against main background
*/
color: theme.palette.primary.contrastTextSecondary,
},
"& .MuiList-root svg path": {
stroke: theme.palette.text.tertiary,
/* Menu Icons */
stroke: iconColor,
},
"& p, & span, & .MuiListSubheader-root": {
color: theme.palette.text.secondary,
"& .selected-path": {
/* Selected menu item */
backgroundColor: theme.palette.secondary.main,
"&:hover": {
backgroundColor: theme.palette.secondary.main,
},
"& .MuiListItemIcon-root svg path": {
/* Selected menu item icon */
stroke: theme.palette.secondary.contrastText,
},
"& .MuiListItemText-root :is(p, span)": {
/* Selected menu item text */
color: theme.palette.secondary.contrastText,
},
},
"& .MuiListItemButton-root:not(.selected-path)": {
transition: "background-color .3s",
" &:hover": {
/* Hovered menu item bg color */
backgroundColor: theme.palette.tertiary.main,
"& :is(p, span)": {
/* Hovered menu item text color */
color: theme.palette.tertiary.contrastText,
},
},
},
}}
>
<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: 1,
borderColor: theme.palette.primary.lowContrast,
p: theme.spacing(2.5),
"& svg": {
width: theme.spacing(8),
height: theme.spacing(8),
"& path": {
/* TODO this should be set at the top level if possible */
stroke: theme.palette.primary.contrastTextSecondary,
},
},
"&:focus": { outline: "none" },
"&:hover": {
backgroundColor: theme.palette.primary.lowContrast,
borderColor: theme.palette.primary.lowContrast,
},
}}
onClick={() => {
setOpen((prev) =>
Object.fromEntries(Object.keys(prev).map((key) => [key, false]))
);
dispatch(toggleSidebar());
}}
>
{collapsed ? <ArrowRight /> : <ArrowLeft />}
</IconButton>
{/* TODO Alignment done using padding. Use single source of truth to that*/}
<Stack
pt={theme.spacing(6)}
pb={theme.spacing(12)}
pl={theme.spacing(8)}
>
{/* TODO Abstract logo into component */}
{/* TODO Turn logo into a link */}
<Stack
direction="row"
alignItems="center"
gap={theme.spacing(4)}
onClick={() =>
window.open(
"https://github.com/bluewave-labs/bluewave-uptime",
"_blank",
"noreferrer"
)
}
onClick={() => navigate("/")}
sx={{ cursor: "pointer" }}
>
<Stack
@@ -193,10 +269,11 @@ function Sidebar() {
minHeight={theme.spacing(16)}
pl="1px"
fontSize={18}
color="white"
color={theme.palette.accent.contrastText}
sx={{
position: "relative",
backgroundColor: theme.palette.primary.main,
backgroundColor: theme.palette.accent.main,
color: theme.palette.accent.contrastText,
borderRadius: theme.shape.borderRadius,
userSelect: "none",
}}
@@ -211,40 +288,7 @@ function Sidebar() {
Checkmate
</Typography>
</Stack>
<IconButton
sx={{
position: "absolute",
top: 60,
right: 0,
transform: `translate(50%, 0)`,
backgroundColor: theme.palette.background.fill,
border: 1,
borderColor: theme.palette.border.light,
p: theme.spacing(2.5),
"& svg": {
width: theme.spacing(8),
height: theme.spacing(8),
"& path": {
stroke: theme.palette.text.secondary,
},
},
"&:focus": { outline: "none" },
"&:hover": {
backgroundColor: theme.palette.border.light,
borderColor: theme.palette.border.light,
},
}}
onClick={() => {
setOpen((prev) =>
Object.fromEntries(Object.keys(prev).map((key) => [key, false]))
);
dispatch(toggleSidebar());
}}
>
{collapsed ? <ArrowRight /> : <ArrowLeft />}
</IconButton>
</Stack>
{/* menu */}
<List
component="nav"
aria-labelledby="nested-menu-subheader"
@@ -254,7 +298,8 @@ function Sidebar() {
component="div"
id="nested-menu-subheader"
sx={{
pt: theme.spacing(4),
py: theme.spacing(4),
/* TODO px should be centralized in container */
px: collapsed ? theme.spacing(2) : theme.spacing(4),
backgroundColor: "transparent",
}}
@@ -262,10 +307,15 @@ function Sidebar() {
Menu
</ListSubheader>
}
sx={{ px: theme.spacing(6) }}
sx={{
px: theme.spacing(6),
height: "100%",
/* overflow: "hidden", */
}}
>
{menu.map((item) =>
item.path ? (
/* If item has a path */
<Tooltip
key={item.path}
placement="right"
@@ -288,10 +338,12 @@ function Sidebar() {
className={location.pathname.includes(item.path) ? "selected-path" : ""}
onClick={() => navigate(`/${item.path}`)}
sx={{
height: "37px",
/*
TODO we do not need this height
minHeight: "37px", */
p: theme.spacing(5),
gap: theme.spacing(4),
borderRadius: theme.shape.borderRadius,
px: theme.spacing(4),
}}
>
<ListItemIcon sx={{ minWidth: 0 }}>{item.icon}</ListItemIcon>
@@ -299,7 +351,7 @@ function Sidebar() {
</ListItemButton>
</Tooltip>
) : collapsed ? (
/* TODO Do we ever get here? */
/* TODO Do we ever get here? If item does not have a path and collapsed state is true */
<React.Fragment key={item.name}>
<Tooltip
placement="right"
@@ -355,8 +407,9 @@ function Sidebar() {
MenuListProps={{ sx: { px: 1, py: 2 } }}
sx={{
ml: theme.spacing(8),
/* TODO what is this selection? */
"& .selected-path": {
backgroundColor: theme.palette.background.accent,
backgroundColor: theme.palette.tertiary.main,
},
}}
>
@@ -387,9 +440,10 @@ function Sidebar() {
sx={{
gap: theme.spacing(4),
opacity: 0.9,
/* TODO this has no effect? */
"& svg": {
"& path": {
stroke: theme.palette.other.icon,
stroke: theme.palette.primary.contrastTextTertiary,
strokeWidth: 1.1,
},
},
@@ -403,6 +457,7 @@ function Sidebar() {
</Menu>
</React.Fragment>
) : (
/* TODO Do we ever get here? If item does not have a path and collapsed state is false */
<React.Fragment key={item.name}>
<ListItemButton
onClick={() =>
@@ -464,7 +519,7 @@ function Sidebar() {
left: "-7px",
height: "100%",
borderLeft: 1,
borderLeftColor: theme.palette.other.line,
borderLeftColor: theme.palette.primary.lowContrast,
},
"&:last-child::before": {
height: "50%",
@@ -477,10 +532,11 @@ function Sidebar() {
height: "3px",
width: "3px",
borderRadius: "50%",
backgroundColor: theme.palette.other.line,
backgroundColor: theme.palette.primary.lowContrast,
},
"&.selected-path::after": {
backgroundColor: theme.palette.text.tertiary,
/* TODO what is this selector doing? */
backgroundColor: theme.palette.primary.contrastTextTertiary,
transform: "scale(1.2)",
},
}}
@@ -547,28 +603,39 @@ function Sidebar() {
{authState.user?.role}
</Typography>
</Box>
<Tooltip
title="Controls"
disableInteractive
<Stack
flexDirection={"row"}
marginLeft={"auto"}
columnGap={theme.spacing(2)}
>
<IconButton
sx={{
ml: "auto",
mr: "-8px",
"&:focus": { outline: "none" },
"& svg": {
width: "20px",
height: "20px",
},
"& svg path": {
stroke: theme.palette.other.icon,
},
}}
onClick={(event) => openPopup(event, "logout")}
<ThemeSwitch color={iconColor} />
<Tooltip
title="Controls"
disableInteractive
>
<DotsVertical />
</IconButton>
</Tooltip>
<IconButton
sx={{
ml: "auto",
mr: "-8px",
"&:focus": { outline: "none" },
alignSelf: "center",
padding: "10px",
"& svg": {
width: "22px",
height: "22px",
},
"& svg path": {
/* Vertical three dots */
stroke: iconColor,
},
}}
onClick={(event) => openPopup(event, "logout")}
>
<DotsVertical />
</IconButton>
</Tooltip>
</Stack>
</>
)}
<Menu
@@ -618,6 +685,7 @@ function Sidebar() {
</Box>
</MenuItem>
)}
{/* TODO Do we need two dividers? */}
{collapsed && <Divider />}
<Divider />
<MenuItem
@@ -627,7 +695,7 @@ function Sidebar() {
borderRadius: theme.shape.borderRadius,
pl: theme.spacing(4),
"& svg path": {
stroke: theme.palette.other.icon,
stroke: iconColor,
},
}}
>
+112
View File
@@ -0,0 +1,112 @@
import { Box, Typography } from "@mui/material";
import { useTheme } from "@mui/material/styles";
import PropTypes from "prop-types";
import useUtils from "../../Pages/Uptime/utils";
/**
* StatBox Component
*
* A reusable component that displays a statistic with a heading and subheading
* in a styled box with a gradient background.
*
* @component
* @param {Object} props - The component props
* @param {string} props.heading - The primary heading/title of the statistic
* @param {string|React.ReactNode} props.subHeading - The value or description of the statistic
* @param {boolean} [props.gradient=false] - Determines if the box should have a gradient background
* @param {string} [props.status] - The status of the statistic
* @param {Object} [props.sx] - Additional custom styling to be applied to the box
*
* @example
* return (
* <StatBox
* heading="Total Users"
* subHeading="1,234"
* sx={{ width: 300 }}
* />
* )
*
* @returns {React.ReactElement} A styled box containing the statistic
*/
const StatBox = ({ heading, subHeading, gradient = false, status = "", sx }) => {
const theme = useTheme();
const { statusToTheme } = useUtils();
const themeColor = statusToTheme[status];
const statusBoxStyles = gradient
? {
background: `linear-gradient(to bottom right, ${theme.palette[themeColor].main} 30%, ${theme.palette[themeColor].lowContrast} 70%)`,
borderColor: theme.palette[themeColor].lowContrast,
}
: {
background: `linear-gradient(340deg, ${theme.palette.tertiary.main} 20%, ${theme.palette.primary.main} 45%)`,
borderColor: theme.palette.primary.lowContrast,
};
const headingStyles = gradient
? {
color: theme.palette[themeColor].contrastText,
}
: {
color: theme.palette.primary.contrastTextSecondary,
};
const spanFixedStyles = { marginLeft: theme.spacing(2), fontSize: 15 };
const detailTextStyles = gradient
? {
color: theme.palette[themeColor].contrastText,
"& span": {
color: theme.palette[themeColor].contrastText,
...spanFixedStyles,
},
}
: {
color: theme.palette.primary.contrastText,
"& span": {
color: theme.palette.primary.contrastTextTertiary,
...spanFixedStyles,
},
};
return (
<Box
sx={{
padding: `${theme.spacing(4)} ${theme.spacing(8)}`,
/* TODO why are we using width and min width here? */
minWidth: 200,
width: 225,
border: 1,
borderStyle: "solid",
borderRadius: 4,
...statusBoxStyles,
"& h2": {
/* TODO font size should come from theme */
fontSize: 13,
fontWeight: 500,
textTransform: "uppercase",
...headingStyles,
},
"& p": {
fontSize: 18,
marginTop: theme.spacing(2),
...detailTextStyles,
},
...sx,
}}
>
<Typography component="h2">{heading}</Typography>
<Typography>{subHeading}</Typography>
</Box>
);
};
StatBox.propTypes = {
heading: PropTypes.string.isRequired,
subHeading: PropTypes.node.isRequired,
gradient: PropTypes.bool,
status: PropTypes.string,
sx: PropTypes.object,
};
export default StatBox;
@@ -28,6 +28,8 @@ const PasswordPanel = () => {
const theme = useTheme();
const dispatch = useDispatch();
const SPACING_GAP = theme.spacing(12);
//redux state
const { authToken, isLoading } = useSelector((state) => state.auth);
@@ -113,7 +115,7 @@ const PasswordPanel = () => {
value="password"
sx={{
"& h1, & input": {
color: theme.palette.text.tertiary,
color: theme.palette.primary.contrastTextTertiary,
},
}}
>
@@ -123,8 +125,7 @@ const PasswordPanel = () => {
noValidate
spellCheck="false"
gap={theme.spacing(26)}
maxWidth={"80ch"}
marginInline={"auto"}
maxWidth={"80ch"} // Keep maxWidth
>
<TextInput
type="text"
@@ -139,7 +140,7 @@ const PasswordPanel = () => {
direction="row"
justifyContent={"flex-start"}
alignItems={"center"}
gap={theme.spacing(8)}
gap={SPACING_GAP}
flexWrap={"wrap"}
>
<Typography
@@ -163,8 +164,8 @@ const PasswordPanel = () => {
</Stack>
<Stack
direction="row"
alignItems={"center"}
gap={theme.spacing(8)}
alignItems={"flex-start"}
gap={SPACING_GAP}
flexWrap={"wrap"}
>
<Typography
@@ -189,8 +190,8 @@ const PasswordPanel = () => {
</Stack>
<Stack
direction="row"
alignItems={"center"}
gap={theme.spacing(8)}
alignItems={"flex-start"}
gap={SPACING_GAP}
flexWrap={"wrap"}
>
<Typography
@@ -227,7 +228,7 @@ const PasswordPanel = () => {
>
<LoadingButton
variant="contained"
color="primary"
color="accent"
type="submit"
loading={isLoading}
loadingIndicator="Saving..."
@@ -30,6 +30,8 @@ const ProfilePanel = () => {
const theme = useTheme();
const dispatch = useDispatch();
const SPACING_GAP = theme.spacing(12);
//redux state
const { user, authToken, isLoading } = useSelector((state) => state.auth);
@@ -214,7 +216,7 @@ const ProfilePanel = () => {
value="profile"
sx={{
"& h1, & p, & input": {
color: theme.palette.text.tertiary,
color: theme.palette.primary.contrastTextTertiary,
},
}}
>
@@ -223,9 +225,13 @@ const ProfilePanel = () => {
className="edit-profile-form"
noValidate
spellCheck="false"
gap={theme.spacing(20)}
gap={SPACING_GAP}
>
<Stack direction="row">
<Stack
direction="row"
gap={SPACING_GAP}
>
{/* This 0.9 is a bit magic numbering, refactor */}
<Box flex={0.9}>
<Typography component="h1">First name</Typography>
</Box>
@@ -240,7 +246,10 @@ const ProfilePanel = () => {
flex={1}
/>
</Stack>
<Stack direction="row">
<Stack
direction="row"
gap={SPACING_GAP}
>
<Box flex={0.9}>
<Typography component="h1">Last name</Typography>
</Box>
@@ -255,7 +264,10 @@ const ProfilePanel = () => {
flex={1}
/>
</Stack>
<Stack direction="row">
<Stack
direction="row"
gap={SPACING_GAP}
>
<Stack flex={0.9}>
<Typography component="h1">Email</Typography>
<Typography
@@ -275,7 +287,10 @@ const ProfilePanel = () => {
flex={1}
/>
</Stack>
<Stack direction="row">
<Stack
direction="row"
gap={SPACING_GAP}
>
<Stack flex={0.9}>
<Typography component="h1">Your photo</Typography>
<Typography
@@ -289,6 +304,7 @@ const ProfilePanel = () => {
direction="row"
alignItems="center"
flex={1}
gap={"8px"}
>
<Avatar
src={
@@ -298,18 +314,18 @@ const ProfilePanel = () => {
? localData.file
: ""
}
sx={{ mr: "8px" }}
sx={{ marginRight: "8px" }}
/>
<Button
variant="text"
color="info"
variant="contained" // CAIO_REIVEW
color="error"
onClick={handleDeletePicture}
>
Delete
</Button>
<Button
variant="text"
color="primary"
variant="contained" // CAIO_REVIEW
color="accent"
onClick={openPictureModal}
>
Update
@@ -330,7 +346,7 @@ const ProfilePanel = () => {
<Box width="fit-content">
<LoadingButton
variant="contained"
color="primary"
color="accent"
onClick={handleSaveProfile}
loading={isLoading}
loadingIndicator="Saving..."
@@ -346,7 +362,7 @@ const ProfilePanel = () => {
aria-hidden="true"
sx={{
marginY: theme.spacing(20),
borderColor: theme.palette.border.light,
borderColor: theme.palette.primary.lowContrast,
}}
/>
{!user.role.includes("demo") && (
@@ -361,7 +377,7 @@ const ProfilePanel = () => {
component="p"
sx={{ opacity: 0.6 }}
>
Note that deleting your account will remove all data from our system. This
Note that deleting your account will remove all data from the server. This
is permanent and non-recoverable.
</Typography>
</Box>
@@ -436,7 +452,7 @@ const ProfilePanel = () => {
</Button>
<Button
variant="contained"
color="primary"
color="accent"
onClick={handleUpdatePicture}
disabled={
(Object.keys(errors).length !== 0 && errors?.picture) ||
@@ -7,11 +7,10 @@ import { credentials } from "../../../Validation/validation";
import { networkService } from "../../../main";
import { createToast } from "../../../Utils/toastUtils";
import { useSelector } from "react-redux";
import BasicTable from "../../BasicTable";
import Select from "../../Inputs/Select";
import LoadingButton from "@mui/lab/LoadingButton";
import { GenericDialog } from "../../Dialog/genericDialog";
import DataTable from "../../Table/";
/**
* TeamPanel component manages the organization and team members,
* providing functionalities like renaming the organization, managing team members,
@@ -21,32 +20,47 @@ import { GenericDialog } from "../../Dialog/genericDialog";
*/
const TeamPanel = () => {
const roleMap = {
superadmin: "Super admin",
admin: "Admin",
user: "Team member",
demo: "Demo User",
};
const theme = useTheme();
const { authToken, user } = useSelector((state) => state.auth);
//TODO
// const [orgStates, setOrgStates] = useState({
// name: "Bluewave Labs",
// isEdit: false,
// });
const SPACING_GAP = theme.spacing(12);
const { authToken } = useSelector((state) => state.auth);
const [toInvite, setToInvite] = useState({
email: "",
role: ["0"],
});
const [tableData, setTableData] = useState({});
const [data, setData] = useState([]);
const [members, setMembers] = useState([]);
const [filter, setFilter] = useState("all");
const [isDisabled, setIsDisabled] = useState(true);
const [errors, setErrors] = useState({});
const [isSendingInvite, setIsSendingInvite] = useState(false);
const headers = [
{
id: "name",
content: "Name",
render: (row) => {
return (
<Stack>
<Typography color={theme.palette.primary.contrastTextSecondary}>
{row.firstName + " " + row.lastName}
</Typography>
<Typography>
Created {new Date(row.createdAt).toLocaleDateString()}
</Typography>
</Stack>
);
},
},
{ id: "email", content: "Email", render: (row) => row.email },
{
id: "role",
content: "Role",
render: (row) => row.role,
},
];
useEffect(() => {
const fetchTeam = async () => {
try {
@@ -62,9 +76,15 @@ const TeamPanel = () => {
};
fetchTeam();
}, [user]);
}, [authToken]);
useEffect(() => {
const ROLE_MAP = {
superadmin: "Super admin",
admin: "Admin",
user: "Team member",
demo: "Demo User",
};
let team = members;
if (filter !== "all")
team = members.filter((member) => {
@@ -74,73 +94,17 @@ const TeamPanel = () => {
return member.role.includes(filter);
});
const data = {
cols: [
{ id: 1, name: "NAME" },
{ id: 2, name: "EMAIL" },
{ id: 3, name: "ROLE" },
// FEATURE STILL TO BE IMPLEMENTED
// { id: 4, name: "ACTION" },
],
rows: team?.map((member, idx) => {
const roles = member.role.map((role) => roleMap[role]).join(",");
return {
id: member._id,
data: [
{
id: idx,
data: (
<Stack>
<Typography color={theme.palette.text.secondary}>
{member.firstName + " " + member.lastName}
</Typography>
<Typography>
Created {new Date(member.createdAt).toLocaleDateString()}
</Typography>
</Stack>
),
},
{ id: idx + 1, data: member.email },
{
// TODO - Add select dropdown
id: idx + 2,
data: roles,
},
// FEATURE STILL TO BE IMPLEMENTED
// {
// // TODO - Add delete onClick
// id: idx + 3,
// data: (
// <IconButton
// aria-label="remove member"
// sx={{
// "&:focus": {
// outline: "none",
// },
// }}
// >
// <Remove />
// </IconButton>
// ),
// },
],
};
}),
};
team = team.map((member) => ({
...member,
id: member._id,
role: member.role.map((role) => ROLE_MAP[role]).join(","),
}));
setData(team);
}, [filter, members]);
setTableData(data);
}, [members, filter]);
useEffect(() => {
setIsDisabled(Object.keys(errors).length !== 0 || toInvite.email === "");
}, [errors, toInvite.email]);
// RENAME ORGANIZATION
// const toggleEdit = () => {
// setOrgStates((prev) => ({ ...prev, isEdit: !prev.isEdit }));
// };
// const handleRename = () => {};
// INVITE MEMBER
const [isOpen, setIsOpen] = useState(false);
const handleChange = (event) => {
@@ -216,66 +180,19 @@ const TeamPanel = () => {
value="team"
sx={{
"& h1": {
color: theme.palette.text.tertiary,
color: theme.palette.primary.contrastTextTertiary,
},
"& .MuiTable-root .MuiTableBody-root .MuiTableCell-root, & .MuiTable-root p + p":
{
color: theme.palette.text.accent,
color: theme.palette.primary.contrastTextSecondary,
},
}}
>
{/* FEATURE STILL TO BE IMPLEMENTED */}
{/* <Stack component="form">
<Box sx={{ alignSelf: "flex-start" }}>
<Typography component="h1">Organization name</Typography>
</Box>
<Stack
direction="row"
justifyContent="flex-end"
alignItems="center"
sx={{ height: "34px" }}
>
<TextField
value={orgStates.name}
onChange={(event) =>
setOrgStates((prev) => ({
...prev,
name: event.target.value,
}))
}
disabled={!orgStates.isEdit}
sx={{
color: theme.palette.otherColors.bluishGray,
"& .Mui-disabled": {
WebkitTextFillColor: "initial !important",
},
"& .Mui-disabled fieldset": {
borderColor: "transparent !important",
},
}}
inputProps={{
sx: { textAlign: "end", padding: theme.spacing(4) },
}}
/>
<Button
level={orgStates.isEdit ? "secondary" : "tertiary"}
label={orgStates.isEdit ? "Save" : ""}
img={!orgStates.isEdit ? <EditSvg /> : ""}
onClick={() => toggleEdit()}
sx={{
minWidth: 0,
paddingX: theme.spacing(4),
ml: orgStates.isEdit ? theme.spacing(4) : 0,
}}
/>
</Stack>
</Stack>
<Divider aria-hidden="true" sx={{ marginY: theme.spacing(4) }} /> */}
<Stack
component="form"
noValidate
spellCheck="false"
gap={theme.spacing(12)}
gap={SPACING_GAP}
>
<Typography component="h1">Team members</Typography>
<Stack
@@ -315,17 +232,17 @@ const TeamPanel = () => {
<LoadingButton
loading={isSendingInvite}
variant="contained"
color="primary"
color="accent"
onClick={() => setIsOpen(true)}
>
Invite a team member
</LoadingButton>
</Stack>
<BasicTable
data={tableData}
paginated={false}
reversed={true}
table={"team"}
<DataTable
headers={headers}
data={data}
config={{ emptyView: "There are no team members with this role" }}
/>
</Stack>
@@ -339,7 +256,7 @@ const TeamPanel = () => {
theme={theme}
>
<TextInput
marginBottom={theme.spacing(12)}
marginBottom={SPACING_GAP}
type="email"
id="input-team-member"
placeholder="Email"
@@ -372,15 +289,15 @@ const TeamPanel = () => {
>
<LoadingButton
loading={isSendingInvite}
variant="text"
color="info"
variant="contained" // CAIO_REVIEW
color="error"
onClick={closeInviteModal}
>
Cancel
</LoadingButton>
<LoadingButton
variant="contained"
color="primary"
color="accent"
onClick={handleInviteMember}
loading={isSendingInvite}
disabled={isDisabled}
@@ -1,9 +1,9 @@
import PropTypes from "prop-types";
import { Box, Button } from "@mui/material";
import LeftArrowDouble from "../../../../../assets/icons/left-arrow-double.svg?react";
import RightArrowDouble from "../../../../../assets/icons/right-arrow-double.svg?react";
import LeftArrow from "../../../../../assets/icons/left-arrow.svg?react";
import RightArrow from "../../../../../assets/icons/right-arrow.svg?react";
import LeftArrowDouble from "../../../../assets/icons/left-arrow-double.svg?react";
import RightArrowDouble from "../../../../assets/icons/right-arrow-double.svg?react";
import LeftArrow from "../../../../assets/icons/left-arrow.svg?react";
import RightArrow from "../../../../assets/icons/right-arrow.svg?react";
TablePaginationActions.propTypes = {
count: PropTypes.number.isRequired,
@@ -2,14 +2,14 @@ import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { Stack, TablePagination, Typography } from "@mui/material";
import { TablePaginationActions } from "./Actions";
import SelectorVertical from "../../../../assets/icons/selector-vertical.svg?react";
import SelectorVertical from "../../../assets/icons/selector-vertical.svg?react";
Pagination.propTypes = {
monitorCount: PropTypes.number.isRequired, // Total number of items for pagination.
itemCount: PropTypes.number.isRequired, // Total number of items for pagination.
page: PropTypes.number.isRequired, // Current page index.
rowsPerPage: PropTypes.number.isRequired, // Number of rows displayed per page.
handleChangePage: PropTypes.func.isRequired, // Function to handle page changes.
handleChangeRowsPerPage: PropTypes.func.isRequired, // Function to handle changes in rows per page.
handleChangeRowsPerPage: PropTypes.func, // Function to handle changes in rows per page.
};
const ROWS_PER_PAGE_OPTIONS = [5, 10, 15, 25];
@@ -18,6 +18,7 @@ const ROWS_PER_PAGE_OPTIONS = [5, 10, 15, 25];
* Pagination component for table navigation with customized styling and behavior.
*
* @param {object} props - Component properties.
* @param {string} props.paginationLabel - Label for the pagination.
* @param {number} props.monitorCount - Total number of monitors to paginate.
* @param {number} props.page - Current page index (0-based).
* @param {number} props.rowsPerPage - Number of rows to display per page.
@@ -26,7 +27,8 @@ const ROWS_PER_PAGE_OPTIONS = [5, 10, 15, 25];
* @returns {JSX.Element} The Pagination component.
*/
function Pagination({
monitorCount,
paginationLabel,
itemCount,
page,
rowsPerPage,
handleChangePage,
@@ -35,7 +37,7 @@ function Pagination({
const theme = useTheme();
const start = page * rowsPerPage + 1;
const end = Math.min(page * rowsPerPage + rowsPerPage, monitorCount);
const end = Math.min(page * rowsPerPage + rowsPerPage, itemCount);
const range = `${start} - ${end}`;
return (
@@ -51,11 +53,11 @@ function Pagination({
variant="body2"
sx={{ opacity: 0.7 }}
>
Showing {range} of {monitorCount} monitor(s)
Showing {range} of {itemCount} {paginationLabel}
</Typography>
<TablePagination
component="div"
count={monitorCount}
count={itemCount}
page={page}
onPageChange={handleChangePage}
rowsPerPage={rowsPerPage}
@@ -92,20 +94,27 @@ function Pagination({
minWidth: theme.spacing(20),
textAlign: "left",
"&.Mui-focused > div": {
backgroundColor: theme.palette.background.main,
backgroundColor: theme.palette.primary.main,
},
"& .MuiSelect-icon": {
// Add this style override
position: "absolute",
right: 0,
top: "50%",
transform: "translateY(-50%)",
},
},
},
}}
sx={{
color: theme.palette.text.secondary,
color: theme.palette.primary.contrastTextSecondary,
"& svg path": {
stroke: theme.palette.text.tertiary,
stroke: theme.palette.primary.contrastTextTertiary,
strokeWidth: 1.3,
},
"& .MuiSelect-select": {
border: 1,
borderColor: theme.palette.border.light,
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
},
}}
@@ -114,4 +123,4 @@ function Pagination({
);
}
export { Pagination };
export default Pagination;
+128
View File
@@ -0,0 +1,128 @@
import {
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
} from "@mui/material";
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
/**
* @typedef {Object} Header
* @property {number|string} id - The unique identifier for the header.
* @property {React.ReactNode} content - The content to display in the header cell.
* @property {Function} render - A function to render the cell content for a given row.
*/
/**
* @typedef {Object} Config
* @property {Function} onRowClick - A function to be called when a row is clicked, receiving the row data as an argument.
* @property {Object} rowSX - Style object for the table row.
*/
/**
* DataTable component renders a table with headers and data.
*
* @param {Object} props - The component props.
* @param {Header[]} props.headers - An array of header objects, each containing an `id`, `content`, and `render` function.
* @param {Array} props.data - An array of data objects, each representing a row.
* @returns {JSX.Element} The rendered table component.
*/
const DataTable = ({ headers, data, config = { emptyView: "No data" } }) => {
const theme = useTheme();
if ((headers?.length ?? 0) === 0) {
return "No data";
}
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,
fontSize: "12px",
},
"& :is(td)": {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastTextSecondary,
},
}}
>
<TableHead>
<TableRow>
{headers.map((header, index) => (
<TableCell
key={header.id}
align={index === 0 ? "left" : "center"}
>
{header.content}
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{(data?.length ?? 0) === 0 ? (
<TableRow>
<TableCell
colSpan={headers.length}
align="center"
>
{config.emptyView}
</TableCell>
</TableRow>
) : (
data.map((row) => {
const key = row.id || row._id || Math.random();
return (
<TableRow
key={key}
sx={config?.rowSX ?? {}}
onClick={config?.onRowClick ? () => config.onRowClick(row) : null}
>
{headers.map((header, index) => {
return (
<TableCell
align={index === 0 ? "left" : "center"}
key={header.id}
>
{header.render(row)}
</TableCell>
);
})}
</TableRow>
);
})
)}
</TableBody>
</Table>
</TableContainer>
);
};
DataTable.propTypes = {
headers: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
content: PropTypes.node.isRequired,
render: PropTypes.func.isRequired,
})
).isRequired,
data: PropTypes.array.isRequired,
config: PropTypes.shape({
onRowClick: PropTypes.func.isRequired,
rowSX: PropTypes.object,
emptyView: PropTypes.node,
}),
};
export default DataTable;
@@ -0,0 +1,98 @@
import { useTheme } from "@mui/material";
import "./index.css";
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 default SunAndMoonIcon;
@@ -0,0 +1,64 @@
.sun-and-moon > :is(.moon, .sun, .sun-beams) {
transform-origin: center;
}
.theme-toggle .sun-and-moon > .sun-beams {
stroke-width: 2px;
}
.theme-dark .sun-and-moon > .sun {
transform: scale(1.75);
}
.theme-dark .sun-and-moon > .sun-beams {
opacity: 0;
}
.theme-dark .sun-and-moon > .moon > circle {
transform: translateX(-7px);
}
@supports (cx: 1) {
.theme-dark .sun-and-moon > .moon > circle {
cx: 17;
transform: translateX(0);
}
}
@media (prefers-reduced-motion: no-preference) {
.sun-and-moon > .sun {
transition: transform 0.5s cubic-bezier(0.68, -0.55, 0.27, 1.55);
}
.sun-and-moon > .sun-beams {
transition:
transform 0.5s cubic-bezier(0.68, -0.55, 0.27, 1.55),
opacity 0.5s cubic-bezier(0.25, 0.1, 0.25, 1);
}
.sun-and-moon .moon > circle {
transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}
@supports (cx: 1) {
.sun-and-moon .moon > circle {
transition: cx 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}
}
.theme-dark .sun-and-moon > .sun {
transition-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1);
transition-duration: 0.25s;
transform: scale(1.75);
}
.theme-dark .sun-and-moon > .sun-beams {
transition-duration: 0.15s;
transform: rotateZ(-25deg);
}
.theme-dark .sun-and-moon > .moon > circle {
transition-duration: 0.5s;
transition-delay: 0.25s;
}
}
@@ -0,0 +1,51 @@
/**
* ThemeSwitch Component
* Dark and Light Theme Switch
* Original Code: https://web.dev/patterns/theming/theme-switch
* License: Apache License 2.0
* Copyright © Google LLC
*
* This code has been adapted for use in this project.
* Apache License: https://www.apache.org/licenses/LICENSE-2.0
*/
import { IconButton } from "@mui/material";
import SunAndMoonIcon from "./SunAndMoonIcon";
import { useDispatch, useSelector } from "react-redux";
import { setMode } from "../../Features/UI/uiSlice";
import "./index.css";
const ThemeSwitch = ({ width = 48, height = 48, color }) => {
const mode = useSelector((state) => state.ui.mode);
const dispatch = useDispatch();
const toggleTheme = () => {
dispatch(setMode(mode === "light" ? "dark" : "light"));
};
return (
<IconButton
id="theme-toggle"
title="Toggles light & dark"
className={`theme-${mode}`}
aria-label="auto"
aria-live="polite"
onClick={toggleTheme}
sx={{
width,
height,
display: "flex",
alignItems: "center",
justifyContent: "center",
"& svg >:is(circle, g)": {
fill: color,
stroke: color,
},
}}
>
<SunAndMoonIcon />
</IconButton>
);
};
export default ThemeSwitch;
@@ -40,7 +40,7 @@ export const checkEndpointResolution = createAsyncThunk(
const res = await networkService.checkEndpointResolution({
authToken: authToken,
monitorURL: monitorURL,
})
});
return res.data;
} catch (error) {
if (error.response && error.response.data) {
@@ -53,7 +53,7 @@ export const checkEndpointResolution = createAsyncThunk(
return thunkApi.rejectWithValue(payload);
}
}
)
);
export const getPagespeedMonitorById = createAsyncThunk(
"monitors/getMonitorById",
@@ -88,7 +88,6 @@ export const getPageSpeedByTeamId = createAsyncThunk(
teamId: user.teamId,
types: ["pagespeed"],
});
return res.data;
} catch (error) {
if (error.response && error.response.data) {
@@ -78,30 +78,6 @@ export const getUptimeMonitorById = createAsyncThunk(
}
);
export const getUptimeMonitorsByTeamId = createAsyncThunk(
"monitors/getMonitorsByTeamId",
async (token, thunkApi) => {
const user = jwtDecode(token);
try {
const res = await networkService.getMonitorsAndSummaryByTeamId({
authToken: token,
teamId: user.teamId,
types: ["http", "ping", "docker"],
});
return res.data;
} catch (error) {
if (error.response && error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
export const updateUptimeMonitor = createAsyncThunk(
"monitors/updateMonitor",
async (data, thunkApi) => {
@@ -257,26 +233,6 @@ const uptimeMonitorsSlice = createSlice({
},
extraReducers: (builder) => {
builder
// *****************************************************
// Monitors by teamId
// *****************************************************
.addCase(getUptimeMonitorsByTeamId.pending, (state) => {
state.isLoading = true;
})
.addCase(getUptimeMonitorsByTeamId.fulfilled, (state, action) => {
state.isLoading = false;
state.success = action.payload.msg;
state.monitorsSummary = action.payload.data;
})
.addCase(getUptimeMonitorsByTeamId.rejected, (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload
? action.payload.msg
: "Getting uptime monitors failed";
})
// *****************************************************
// Create Monitor
// *****************************************************
+1 -1
View File
@@ -14,7 +14,7 @@
margin-top: 50px;
}
.account button:not(.MuiIconButton-root) {
height: 34px;
min-height: 34px;
}
.account .field {
flex: 1;
+26 -12
View File
@@ -57,22 +57,27 @@ const Account = ({ open = "profile" }) => {
className="account"
px={theme.spacing(20)}
py={theme.spacing(12)}
backgroundColor={theme.palette.primary.main}
border={1}
borderColor={theme.palette.border.light}
borderColor={theme.palette.primary.lowContrast}
borderRadius={theme.shape.borderRadius}
backgroundColor={theme.palette.background.main}
>
<TabContext value={tab}>
<Box
sx={{
borderBottom: 1,
borderColor: theme.palette.border.light,
borderColor: theme.palette.primary.lowContrast,
"& .MuiTabs-root": { height: "fit-content", minHeight: "0" },
}}
>
<TabList
onChange={handleTabChange}
aria-label="account tabs"
sx={{
"& .MuiTabs-indicator": {
backgroundColor: theme.palette.tertiary.contrastText,
},
}}
>
{tabList.map((label, index) => (
<Tab
@@ -84,20 +89,29 @@ const Account = ({ open = "profile" }) => {
tabIndex={index}
sx={{
fontSize: 13,
color: theme.palette.text.tertiary,
color: theme.palette.tertiary.contrastText,
backgroundColor: theme.palette.tertiary.main,
textTransform: "none",
minWidth: "fit-content",
minHeight: 0,
paddingLeft: 0,
paddingY: theme.spacing(4),
paddingY: theme.spacing(6),
fontWeight: 400,
marginRight: theme.spacing(8),
"&:focus": {
borderBottom: `2px solid ${theme.palette.border.light}`,
borderBottom: "2px solid transparent",
borderRight: `1px solid ${theme.palette.primary.lowContrast}`,
"&:first-child": { borderTopLeftRadius: "8px" },
"&:last-child": { borderTopRightRadius: "8px", borderRight: 0 },
"&:focus-visible": {
color: theme.palette.primary.contrastText,
borderColor: theme.palette.tertiary.contrastText,
borderRightColor: theme.palette.primary.lowContrast,
},
"&.Mui-selected": {
backgroundColor: theme.palette.secondary.main,
color: theme.palette.secondary.contrastText,
borderColor: theme.palette.secondary.contrastText,
borderRightColor: theme.palette.primary.lowContrast,
},
"&:hover": {
borderBottom: `2px solid ${theme.palette.border.light}`,
borderColor: theme.palette.primary.lowContrast,
},
}}
/>
-359
View File
@@ -1,359 +0,0 @@
import { useTheme } from "@emotion/react";
import { Box, Stack, Typography } from "@mui/material";
import TextInput from "../../Components/Inputs/TextInput";
import Link from "../../Components/Link";
import "./index.css";
import { useDispatch, useSelector } from "react-redux";
import { createToast } from "../../Utils/toastUtils";
import PropTypes from "prop-types";
import LoadingButton from "@mui/lab/LoadingButton";
import { ConfigBox } from "../Settings/styled";
import { useNavigate } from "react-router";
import { getAppSettings, updateAppSettings } from "../../Features/Settings/settingsSlice";
import { useState, useEffect } from "react";
import Select from "../../Components/Inputs/Select";
import { advancedSettingsValidation } from "../../Validation/validation";
import { buildErrors, hasValidationErrors } from "../../Validation/error";
const AdvancedSettings = ({ isAdmin }) => {
const navigate = useNavigate();
useEffect(() => {
if (!isAdmin) {
navigate("/");
}
}, [navigate, isAdmin]);
const [errors, setErrors] = useState({});
const theme = useTheme();
const { authToken } = useSelector((state) => state.auth);
const dispatch = useDispatch();
const settings = useSelector((state) => state.settings);
const [localSettings, setLocalSettings] = useState({
apiBaseUrl: "",
logLevel: "debug",
systemEmailHost: "",
systemEmailPort: "",
systemEmailAddress: "",
systemEmailPassword: "",
jwtTTLNum: 99,
jwtTTLUnits: "days",
jwtTTL: "99d",
dbType: "",
redisHost: "",
redisPort: "",
pagespeedApiKey: "",
});
const parseJWTTTL = (data) => {
if (data.jwtTTL) {
const len = data.jwtTTL.length;
data.jwtTTLNum = data.jwtTTL.substring(0, len - 1);
data.jwtTTLUnits = unitItems.filter(
(itm) => itm._id == data.jwtTTL.substring(len - 1)
)[0].name;
}
};
useEffect(() => {
const getSettings = async () => {
const action = await dispatch(getAppSettings({ authToken }));
if (action.payload.success) {
parseJWTTTL(action.payload.data);
setLocalSettings(action.payload.data);
} else {
createToast({ body: "Failed to get settings" });
}
};
getSettings();
}, [authToken, dispatch]);
const logItems = [
{ _id: 1, name: "none" },
{ _id: 2, name: "debug" },
{ _id: 3, name: "error" },
{ _id: 4, name: "warn" },
];
const logItemLookup = {
none: 1,
debug: 2,
error: 3,
warn: 4,
};
const unitItemLookup = {
days: "d",
hours: "h",
};
const unitItems = Object.keys(unitItemLookup).map((key) => ({
_id: unitItemLookup[key],
name: key,
}));
const handleLogLevel = (e) => {
const id = e.target.value;
const newLogLevel = logItems.find((item) => item._id === id).name;
setLocalSettings({ ...localSettings, logLevel: newLogLevel });
};
const handleJWTTTLUnits = (e) => {
const id = e.target.value;
const newUnits = unitItems.find((item) => item._id === id).name;
setLocalSettings({ ...localSettings, jwtTTLUnits: newUnits });
};
const handleBlur = (event) => {
const { value, id } = event.target;
const { error } = advancedSettingsValidation.validate(
{ [id]: value },
{
abortEarly: false,
}
);
setErrors((prev) => {
return buildErrors(prev, id, error);
});
};
const handleChange = (event) => {
const { value, id } = event.target;
setLocalSettings({ ...localSettings, [id]: value });
};
const handleSave = async () => {
localSettings.jwtTTL =
localSettings.jwtTTLNum + unitItemLookup[localSettings.jwtTTLUnits];
if (hasValidationErrors(localSettings, advancedSettingsValidation, setErrors)) {
return;
}
const action = await dispatch(
updateAppSettings({ settings: localSettings, authToken })
);
let body = "";
if (action.payload.success) {
parseJWTTTL(action.payload.data);
setLocalSettings(action.payload.data);
body = "Settings saved successfully";
} else {
body = "Failed to save settings";
}
createToast({ body });
};
return (
<Box
className="settings"
style={{
paddingBottom: 0,
}}
>
<Stack
component="form"
gap={theme.spacing(12)}
noValidate
spellCheck="false"
>
<ConfigBox>
<Box>
<Typography component="h1">Client settings</Typography>
<Typography sx={{ mt: theme.spacing(2) }}>
Modify client settings here.
</Typography>
</Box>
<Stack gap={theme.spacing(20)}>
<TextInput
id="apiBaseUrl"
label="API URL Host"
value={localSettings.apiBaseUrl}
onChange={handleChange}
onBlur={handleBlur}
error={errors.apiBaseUrl ? true : false}
helperText={errors.apiBaseUrl}
/>
<Select
id="logLevel"
label="Log level"
name="logLevel"
items={logItems}
value={logItemLookup[localSettings.logLevel]}
onChange={handleLogLevel}
onBlur={handleBlur}
error={errors.logLevel}
/>
</Stack>
</ConfigBox>
<ConfigBox>
<Box>
<Typography component="h1">Email settings</Typography>
<Typography sx={{ mt: theme.spacing(2) }}>
Set your host email settings here. These settings are used for sending
system emails.
</Typography>
</Box>
<Stack gap={theme.spacing(20)}>
<TextInput
type="text"
id="systemEmailHost"
label="System email host"
name="systemEmailHost"
value={localSettings.systemEmailHost}
onChange={handleChange}
onBlur={handleBlur}
error={errors.systemEmailHost ? true : false}
helperText={errors.systemEmailHost}
/>
<TextInput
type="number"
id="systemEmailPort"
label="System email port"
name="systemEmailPort"
value={localSettings.systemEmailPort?.toString()}
onChange={handleChange}
onBlur={handleBlur}
error={errors.systemEmailPort ? true : false}
helperText={errors.systemEmailPort}
/>
<TextInput
type="email"
id="systemEmailAddress"
label="System email address"
name="systemEmailAddress"
value={localSettings.systemEmailAddress}
onChange={handleChange}
onBlur={handleBlur}
error={errors.systemEmailAddress ? true : false}
helperText={errors.systemEmailAddress}
/>
<TextInput
type="text"
id="systemEmailPassword"
label="System email password"
name="systemEmailPassword"
value={localSettings.systemEmailPassword}
onChange={handleChange}
onBlur={handleBlur}
error={errors.systemEmailPassword ? true : false}
helperText={errors.systemEmailPassword}
/>
</Stack>
</ConfigBox>
<ConfigBox>
<Box>
<Typography component="h1">Server settings</Typography>
<Typography sx={{ mt: theme.spacing(2) }}>
Modify server settings here.
</Typography>
</Box>
<Stack gap={theme.spacing(20)}>
<Stack
direction="row"
gap={theme.spacing(10)}
>
<TextInput
type="number"
id="jwtTTLNum"
label="JWT time to live"
name="jwtTTLNum"
value={localSettings.jwtTTLNum.toString()}
onChange={handleChange}
onBlur={handleBlur}
error={errors.jwtTTLNum ? true : false}
helperText={errors.jwtTTLNum}
/>
<Select
id="jwtTTLUnits"
label="JWT TTL Units"
name="jwtTTLUnits"
placeholder="Select time"
isHidden={true}
items={unitItems}
value={unitItemLookup[localSettings.jwtTTLUnits]}
onChange={handleJWTTTLUnits}
onBlur={handleBlur}
error={errors.jwtTTLUnits}
/>
</Stack>
<TextInput
type="text"
id="dbType"
label="Database type"
name="dbType"
value={localSettings.dbType}
onChange={handleChange}
onBlur={handleBlur}
error={errors.dbType ? true : false}
helperText={errors.dbType}
/>
<TextInput
type="text"
id="redisHost"
label="Redis host"
name="redisHost"
value={localSettings.redisHost}
onChange={handleChange}
onBlur={handleBlur}
error={errors.redisHost ? true : false}
helperText={errors.redisHost}
/>
<TextInput
type="number"
id="redisPort"
label="Redis port"
name="redisPort"
value={localSettings.redisPort?.toString()}
onChange={handleChange}
onBlur={handleBlur}
error={errors.redisPort ? true : false}
helperText={errors.redisPort}
/>
<TextInput
type="text"
id="pagespeedApiKey"
label="PageSpeed API key"
name="pagespeedApiKey"
value={localSettings.pagespeedApiKey}
onChange={handleChange}
onBlur={handleBlur}
error={errors.pagespeedApiKey ? true : false}
helperText={errors.pagespeedApiKey}
/>
</Stack>
</ConfigBox>
<ConfigBox>
<Box>
<Typography component="h1">About</Typography>
</Box>
<Box>
<Typography component="h2">BlueWave Uptime v1.0.0</Typography>
<Typography sx={{ mt: theme.spacing(2), mb: theme.spacing(6), opacity: 0.6 }}>
Developed by Bluewave Labs.
</Typography>
<Link
level="secondary"
url="https://github.com/bluewave-labs"
label="https://github.com/bluewave-labs"
/>
</Box>
</ConfigBox>
<Stack
direction="row"
justifyContent="flex-end"
>
<LoadingButton
loading={settings.isLoading || settings.authIsLoading}
variant="contained"
color="primary"
sx={{ px: theme.spacing(12), mt: theme.spacing(20) }}
onClick={handleSave}
>
Save
</LoadingButton>
</Stack>
</Stack>
</Box>
);
};
AdvancedSettings.propTypes = {
isAdmin: PropTypes.bool,
};
export default AdvancedSettings;
+25 -11
View File
@@ -5,10 +5,10 @@ import { useDispatch } from "react-redux";
import { useNavigate } from "react-router";
import { createToast } from "../../Utils/toastUtils";
import { forgotPassword } from "../../Features/Auth/authSlice";
import { IconBox } from "./styled";
import Background from "../../assets/Images/background-grid.svg?react";
import EmailIcon from "../../assets/icons/email.svg?react";
import Logo from "../../assets/icons/bwu-icon.svg?react";
import Logo from "../../assets/icons/checkmate-icon.svg?react";
import IconBox from "../../Components/IconBox";
import "./index.css";
const CheckEmail = () => {
@@ -94,7 +94,8 @@ const CheckEmail = () => {
fontWeight: 600,
fontSize: 22,
},
"& p": { color: theme.palette.text.accent, fontSize: 13.5 },
/* TODO font size from theme */
"& p": { color: theme.palette.primary.contrastTextSecondary, fontSize: 13.5 },
"& span": { fontSize: "inherit" },
}}
>
@@ -102,7 +103,7 @@ const CheckEmail = () => {
className="background-pattern-svg"
sx={{
"& svg g g:last-of-type path": {
stroke: theme.palette.border.light,
stroke: theme.palette.primary.lowContrast,
},
}}
>
@@ -115,7 +116,7 @@ const CheckEmail = () => {
gap={theme.spacing(4)}
>
<Logo style={{ borderRadius: theme.shape.borderRadius }} />
<Typography sx={{ userSelect: "none" }}>BlueWave Uptime</Typography>
<Typography sx={{ userSelect: "none" }}>Checkmate</Typography>
</Stack>
<Stack
width="100%"
@@ -129,8 +130,8 @@ const CheckEmail = () => {
"& > .MuiStack-root": {
border: 1,
borderRadius: theme.spacing(5),
borderColor: theme.palette.border.light,
backgroundColor: theme.palette.background.main,
borderColor: theme.palette.primary.lowContrast,
backgroundColor: theme.palette.primary.main,
padding: {
xs: theme.spacing(12),
sm: theme.spacing(20),
@@ -144,9 +145,22 @@ const CheckEmail = () => {
textAlign="center"
>
<Box>
<IconBox>
<EmailIcon alt="email icon" />
</IconBox>
<Stack
direction="row"
justifyContent="center"
>
<IconBox
height={48}
width={48}
minWidth={48}
borderRadius={12}
svgWidth={24}
svgHeight={24}
mb={theme.spacing(4)}
>
<EmailIcon alt="email icon" />
</IconBox>
</Stack>
<Typography component="h1">Check your email</Typography>
<Typography>
We sent a password reset link to{" "}
@@ -160,7 +174,7 @@ const CheckEmail = () => {
</Box>
<Button
variant="contained"
color="primary"
color="accent"
onClick={openMail}
sx={{
width: "100%",
+25 -11
View File
@@ -6,12 +6,12 @@ import { forgotPassword } from "../../Features/Auth/authSlice";
import { useEffect, useState } from "react";
import { credentials } from "../../Validation/validation";
import { useNavigate } from "react-router-dom";
import { IconBox } from "./styled";
import TextInput from "../../Components/Inputs/TextInput";
import Logo from "../../assets/icons/bwu-icon.svg?react";
import Logo from "../../assets/icons/checkmate-icon.svg?react";
import Key from "../../assets/icons/key.svg?react";
import Background from "../../assets/Images/background-grid.svg?react";
import LoadingButton from "@mui/lab/LoadingButton";
import IconBox from "../../Components/IconBox";
import "./index.css";
const ForgotPassword = () => {
@@ -95,8 +95,9 @@ const ForgotPassword = () => {
fontSize: 21,
},
"& p": {
/* TODO font size from theme */
fontSize: 14,
color: theme.palette.text.accent,
color: theme.palette.primary.contrastTextSecondary,
},
}}
>
@@ -104,7 +105,7 @@ const ForgotPassword = () => {
className="background-pattern-svg"
sx={{
"& svg g g:last-of-type path": {
stroke: theme.palette.border.light,
stroke: theme.palette.primary.lowContrast,
},
}}
>
@@ -117,7 +118,7 @@ const ForgotPassword = () => {
gap={theme.spacing(4)}
>
<Logo style={{ borderRadius: theme.shape.borderRadius }} />
<Typography sx={{ userSelect: "none" }}>BlueWave Uptime</Typography>
<Typography sx={{ userSelect: "none" }}>Checkmate</Typography>
</Stack>
<Stack
width="100%"
@@ -131,8 +132,8 @@ const ForgotPassword = () => {
"& > .MuiStack-root": {
border: 1,
borderRadius: theme.spacing(5),
borderColor: theme.palette.border.light,
backgroundColor: theme.palette.background.main,
borderColor: theme.palette.primary.lowContrast,
backgroundColor: theme.palette.primary.main,
padding: {
xs: theme.spacing(12),
sm: theme.spacing(20),
@@ -146,9 +147,22 @@ const ForgotPassword = () => {
textAlign="center"
>
<Box>
<IconBox>
<Key alt="password key icon" />
</IconBox>
<Stack
direction="row"
justifyContent="center"
>
<IconBox
height={48}
width={48}
minWidth={48}
borderRadius={12}
svgWidth={24}
svgHeight={24}
mb={theme.spacing(4)}
>
<Key alt="password key icon" />
</IconBox>
</Stack>
<Typography component="h1">Forgot password?</Typography>
<Typography>No worries, we&apos;ll send you reset instructions.</Typography>
</Box>
@@ -173,7 +187,7 @@ const ForgotPassword = () => {
/>
<LoadingButton
variant="contained"
color="primary"
color="accent"
loading={isLoading}
disabled={errors.email !== undefined}
onClick={handleSubmit}
-562
View File
@@ -1,562 +0,0 @@
import { useState, useEffect, useRef } from "react";
import { useNavigate } from "react-router-dom";
import { Box, Button, Stack, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import { credentials } from "../../Validation/validation";
import { login } from "../../Features/Auth/authSlice";
import LoadingButton from "@mui/lab/LoadingButton";
import { useDispatch, useSelector } from "react-redux";
import { createToast } from "../../Utils/toastUtils";
import { networkService } from "../../main";
import TextInput from "../../Components/Inputs/TextInput";
import { PasswordEndAdornment } from "../../Components/Inputs/TextInput/Adornments";
import Background from "../../assets/Images/background-grid.svg?react";
import Logo from "../../assets/icons/bwu-icon.svg?react";
import Mail from "../../assets/icons/mail.svg?react";
import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded";
import PropTypes from "prop-types";
import { logger } from "../../Utils/Logger";
import "./index.css";
const DEMO = import.meta.env.VITE_APP_DEMO;
/**
* Displays the initial landing page.
*
* @param {Object} props
* @param {Function} props.onContinue - Callback function to handle "Continue with Email" button click.
* @returns {JSX.Element}
*/
const LandingPage = ({ onContinue }) => {
const theme = useTheme();
return (
<>
<Stack
gap={{ xs: theme.spacing(8), sm: theme.spacing(12) }}
alignItems="center"
textAlign="center"
>
<Box>
<Typography component="h1">Log In</Typography>
<Typography>We are pleased to see you again!</Typography>
</Box>
<Box width="100%">
<Button
variant="outlined"
color="info"
onClick={onContinue}
sx={{
width: "100%",
"& svg": {
mr: theme.spacing(4),
"& path": {
stroke: theme.palette.other.icon,
},
},
"&:focus-visible": {
outline: `2px solid ${theme.palette.primary.main}`,
outlineOffset: `2px`,
},
}}
>
<Mail />
Continue with Email
</Button>
</Box>
<Box maxWidth={400}>
<Typography className="tos-p">
By continuing, you agree to our{" "}
<Typography
component="span"
onClick={() => {
window.open(
"https://bluewavelabs.ca/terms-of-service-open-source",
"_blank",
"noreferrer"
);
}}
sx={{
"&:hover": {
color: theme.palette.text.tertiary,
},
}}
>
Terms of Service
</Typography>{" "}
and{" "}
<Typography
component="span"
onClick={() => {
window.open(
"https://bluewavelabs.ca/privacy-policy-open-source",
"_blank",
"noreferrer"
);
}}
sx={{
"&:hover": {
color: theme.palette.text.tertiary,
},
}}
>
Privacy Policy.
</Typography>
</Typography>
</Box>
</Stack>
</>
);
};
LandingPage.propTypes = {
onContinue: PropTypes.func.isRequired,
};
/**
* Renders the first step of the login process.
*
* @param {Object} props
* @param {Object} props.form - Form state object.
* @param {Object} props.errors - Object containing form validation errors.
* @param {Function} props.onSubmit - Callback function to handle form submission.
* @param {Function} props.onChange - Callback function to handle form input changes.
* @param {Function} props.onBack - Callback function to handle "Back" button click.
* @returns {JSX.Element}
*/
const StepOne = ({ form, errors, onSubmit, onChange, onBack }) => {
const theme = useTheme();
const inputRef = useRef(null);
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
return (
<>
<Stack
gap={{ xs: theme.spacing(12), sm: theme.spacing(16) }}
textAlign="center"
>
<Box>
<Typography component="h1">Log In</Typography>
<Typography>Enter your email address</Typography>
</Box>
<Box
textAlign="left"
component="form"
noValidate
spellCheck={false}
onSubmit={onSubmit}
display="grid"
gap={{ xs: theme.spacing(12), sm: theme.spacing(16) }}
>
<TextInput
type="email"
id="login-email-input"
label="Email"
isRequired={true}
placeholder="jordan.ellis@domain.com"
autoComplete="email"
value={form.email}
onInput={(e) => (e.target.value = e.target.value.toLowerCase())}
onChange={onChange}
error={errors.email ? true : false}
helperText={errors.email}
ref={inputRef}
/>
<Stack
direction="row"
justifyContent="space-between"
>
<Button
variant="outlined"
color="info"
onClick={onBack}
sx={{
px: theme.spacing(5),
"& svg.MuiSvgIcon-root": {
mr: theme.spacing(3),
},
"&:focus-visible": {
outline: `2px solid ${theme.palette.primary.main}`,
outlineOffset: `2px`,
},
}}
>
<ArrowBackRoundedIcon />
Back
</Button>
<Button
variant="contained"
color="primary"
type="submit"
disabled={errors.email && true}
sx={{
width: "30%",
"&.Mui-focusVisible": {
outline: `2px solid ${theme.palette.primary.main}`,
outlineOffset: `2px`,
boxShadow: `none`,
},
}}
>
Continue
</Button>
</Stack>
</Box>
</Stack>
</>
);
};
StepOne.propTypes = {
form: PropTypes.object.isRequired,
errors: PropTypes.object.isRequired,
onSubmit: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
onBack: PropTypes.func.isRequired,
};
/**
* Renders the second step of the login process, including a password input field.
*
* @param {Object} props
* @param {Object} props.form - Form state object.
* @param {Object} props.errors - Object containing form validation errors.
* @param {Function} props.onSubmit - Callback function to handle form submission.
* @param {Function} props.onChange - Callback function to handle form input changes.
* @param {Function} props.onBack - Callback function to handle "Back" button click.
* @returns {JSX.Element}
*/
const StepTwo = ({ form, errors, onSubmit, onChange, onBack }) => {
const theme = useTheme();
const navigate = useNavigate();
const inputRef = useRef(null);
const authState = useSelector((state) => state.auth);
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
const handleNavigate = () => {
if (form.email !== "" && !errors.email) {
sessionStorage.setItem("email", form.email);
}
navigate("/forgot-password");
};
return (
<>
<Stack
gap={{ xs: theme.spacing(12), sm: theme.spacing(16) }}
position="relative"
textAlign="center"
>
<Box>
<Typography component="h1">Log In</Typography>
<Typography>Enter your password</Typography>
</Box>
<Box
component="form"
noValidate
spellCheck={false}
onSubmit={onSubmit}
textAlign="left"
mb={theme.spacing(5)}
sx={{
display: "grid",
gap: { xs: theme.spacing(12), sm: theme.spacing(16) },
}}
>
<TextInput
type="password"
id="login-password-input"
label="Password"
isRequired={true}
placeholder="••••••••••"
autoComplete="current-password"
value={form.password}
onChange={onChange}
error={errors.password ? true : false}
helperText={errors.password}
ref={inputRef}
endAdornment={<PasswordEndAdornment />}
/>
<Stack
direction="row"
justifyContent="space-between"
>
<Button
variant="outlined"
color="info"
onClick={onBack}
sx={{
px: theme.spacing(5),
"& svg.MuiSvgIcon-root": {
mr: theme.spacing(3),
},
"&:focus-visible": {
outline: `2px solid ${theme.palette.primary.main}`,
outlineOffset: `2px`,
},
}}
>
<ArrowBackRoundedIcon />
Back
</Button>
<LoadingButton
variant="contained"
color="primary"
type="submit"
loading={authState.isLoading}
disabled={errors.password && true}
sx={{
width: "30%",
"&.Mui-focusVisible": {
outline: `2px solid ${theme.palette.primary.main}`,
outlineOffset: `2px`,
boxShadow: `none`,
},
}}
>
Continue
</LoadingButton>
</Stack>
</Box>
<Box
textAlign="center"
sx={{
position: "absolute",
bottom: 0,
left: "50%",
transform: `translate(-50%, 150%)`,
}}
>
<Typography
className="forgot-p"
display="inline-block"
color={theme.palette.primary.main}
>
Forgot password?
</Typography>
<Typography
component="span"
color={theme.palette.primary.main}
ml={theme.spacing(2)}
sx={{ userSelect: "none" }}
onClick={handleNavigate}
>
Reset password
</Typography>
</Box>
</Stack>
</>
);
};
StepTwo.propTypes = {
form: PropTypes.object.isRequired,
errors: PropTypes.object.isRequired,
onSubmit: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
onBack: PropTypes.func.isRequired,
};
const Login = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const theme = useTheme();
const authState = useSelector((state) => state.auth);
const { authToken } = authState;
const idMap = {
"login-email-input": "email",
"login-password-input": "password",
};
const [form, setForm] = useState({
email: DEMO !== undefined ? "uptimedemo@demo.com" : "",
password: DEMO !== undefined ? "Demouser1!" : "",
});
const [errors, setErrors] = useState({});
const [step, setStep] = useState(0);
useEffect(() => {
if (authToken) {
navigate("/monitors");
return;
}
networkService
.doesSuperAdminExist()
.then((response) => {
if (response.data.data === false) {
navigate("/register");
}
})
.catch((error) => {
logger.error(error);
});
}, [authToken, navigate]);
const handleChange = (event) => {
const { value, id } = event.target;
const name = idMap[id];
setForm((prev) => ({
...prev,
[name]: value,
}));
const { error } = credentials.validate({ [name]: value }, { abortEarly: false });
setErrors((prev) => {
const prevErrors = { ...prev };
if (error) prevErrors[name] = error.details[0].message;
else delete prevErrors[name];
return prevErrors;
});
};
const handleSubmit = async (event) => {
event.preventDefault();
if (step === 1) {
const { error } = credentials.validate(
{ email: form.email },
{ abortEarly: false }
);
if (error) {
setErrors((prev) => ({ ...prev, email: error.details[0].message }));
createToast({ body: error.details[0].message });
} else {
setStep(2);
}
} else if (step === 2) {
const { error } = credentials.validate(form, { abortEarly: false });
if (error) {
// validation errors
const newErrors = {};
error.details.forEach((err) => {
newErrors[err.path[0]] = err.message;
});
setErrors(newErrors);
createToast({
body:
error.details && error.details.length > 0
? error.details[0].message
: "Error validating data.",
});
} else {
const action = await dispatch(login(form));
if (action.payload.success) {
navigate("/monitors");
createToast({
body: "Welcome back! You're successfully logged in.",
});
} else {
if (action.payload) {
if (action.payload.msg === "Incorrect password")
setErrors({
password: "The password you provided does not match our records",
});
// dispatch errors
createToast({
body: action.payload.msg,
});
} else {
// unknown errors
createToast({
body: "Unknown error.",
});
}
}
}
}
};
return (
<Stack
className="login-page auth"
overflow="hidden"
sx={{
"& h1": {
color: theme.palette.primary.main,
fontWeight: 600,
fontSize: 28,
},
"& p": { fontSize: 14, color: theme.palette.text.accent },
"& span": { fontSize: "inherit" },
}}
>
<Box
className="background-pattern-svg"
sx={{
"& svg g g:last-of-type path": {
stroke: theme.palette.border.light,
},
}}
>
<Background style={{ width: "100%" }} />
</Box>
<Stack
direction="row"
alignItems="center"
px={theme.spacing(12)}
gap={theme.spacing(4)}
>
<Logo style={{ borderRadius: theme.shape.borderRadius }} />
<Typography sx={{ userSelect: "none" }}>BlueWave Uptime</Typography>
</Stack>
<Stack
width="100%"
maxWidth={600}
flex={1}
justifyContent="center"
px={{ xs: theme.spacing(12), lg: theme.spacing(20) }}
pb={theme.spacing(20)}
mx="auto"
sx={{
"& > .MuiStack-root": {
border: 1,
borderRadius: theme.spacing(5),
borderColor: theme.palette.border.light,
backgroundColor: theme.palette.background.main,
padding: {
xs: theme.spacing(12),
sm: theme.spacing(20),
},
},
}}
>
{step === 0 ? (
<LandingPage onContinue={() => setStep(1)} />
) : step === 1 ? (
<StepOne
form={form}
errors={errors}
onSubmit={handleSubmit}
onChange={handleChange}
onBack={() => setStep(0)}
/>
) : (
step === 2 && (
<StepTwo
form={form}
errors={errors}
onSubmit={handleSubmit}
onChange={handleChange}
onBack={() => setStep(1)}
/>
)
)}
</Stack>
</Stack>
);
};
export default Login;
@@ -0,0 +1,96 @@
import { useRef, useEffect } from "react";
import { Box, Button, Stack, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import TextInput from "../../../../Components/Inputs/TextInput";
import PropTypes from "prop-types";
/**
* Renders the email step of the login process which includes an email field.
*
* @param {Object} props
* @param {Object} props.form - Form state object.
* @param {Object} props.errors - Object containing form validation errors.
* @param {Function} props.onSubmit - Callback function to handle form submission.
* @param {Function} props.onChange - Callback function to handle form input changes.
* @param {Function} props.onBack - Callback function to handle "Back" button click.
* @returns {JSX.Element}
*/
const EmailStep = ({ form, errors, onSubmit, onChange }) => {
const theme = useTheme();
const inputRef = useRef(null);
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
return (
<>
<Stack
gap={{ xs: theme.spacing(12), sm: theme.spacing(16) }}
textAlign="center"
position="relative"
>
<Box>
<Typography component="h1">Log In</Typography>
<Typography>Enter your email address</Typography>
</Box>
<Box
textAlign="left"
component="form"
noValidate
spellCheck={false}
onSubmit={onSubmit}
display="grid"
gap={{ xs: theme.spacing(12), sm: theme.spacing(16) }}
>
<TextInput
type="email"
id="login-email-input"
label="Email"
isRequired={true}
placeholder="jordan.ellis@domain.com"
autoComplete="email"
value={form.email}
onInput={(e) => (e.target.value = e.target.value.toLowerCase())}
onChange={onChange}
error={errors.email ? true : false}
helperText={errors.email}
ref={inputRef}
/>
<Stack
direction="row"
justifyContent="flex-end"
>
<Button
variant="contained"
color="accent"
type="submit"
disabled={errors.email && true}
sx={{
width: "30%",
"&.Mui-focusVisible": {
outline: `2px solid ${theme.palette.primary.main}`,
outlineOffset: `2px`,
boxShadow: `none`,
},
}}
>
Continue
</Button>
</Stack>
</Box>
</Stack>
</>
);
};
EmailStep.propTypes = {
form: PropTypes.object.isRequired,
errors: PropTypes.object.isRequired,
onSubmit: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
};
export default EmailStep;
@@ -0,0 +1,43 @@
import { Box, Typography, useTheme } from "@mui/material";
import PropTypes from "prop-types";
import { useNavigate } from "react-router";
const ForgotPasswordLabel = ({ email, errorEmail }) => {
const theme = useTheme();
const navigate = useNavigate();
const handleNavigate = () => {
if (email !== "" && !errorEmail) {
sessionStorage.setItem("email", email);
}
navigate("/forgot-password");
};
return (
<Box textAlign="center">
<Typography
className="forgot-p"
display="inline-block"
color={theme.palette.primary.main}
>
Forgot password?
</Typography>
<Typography
component="span"
color={theme.palette.accent.main}
ml={theme.spacing(2)}
sx={{ userSelect: "none" }}
onClick={handleNavigate}
>
Reset password
</Typography>
</Box>
);
};
ForgotPasswordLabel.proptype = {
email: PropTypes.string.isRequired,
emailError: PropTypes.string.isRequired,
};
export default ForgotPasswordLabel;
@@ -0,0 +1,124 @@
import { useRef, useEffect } from "react";
import { Box, Button, Stack, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import LoadingButton from "@mui/lab/LoadingButton";
import { useSelector } from "react-redux";
import TextInput from "../../../../Components/Inputs/TextInput";
import { PasswordEndAdornment } from "../../../../Components/Inputs/TextInput/Adornments";
import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded";
import PropTypes from "prop-types";
/**
* Renders the password step of the login process, including a password input field.
*
* @param {Object} props
* @param {Object} props.form - Form state object.
* @param {Object} props.errors - Object containing form validation errors.
* @param {Function} props.onSubmit - Callback function to handle form submission.
* @param {Function} props.onChange - Callback function to handle form input changes.
* @param {Function} props.onBack - Callback function to handle "Back" button click.
* @returns {JSX.Element}
*/
const PasswordStep = ({ form, errors, onSubmit, onChange, onBack }) => {
const theme = useTheme();
const inputRef = useRef(null);
const authState = useSelector((state) => state.auth);
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
return (
<>
<Stack
gap={{ xs: theme.spacing(12), sm: theme.spacing(16) }}
position="relative"
textAlign="center"
>
<Box>
<Typography component="h1">Log In</Typography>
<Typography>Enter your password</Typography>
</Box>
<Box
component="form"
noValidate
spellCheck={false}
onSubmit={onSubmit}
textAlign="left"
mb={theme.spacing(5)}
sx={{
display: "grid",
gap: { xs: theme.spacing(12), sm: theme.spacing(16) },
}}
>
<TextInput
type="password"
id="login-password-input"
label="Password"
isRequired={true}
placeholder="••••••••••"
autoComplete="current-password"
value={form.password}
onChange={onChange}
error={errors.password ? true : false}
helperText={errors.password}
ref={inputRef}
endAdornment={<PasswordEndAdornment />}
/>
<Stack
direction="row"
justifyContent="space-between"
>
<Button
variant="outlined"
color="info"
onClick={onBack}
sx={{
px: theme.spacing(5),
"& svg.MuiSvgIcon-root": {
mr: theme.spacing(3),
},
"&:focus-visible": {
outline: `2px solid ${theme.palette.primary.main}`,
outlineOffset: `2px`,
},
}}
>
<ArrowBackRoundedIcon />
Back
</Button>
<LoadingButton
variant="contained"
color="accent"
type="submit"
loading={authState.isLoading}
disabled={errors.password && true}
sx={{
width: "30%",
"&.Mui-focusVisible": {
outline: `2px solid ${theme.palette.primary.main}`,
outlineOffset: `2px`,
boxShadow: `none`,
},
}}
>
Continue
</LoadingButton>
</Stack>
</Box>
</Stack>
</>
);
};
PasswordStep.propTypes = {
form: PropTypes.object.isRequired,
errors: PropTypes.object.isRequired,
onSubmit: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
onBack: PropTypes.func.isRequired,
};
export default PasswordStep;
+224
View File
@@ -0,0 +1,224 @@
import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { Box, Stack, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import { credentials } from "../../../Validation/validation";
import { login } from "../../../Features/Auth/authSlice";
import { useDispatch, useSelector } from "react-redux";
import { createToast } from "../../../Utils/toastUtils";
import { networkService } from "../../../main";
import Background from "../../../assets/Images/background-grid.svg?react";
import Logo from "../../../assets/icons/checkmate-icon.svg?react";
import { logger } from "../../../Utils/Logger";
import "../index.css";
import EmailStep from "./Components/EmailStep";
import PasswordStep from "./Components/PasswordStep";
import ThemeSwitch from "../../../Components/ThemeSwitch";
import ForgotPasswordLabel from "./Components/ForgotPasswordLabel";
const DEMO = import.meta.env.VITE_APP_DEMO;
/**
* Displays the login page.
*/
const Login = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const theme = useTheme();
const authState = useSelector((state) => state.auth);
const { authToken } = authState;
const idMap = {
"login-email-input": "email",
"login-password-input": "password",
};
const [form, setForm] = useState({
email: DEMO !== undefined ? "uptimedemo@demo.com" : "",
password: DEMO !== undefined ? "Demouser1!" : "",
});
const [errors, setErrors] = useState({});
const [step, setStep] = useState(0);
useEffect(() => {
if (authToken) {
navigate("/uptime");
return;
}
networkService
.doesSuperAdminExist()
.then((response) => {
if (response.data.data === false) {
navigate("/register");
}
})
.catch((error) => {
logger.error(error);
});
}, [authToken, navigate]);
const handleChange = (event) => {
const { value, id } = event.target;
const name = idMap[id];
setForm((prev) => ({
...prev,
[name]: value,
}));
const { error } = credentials.validate({ [name]: value }, { abortEarly: false });
setErrors((prev) => {
const prevErrors = { ...prev };
if (error) prevErrors[name] = error.details[0].message;
else delete prevErrors[name];
return prevErrors;
});
};
const handleSubmit = async (event) => {
event.preventDefault();
if (step === 0) {
const { error } = credentials.validate(
{ email: form.email },
{ abortEarly: false }
);
if (error) {
setErrors((prev) => ({ ...prev, email: error.details[0].message }));
createToast({ body: error.details[0].message });
} else {
setStep(1);
}
} else if (step === 1) {
const { error } = credentials.validate(form, { abortEarly: false });
if (error) {
// validation errors
const newErrors = {};
error.details.forEach((err) => {
newErrors[err.path[0]] = err.message;
});
setErrors(newErrors);
createToast({
body:
error.details && error.details.length > 0
? error.details[0].message
: "Error validating data.",
});
} else {
const action = await dispatch(login(form));
if (action.payload.success) {
navigate("/uptime");
createToast({
body: "Welcome back! You're successfully logged in.",
});
} else {
if (action.payload) {
if (action.payload.msg === "Incorrect password")
setErrors({
password: "The password you provided does not match our records",
});
// dispatch errors
createToast({
body: action.payload.msg,
});
} else {
// unknown errors
createToast({
body: "Unknown error.",
});
}
}
}
}
};
return (
<Stack
className="login-page auth"
overflow="hidden"
sx={{
"& h1": {
color: theme.palette.primary.main,
fontWeight: 600,
fontSize: 28,
},
/* TODO set font size from theme */
"& p": { fontSize: 14, color: theme.palette.primary.contrastTextSecondary },
"& span": { fontSize: "inherit" },
}}
>
<Box
className="background-pattern-svg"
sx={{
"& svg g g:last-of-type path": {
stroke: theme.palette.primary.lowContrast,
},
}}
>
<Background style={{ width: "100%" }} />
</Box>
<Stack
direction="row"
alignItems="center"
px={theme.spacing(12)}
gap={theme.spacing(4)}
>
<Logo style={{ borderRadius: theme.shape.borderRadius }} />
<Typography sx={{ userSelect: "none" }}>Checkmate</Typography>
</Stack>
<Stack
width="100%"
maxWidth={600}
flex={1}
justifyContent="center"
px={{ xs: theme.spacing(12), lg: theme.spacing(20) }}
pb={theme.spacing(20)}
mx="auto"
rowGap={theme.spacing(8)}
sx={{
"& > .MuiStack-root": {
border: 1,
borderRadius: theme.spacing(5),
borderColor: theme.palette.primary.lowContrast,
backgroundColor: theme.palette.primary.main,
padding: {
xs: theme.spacing(12),
sm: theme.spacing(20),
},
},
}}
>
{step === 0 ? (
<EmailStep
form={form}
errors={errors}
onSubmit={handleSubmit}
onChange={handleChange}
/>
) : (
step === 1 && (
<PasswordStep
form={form}
errors={errors}
onSubmit={handleSubmit}
onChange={handleChange}
onBack={() => setStep(0)}
/>
)
)}
<ForgotPasswordLabel
email={form.email}
errorEmail={errors.email}
/>
<Box marginX={"auto"}>
<ThemeSwitch />
</Box>
</Stack>
</Stack>
);
};
export default Login;
+25 -12
View File
@@ -4,10 +4,10 @@ import { useNavigate } from "react-router";
import { useDispatch } from "react-redux";
import { clearAuthState } from "../../Features/Auth/authSlice";
import { clearUptimeMonitorState } from "../../Features/UptimeMonitors/uptimeMonitorsSlice";
import { IconBox } from "./styled";
import Background from "../../assets/Images/background-grid.svg?react";
import ConfirmIcon from "../../assets/icons/check-outlined.svg?react";
import Logo from "../../assets/icons/bwu-icon.svg?react";
import Logo from "../../assets/icons/checkmate-icon.svg?react";
import IconBox from "../../Components/IconBox";
import "./index.css";
const NewPasswordConfirmed = () => {
@@ -31,14 +31,15 @@ const NewPasswordConfirmed = () => {
fontWeight: 600,
fontSize: 21,
},
"& p": { fontSize: 13.5, color: theme.palette.text.accent },
/* TODO font size from theme*/
"& p": { fontSize: 13.5, color: theme.palette.primary.contrastTextSecondary },
}}
>
<Box
className="background-pattern-svg"
sx={{
"& svg g g:last-of-type path": {
stroke: theme.palette.border.light,
stroke: theme.palette.primary.lowContrast,
},
}}
>
@@ -51,7 +52,7 @@ const NewPasswordConfirmed = () => {
gap={theme.spacing(4)}
>
<Logo style={{ borderRadius: theme.shape.borderRadius }} />
<Typography sx={{ userSelect: "none" }}>BlueWave Uptime</Typography>
<Typography sx={{ userSelect: "none" }}>Checkmate</Typography>
</Stack>
<Stack
width="100%"
@@ -65,8 +66,8 @@ const NewPasswordConfirmed = () => {
"& > .MuiStack-root": {
border: 1,
borderRadius: theme.spacing(5),
borderColor: theme.palette.border.light,
backgroundColor: theme.palette.background.main,
borderColor: theme.palette.primary.lowContrast,
backgroundColor: theme.palette.primary.main,
padding: {
xs: theme.spacing(12),
sm: theme.spacing(20),
@@ -80,9 +81,22 @@ const NewPasswordConfirmed = () => {
textAlign="center"
>
<Box>
<IconBox>
<ConfirmIcon alt="password confirm icon" />
</IconBox>
<Stack
direction="row"
justifyContent="center"
>
<IconBox
height={48}
width={48}
minWidth={48}
borderRadius={12}
svgWidth={24}
svgHeight={24}
mb={theme.spacing(4)}
>
<ConfirmIcon alt="password confirm icon" />
</IconBox>
</Stack>
<Typography component="h1">Password reset</Typography>
<Typography mb={theme.spacing(2)}>
Your password has been successfully reset. Click below to log in magically.
@@ -90,8 +104,7 @@ const NewPasswordConfirmed = () => {
</Box>
<Button
variant="contained"
color="primary"
onClick={() => navigate("/monitors")}
onClick={() => navigate("/uptime")}
sx={{
width: "100%",
maxWidth: 400,
+11 -10
View File
@@ -12,7 +12,7 @@ import { credentials } from "../../../Validation/validation";
import { createToast } from "../../../Utils/toastUtils";
import { register } from "../../../Features/Auth/authSlice";
import Background from "../../../assets/Images/background-grid.svg?react";
import Logo from "../../../assets/icons/bwu-icon.svg?react";
import Logo from "../../../assets/icons/checkmate-icon.svg?react";
import Mail from "../../../assets/icons/mail.svg?react";
import "../index.css";
@@ -50,7 +50,7 @@ const LandingPage = ({ isSuperAdmin, onSignup }) => {
"& svg": {
mr: theme.spacing(4),
"& path": {
stroke: theme.palette.other.icon,
stroke: theme.palette.primary.contrastTextTertiary,
},
},
"&:focus-visible": {
@@ -77,7 +77,7 @@ const LandingPage = ({ isSuperAdmin, onSignup }) => {
}}
sx={{
"&:hover": {
color: theme.palette.text.tertiary,
color: theme.palette.primary.contrastTextTertiary,
},
}}
>
@@ -95,7 +95,7 @@ const LandingPage = ({ isSuperAdmin, onSignup }) => {
}}
sx={{
"&:hover": {
color: theme.palette.text.tertiary,
color: theme.palette.primary.contrastTextTertiary,
},
}}
>
@@ -237,7 +237,7 @@ const Register = ({ isSuperAdmin }) => {
if (action.payload.success) {
const authToken = action.payload.data;
localStorage.setItem("token", authToken);
navigate("/monitors");
navigate("/uptime");
createToast({
body: "Welcome! Your account was created successfully.",
});
@@ -285,7 +285,8 @@ const Register = ({ isSuperAdmin }) => {
fontWeight: 600,
fontSize: 28,
},
"& p": { fontSize: 14, color: theme.palette.text.accent },
/* TODO set fontsize from theme */
"& p": { fontSize: 14, color: theme.palette.primary.contrastTextSecondary },
"& span": { fontSize: "inherit" },
}}
>
@@ -293,7 +294,7 @@ const Register = ({ isSuperAdmin }) => {
className="background-pattern-svg"
sx={{
"& svg g g:last-of-type path": {
stroke: theme.palette.border.light,
stroke: theme.palette.primary.lowContrast,
},
}}
>
@@ -306,7 +307,7 @@ const Register = ({ isSuperAdmin }) => {
gap={theme.spacing(4)}
>
<Logo style={{ borderRadius: theme.shape.borderRadius }} />
<Typography sx={{ userSelect: "none" }}>BlueWave Uptime</Typography>
<Typography sx={{ userSelect: "none" }}>Checkmate</Typography>
</Stack>
<Stack
width="100%"
@@ -320,8 +321,8 @@ const Register = ({ isSuperAdmin }) => {
"& > .MuiStack-root": {
border: 1,
borderRadius: theme.spacing(5),
borderColor: theme.palette.border.light,
backgroundColor: theme.palette.background.main,
borderColor: theme.palette.primary.lowContrast,
backgroundColor: theme.palette.primary.main,
padding: {
xs: theme.spacing(12),
sm: theme.spacing(20),
@@ -111,7 +111,7 @@ function StepOne({ form, errors, onSubmit, onChange, onBack }) {
</Button>
<Button
variant="contained"
color="primary"
color="accent"
type="submit"
disabled={(errors.firstName || errors.lastName) && true}
sx={{
@@ -31,7 +31,6 @@ function StepThree({ onSubmit, onBack }) {
}, []);
const { handleChange, feedbacks, form, errors } = useValidatePassword();
console.log(errors);
return (
<>
<Stack
@@ -135,7 +134,7 @@ function StepThree({ onSubmit, onBack }) {
mr: theme.spacing(3),
},
":focus-visible": {
outline: `2px solid ${theme.palette.primary.dark}`,
outline: `2px solid ${theme.palette.primary.lowContrast}`,
outlineOffset: "4px",
},
}}
@@ -146,7 +145,7 @@ function StepThree({ onSubmit, onBack }) {
<Button
type="submit"
variant="contained"
color="primary"
color="accent"
disabled={
form.password.length === 0 ||
form.confirm.length === 0 ||
@@ -93,7 +93,7 @@ function StepTwo({ form, errors, onSubmit, onChange, onBack }) {
</Button>
<Button
variant="contained"
color="primary"
color="accent"
onClick={onSubmit}
disabled={errors.email && true}
sx={{
+25 -12
View File
@@ -11,10 +11,9 @@ import { credentials } from "../../Validation/validation";
import Check from "../../Components/Check/Check";
import TextInput from "../../Components/Inputs/TextInput";
import { PasswordEndAdornment } from "../../Components/Inputs/TextInput/Adornments";
import { IconBox } from "./styled";
import IconBox from "../../Components/IconBox";
import LockIcon from "../../assets/icons/lock.svg?react";
import Logo from "../../assets/icons/bwu-icon.svg?react";
import Logo from "../../assets/icons/checkmate-icon.svg?react";
import Background from "../../assets/Images/background-grid.svg?react";
import "./index.css";
import { useValidatePassword } from "./hooks/useValidatePassword";
@@ -75,8 +74,9 @@ const SetNewPassword = () => {
fontSize: 24,
},
"& p": {
/* TODO font size from theme */
fontSize: 14,
color: theme.palette.text.accent,
color: theme.palette.primary.contrastTextSecondary,
},
}}
>
@@ -84,7 +84,7 @@ const SetNewPassword = () => {
className="background-pattern-svg"
sx={{
"& svg g g:last-of-type path": {
stroke: theme.palette.border.light,
stroke: theme.palette.primary.lowContrast,
},
}}
>
@@ -97,7 +97,7 @@ const SetNewPassword = () => {
gap={theme.spacing(4)}
>
<Logo style={{ borderRadius: theme.shape.borderRadius }} />
<Typography sx={{ userSelect: "none" }}>BlueWave Uptime</Typography>
<Typography sx={{ userSelect: "none" }}>Checkmate</Typography>
</Stack>
<Stack
width="100%"
@@ -111,8 +111,8 @@ const SetNewPassword = () => {
"& > .MuiStack-root": {
border: 1,
borderRadius: theme.spacing(5),
borderColor: theme.palette.border.light,
backgroundColor: theme.palette.background.main,
borderColor: theme.palette.primary.lowContrast,
backgroundColor: theme.palette.primary.main,
padding: {
xs: theme.spacing(12),
sm: theme.spacing(20),
@@ -126,9 +126,22 @@ const SetNewPassword = () => {
textAlign="center"
>
<Box>
<IconBox>
<LockIcon alt="lock icon" />
</IconBox>
<Stack
direction="row"
justifyContent="center"
>
<IconBox
height={48}
width={48}
minWidth={48}
borderRadius={12}
svgWidth={24}
svgHeight={24}
mb={theme.spacing(4)}
>
<LockIcon alt="lock icon" />
</IconBox>
</Stack>
<Typography component="h1">Set new password</Typography>
<Typography>
Your new password must be different to previously used passwords.
@@ -221,7 +234,7 @@ const SetNewPassword = () => {
</Box>
<LoadingButton
variant="contained"
color="primary"
color="accent"
loading={isLoading}
onClick={handleSubmit}
disabled={
-26
View File
@@ -1,26 +0,0 @@
import { Box, styled } from "@mui/material";
export const IconBox = styled(Box)(({ theme }) => ({
height: 48,
minWidth: 48,
width: 48,
position: "relative",
border: 1,
borderStyle: "solid",
borderColor: theme.palette.border.dark,
borderRadius: 12,
backgroundColor: theme.palette.background.accent,
margin: "auto",
marginBottom: 8,
"& svg": {
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 24,
height: 24,
"& path": {
stroke: theme.palette.text.tertiary,
},
},
}));
@@ -16,7 +16,7 @@ const Empty = ({ styles, mode }) => {
</Box>
<Typography
textAlign="center"
color={theme.palette.text.secondary}
color={theme.palette.primary.contrastTextSecondary}
>
No incidents recorded yet.
</Typography>
@@ -1,20 +1,6 @@
import PropTypes from "prop-types";
import {
TableContainer,
Table,
TableHead,
TableRow,
TableCell,
TableBody,
Pagination,
PaginationItem,
Paper,
Typography,
Box,
} from "@mui/material";
import { Typography, Box } from "@mui/material";
import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded";
import ArrowForwardRoundedIcon from "@mui/icons-material/ArrowForwardRounded";
import { useState, useEffect } from "react";
import { useSelector } from "react-redux";
import { networkService } from "../../../main";
@@ -27,8 +13,10 @@ import PlaceholderDark from "../../../assets/Images/data_placeholder_dark.svg?re
import { HttpStatusLabel } from "../../../Components/HttpStatusLabel";
import { Empty } from "./Empty/Empty";
import { IncidentSkeleton } from "./Skeleton/Skeleton";
import DataTable from "../../../Components/Table";
import Pagination from "../../../Components/Table/TablePagination";
const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
const IncidentTable = ({ monitors, selectedMonitor, filter, dateRange }) => {
const uiTimezone = useSelector((state) => state.ui.timezone);
const theme = useTheme();
@@ -36,19 +24,10 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
const mode = useSelector((state) => state.ui.mode);
const [checks, setChecks] = useState([]);
const [checksCount, setChecksCount] = useState(0);
const [paginationController, setPaginationController] = useState({
page: 0,
rowsPerPage: 14,
});
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(10);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
setPaginationController((prevPaginationController) => ({
...prevPaginationController,
page: 0,
}));
}, [filter, selectedMonitor]);
useEffect(() => {
const fetchPage = async () => {
if (!monitors || Object.keys(monitors).length === 0) {
@@ -63,10 +42,10 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
teamId: user.teamId,
sortOrder: "desc",
limit: null,
dateRange: null,
dateRange,
filter: filter,
page: paginationController.page,
rowsPerPage: paginationController.rowsPerPage,
page: page,
rowsPerPage: rowsPerPage,
});
} else {
res = await networkService.getChecksByMonitor({
@@ -74,12 +53,13 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
monitorId: selectedMonitor,
sortOrder: "desc",
limit: null,
dateRange: null,
dateRange,
filter: filter,
page: paginationController.page,
rowsPerPage: paginationController.rowsPerPage,
page,
rowsPerPage,
});
}
setChecks(res.data.data.checks);
setChecksCount(res.data.data.checksCount);
} catch (error) {
@@ -89,50 +69,61 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
}
};
fetchPage();
}, [
authToken,
user,
monitors,
selectedMonitor,
filter,
paginationController.page,
paginationController.rowsPerPage,
]);
}, [authToken, user, monitors, selectedMonitor, filter, page, rowsPerPage, dateRange]);
const handlePageChange = (_, newPage) => {
setPaginationController({
...paginationController,
page: newPage - 1, // 0-indexed
});
setPage(newPage);
};
let paginationComponent = <></>;
if (checksCount > paginationController.rowsPerPage) {
paginationComponent = (
<Pagination
count={Math.ceil(checksCount / paginationController.rowsPerPage)}
page={paginationController.page + 1} //0-indexed
onChange={handlePageChange}
shape="rounded"
renderItem={(item) => (
<PaginationItem
slots={{
previous: ArrowBackRoundedIcon,
next: ArrowForwardRoundedIcon,
}}
{...item}
const handleChangeRowsPerPage = (event) => {
setRowsPerPage(event.target.value);
};
const headers = [
{
id: "monitorName",
content: "Monitor Name",
render: (row) => monitors[row.monitorId]?.name ?? "N/A",
},
{
id: "status",
content: "Status",
render: (row) => {
const status = row.status === true ? "up" : "down";
return (
<StatusLabel
status={status}
text={status}
customStyles={{ textTransform: "capitalize" }}
/>
)}
sx={{ mt: "auto" }}
/>
);
}
);
},
},
{
id: "dateTime",
content: "Date & Time",
render: (row) => {
const formattedDate = formatDateWithTz(
row.createdAt,
"YYYY-MM-DD HH:mm:ss A",
uiTimezone
);
return formattedDate;
},
},
{
id: "statusCode",
content: "Status Code",
render: (row) => <HttpStatusLabel status={row.statusCode} />,
},
{ id: "message", content: "Message", render: (row) => row.message },
];
let sharedStyles = {
border: 1,
borderColor: theme.palette.border.light,
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
backgroundColor: theme.palette.background.main,
backgroundColor: theme.palette.primary.main,
p: theme.spacing(30),
};
@@ -159,55 +150,25 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
</Box>
<Typography
textAlign="center"
color={theme.palette.text.secondary}
color={theme.palette.primary.contrastTextSecondary}
>
The monitor you have selected has no recorded incidents yet.
</Typography>
</Box>
) : (
<>
<TableContainer component={Paper}>
<Table>
<TableHead>
<TableRow>
<TableCell>Monitor Name</TableCell>
<TableCell>Status</TableCell>
<TableCell>Date & Time</TableCell>
<TableCell>Status Code</TableCell>
<TableCell>Message</TableCell>
</TableRow>
</TableHead>
<TableBody>
{checks.map((check) => {
const status = check.status === true ? "up" : "down";
const formattedDate = formatDateWithTz(
check.createdAt,
"YYYY-MM-DD HH:mm:ss A",
uiTimezone
);
return (
<TableRow key={check._id}>
<TableCell>{monitors[check.monitorId]?.name}</TableCell>
<TableCell>
<StatusLabel
status={status}
text={status}
customStyles={{ textTransform: "capitalize" }}
/>
</TableCell>
<TableCell>{formattedDate}</TableCell>
<TableCell>
<HttpStatusLabel status={check.statusCode} />
</TableCell>
<TableCell>{check.message}</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
{paginationComponent}
<DataTable
headers={headers}
data={checks}
/>
<Pagination
paginationLabel="incidents"
itemCount={checksCount}
page={page}
rowsPerPage={rowsPerPage}
handleChangePage={handlePageChange}
handleChangeRowsPerPage={handleChangeRowsPerPage}
/>
</>
)}
</>
@@ -218,6 +179,7 @@ IncidentTable.propTypes = {
monitors: PropTypes.object.isRequired,
selectedMonitor: PropTypes.string.isRequired,
filter: PropTypes.string.isRequired,
dateRange: PropTypes.string.isRequired,
};
export default IncidentTable;
-11
View File
@@ -1,11 +0,0 @@
.incidents h1.MuiTypography-root {
font-size: var(--env-var-font-size-large);
font-weight: 600;
}
.incidents button.MuiButtonBase-root,
.incidents p.MuiTypography-root {
font-size: var(--env-var-font-size-medium);
}
.incidents button.MuiButtonBase-root {
height: 34px;
}
+117 -47
View File
@@ -8,7 +8,6 @@ import { useTheme } from "@emotion/react";
import Select from "../../Components/Inputs/Select";
import IncidentTable from "./IncidentTable";
import SkeletonLayout from "./skeleton";
import "./index.css";
const Incidents = () => {
const theme = useTheme();
@@ -21,6 +20,7 @@ const Incidents = () => {
// TODO do something with these filters
const [filter, setFilter] = useState("all");
const [dateRange, setDateRange] = useState("hour");
useEffect(() => {
const fetchMonitors = async () => {
@@ -29,7 +29,7 @@ const Incidents = () => {
const res = await networkService.getMonitorsByTeamId({
authToken: authState.authToken,
teamId: authState.user.teamId,
limit: -1,
limit: null,
types: null,
status: null,
checkOrder: null,
@@ -40,7 +40,6 @@ const Incidents = () => {
field: null,
order: null,
});
// Reduce to a lookup object for 0(1) lookup
if (res?.data?.data?.monitors?.length > 0) {
const monitorLookup = res.data.data.monitors.reduce((acc, monitor) => {
acc[monitor._id] = monitor;
@@ -56,7 +55,7 @@ const Incidents = () => {
}
};
fetchMonitors();
}, [authState]);
}, [authState, monitorId]);
useEffect(() => {}, [monitors]);
@@ -78,61 +77,132 @@ const Incidents = () => {
<>
<Stack
direction="row"
alignItems="center"
justifyContent="space-between"
gap={theme.spacing(6)}
>
<Typography
display="inline-block"
component="h1"
color={theme.palette.text.secondary}
<Stack
direction="row"
alignItems="center"
gap={theme.spacing(6)}
>
Incidents for
</Typography>
<Select
id="incidents-select-monitor"
placeholder="All servers"
value={selectedMonitor}
onChange={handleSelect}
items={Object.values(monitors)}
sx={{
backgroundColor: theme.palette.background.main,
}}
/>
<ButtonGroup
sx={{
ml: "auto",
"& .MuiButtonBase-root, & .MuiButtonBase-root:hover": {
borderColor: theme.palette.border.light,
},
}}
<Typography
display="inline-block"
component="h1"
color={theme.palette.primary.contrastTextSecondary}
>
Incidents for
</Typography>
<Select
id="incidents-select-monitor"
placeholder="All servers"
value={selectedMonitor}
onChange={handleSelect}
items={Object.values(monitors)}
sx={{
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastTextSecondary,
}}
/>
</Stack>
<Stack
direction="row"
alignItems="center"
gap={theme.spacing(6)}
>
<Button
variant="group"
filled={(filter === "all").toString()}
onClick={() => setFilter("all")}
<Typography
display="inline-block"
component="h1"
color={theme.palette.primary.contrastTextSecondary}
>
All
</Button>
<Button
variant="group"
filled={(filter === "down").toString()}
onClick={() => setFilter("down")}
Filter by:
</Typography>
<ButtonGroup
sx={{
ml: "auto",
"& .MuiButtonBase-root, & .MuiButtonBase-root:hover": {
borderColor: theme.palette.primary.lowContrast,
},
}}
>
Down
</Button>
<Button
variant="group"
filled={(filter === "resolve").toString()}
onClick={() => setFilter("resolve")}
<Button
variant="group"
filled={(filter === "all").toString()}
onClick={() => setFilter("all")}
>
All
</Button>
<Button
variant="group"
filled={(filter === "down").toString()}
onClick={() => setFilter("down")}
>
Down
</Button>
<Button
variant="group"
filled={(filter === "resolve").toString()}
onClick={() => setFilter("resolve")}
>
Cannot resolve
</Button>
</ButtonGroup>
</Stack>
<Stack
direction="row"
alignItems="center"
gap={theme.spacing(6)}
>
<Typography
display="inline-block"
component="h1"
color={theme.palette.primary.contrastTextSecondary}
>
Cannot Resolve
</Button>
</ButtonGroup>
Show:
</Typography>
<ButtonGroup
sx={{
ml: "auto",
"& .MuiButtonBase-root, & .MuiButtonBase-root:hover": {
borderColor: theme.palette.primary.lowContrast,
},
}}
>
<Button
variant="group"
filled={(dateRange === "hour").toString()}
onClick={() => setDateRange("hour")}
>
Last hour
</Button>
<Button
variant="group"
filled={(dateRange === "day").toString()}
onClick={() => setDateRange("day")}
>
Last day
</Button>
<Button
variant="group"
filled={(dateRange === "week").toString()}
onClick={() => setDateRange("week")}
>
Last week
</Button>
<Button
variant="group"
filled={(dateRange === "all").toString()}
onClick={() => setDateRange("all")}
>
All
</Button>
</ButtonGroup>
</Stack>
</Stack>
<IncidentTable
monitors={monitors}
selectedMonitor={selectedMonitor}
filter={filter}
dateRange={dateRange}
/>
</>
)}
@@ -5,32 +5,58 @@ import { useTheme } from "@emotion/react";
import PropTypes from "prop-types";
/**
* `CustomThreshold` is a functional React component that displays a
* group of CheckBox with a label and its correspondant threshold input field.
* CustomThreshold Component
*
* @param {{ checkboxId: any; checkboxLabel: any; onCheckboxChange: any; fieldId: any; onFieldChange: any; onFieldBlur: any; alertUnit: any; infrastructureMonitor: any; errors: any; }} param0
* @param {string} param0.checkboxId - The text is the id of the checkbox.
* @param {string} param0.checkboxLabel - The text to be displayed as the label next to the check icon.
* @param {func} param0.onCheckboxChange - The function to invoke when checkbox is checked or unchecked.
* @param {string} param0.fieldId - The text is the id of the input field.
* @param {func} param0.onFieldChange - The function to invoke when input field is changed.
* @param {func} param0.onFieldBlur - The function to invoke when input field is losing focus.
* @param {string} param0.alertUnit the threshold unit such as usage percentage '%' etc
* @param {object} param0.infrastructureMonitor the form object of the create infrastrcuture monitor page
* @param {object} param0.errors the object that holds all the errors of the form page
* @returns A compound React component that renders the custom threshold alert section
* A reusable component that renders a checkbox with an associated numeric input field
* and an optional unit label. The input field can be enabled/disabled based on checkbox state.
*
* @component
* @param {Object} props - Component properties
* @param {string} [props.checkboxId] - Optional unique identifier for the checkbox
* @param {string} [props.checkboxName] - Optional name attribute for the checkbox
* @param {string} props.checkboxLabel - Label text for the checkbox
* @param {boolean} props.isChecked - Current checked state of the checkbox
* @param {Function} props.onCheckboxChange - Callback function when checkbox is toggled
* @param {string} props.fieldId - Unique identifier for the input field
* @param {string} [props.fieldName] - Optional name attribute for the input field
* @param {string} props.fieldValue - Current value of the input field
* @param {Function} props.onFieldChange - Callback function when input field value changes
* @param {Function} props.onFieldBlur - Callback function when input field loses focus
* @param {string} props.alertUnit - Unit label displayed next to the input field
* @param {Object} props.errors - Object containing validation errors for the field
* @param {Object} props.infrastructureMonitor - Infrastructure monitor configuration object
*
* @returns {React.ReactElement} Rendered CustomThreshold component
*
* @example
* <CustomThreshold
* checkboxId="cpu-threshold"
* checkboxName="cpu_threshold"
* checkboxLabel="Enable CPU Threshold"
* isChecked={true}
* onCheckboxChange={handleCheckboxToggle}
* fieldId="cpu-threshold-value"
* fieldName="cpu_threshold_value"
* fieldValue="80"
* onFieldChange={handleFieldChange}
* onFieldBlur={handleFieldBlur}
* alertUnit="%"
* errors={{}}
* infrastructureMonitor={monitorConfig}
* />
*/
export const CustomThreshold = ({
checkboxId,
checkboxName,
checkboxLabel,
onCheckboxChange,
isChecked,
fieldId,
fieldName,
fieldValue,
onFieldChange,
onFieldBlur,
alertUnit,
infrastructureMonitor,
errors,
}) => {
const theme = useTheme();
@@ -46,8 +72,9 @@ export const CustomThreshold = ({
<Box>
<Checkbox
id={checkboxId}
name={checkboxName}
label={checkboxLabel}
isChecked={infrastructureMonitor[checkboxId]}
isChecked={isChecked}
onChange={onCheckboxChange}
/>
</Box>
@@ -61,11 +88,12 @@ export const CustomThreshold = ({
maxWidth="var(--env-var-width-4)"
type="number"
id={fieldId}
value={infrastructureMonitor[fieldId]}
name={fieldName}
value={fieldValue}
onBlur={onFieldBlur}
onChange={onFieldChange}
error={errors[fieldId] ? true : false}
disabled={!infrastructureMonitor[checkboxId]}
disabled={!isChecked}
/>
<Typography
@@ -80,10 +108,14 @@ export const CustomThreshold = ({
};
CustomThreshold.propTypes = {
checkboxId: PropTypes.string.isRequired,
checkboxId: PropTypes.string,
checkboxName: PropTypes.string,
checkboxLabel: PropTypes.string.isRequired,
isChecked: PropTypes.bool.isRequired,
onCheckboxChange: PropTypes.func.isRequired,
fieldId: PropTypes.string.isRequired,
fieldName: PropTypes.string,
fieldValue: PropTypes.string.isRequired,
onFieldChange: PropTypes.func.isRequired,
onFieldBlur: PropTypes.func.isRequired,
alertUnit: PropTypes.string.isRequired,
@@ -1,27 +1,62 @@
import { useState } from "react";
import { Box, Stack, Typography } from "@mui/material";
import LoadingButton from "@mui/lab/LoadingButton";
import { useSelector, useDispatch } from "react-redux";
import { infrastructureMonitorValidation } from "../../../Validation/validation";
import { parseDomainName } from "../../../Utils/monitorUtils";
import {
createInfrastructureMonitor,
checkInfrastructureEndpointResolution,
} from "../../../Features/InfrastructureMonitors/infrastructureMonitorsSlice";
import { useNavigate } from "react-router-dom";
// React, Redux, Router
import { useTheme } from "@emotion/react";
import { createToast } from "../../../Utils/toastUtils";
import Link from "../../../Components/Link";
import { ConfigBox } from "../../Monitors/styled";
import TextInput from "../../../Components/Inputs/TextInput";
import Select from "../../../Components/Inputs/Select";
import Checkbox from "../../../Components/Inputs/Checkbox";
import Breadcrumbs from "../../../Components/Breadcrumbs";
import { buildErrors, hasValidationErrors } from "../../../Validation/error";
import { useNavigate } from "react-router-dom";
import { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
// Utility and Network
import { infrastructureMonitorValidation } from "../../../Validation/validation";
import { createInfrastructureMonitor } from "../../../Features/InfrastructureMonitors/infrastructureMonitorsSlice";
import { capitalizeFirstLetter } from "../../../Utils/stringUtils";
import { CustomThreshold } from "../CreateMonitor/CustomThreshold";
// MUI
import { Box, Stack, Typography, Button, ButtonGroup } from "@mui/material";
import LoadingButton from "@mui/lab/LoadingButton";
//Components
import Breadcrumbs from "../../../Components/Breadcrumbs";
import Link from "../../../Components/Link";
import ConfigBox from "../../../Components/ConfigBox";
import TextInput from "../../../Components/Inputs/TextInput";
import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments";
import { createToast } from "../../../Utils/toastUtils";
import Checkbox from "../../../Components/Inputs/Checkbox";
import Select from "../../../Components/Inputs/Select";
import { CustomThreshold } from "./CustomThreshold";
const SELECT_VALUES = [
{ _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" },
];
const METRICS = ["cpu", "memory", "disk", "temperature"];
const METRIC_PREFIX = "usage_";
const MS_PER_MINUTE = 60000;
const hasAlertError = (errors) => {
return Object.keys(errors).filter((k) => k.startsWith(METRIC_PREFIX)).length > 0;
};
const getAlertError = (errors) => {
return Object.keys(errors).find((key) => key.startsWith(METRIC_PREFIX))
? errors[Object.keys(errors).find((key) => key.startsWith(METRIC_PREFIX))]
: null;
};
const CreateInfrastructureMonitor = () => {
const theme = useTheme();
const { user, authToken } = useSelector((state) => state.auth);
const monitorState = useSelector((state) => state.infrastructureMonitor);
const dispatch = useDispatch();
const navigate = useNavigate();
// State
const [errors, setErrors] = useState({});
const [https, setHttps] = useState(false);
const [infrastructureMonitor, setInfrastructureMonitor] = useState({
url: "",
name: "",
@@ -38,118 +73,73 @@ const CreateInfrastructureMonitor = () => {
secret: "",
});
const MS_PER_MINUTE = 60000;
const THRESHOLD_FIELD_PREFIX = "usage_";
const HARDWARE_MONITOR_TYPES = ["cpu", "memory", "disk", "temperature"];
const { user, authToken } = useSelector((state) => state.auth);
const monitorState = useSelector((state) => state.infrastructureMonitor);
const dispatch = useDispatch();
const navigate = useNavigate();
const theme = useTheme();
const idMap = {
"notify-email-default": "notification-email",
};
const [errors, setErrors] = useState({});
const alertErrKeyLen = Object.keys(errors).filter((k) =>
k.startsWith(THRESHOLD_FIELD_PREFIX)
).length;
const handleCustomAlertCheckChange = (event) => {
const { value, id } = event.target;
setInfrastructureMonitor((prev) => {
const newState = {
[id]: prev[id] == undefined && value == "on" ? true : !prev[id],
};
return {
...prev,
...newState,
[THRESHOLD_FIELD_PREFIX + id]: newState[id]
? prev[THRESHOLD_FIELD_PREFIX + id]
: "",
};
});
// Remove the error if unchecked
setErrors((prev) => {
return buildErrors(prev, [THRESHOLD_FIELD_PREFIX + id]);
});
};
const handleBlur = (event, appendID) => {
// Handlers
const handleCreateInfrastructureMonitor = async (event) => {
event.preventDefault();
const { value, id } = event.target;
let name = idMap[id] ?? id;
if (name === "url" && infrastructureMonitor.name === "") {
setInfrastructureMonitor((prev) => ({
...prev,
name: parseDomainName(value),
}));
// Build the form
let form = {
url: `http${https ? "s" : ""}://` + infrastructureMonitor.url,
name:
infrastructureMonitor.name === ""
? infrastructureMonitor.url
: infrastructureMonitor.name,
interval: infrastructureMonitor.interval * MS_PER_MINUTE,
cpu: infrastructureMonitor.cpu,
...(infrastructureMonitor.cpu
? { usage_cpu: infrastructureMonitor.usage_cpu }
: {}),
memory: infrastructureMonitor.memory,
...(infrastructureMonitor.memory
? { usage_memory: infrastructureMonitor.usage_memory }
: {}),
disk: infrastructureMonitor.disk,
...(infrastructureMonitor.disk
? { usage_disk: infrastructureMonitor.usage_disk }
: {}),
temperature: infrastructureMonitor.temperature,
...(infrastructureMonitor.temperature
? { usage_temperature: infrastructureMonitor.usage_temperature }
: {}),
secret: infrastructureMonitor.secret,
};
const { error } = infrastructureMonitorValidation.validate(form, {
abortEarly: false,
});
if (error) {
const newErrors = {};
error.details.forEach((err) => {
newErrors[err.path[0]] = err.message;
});
setErrors(newErrors);
createToast({ body: "Please check the form for errors." });
return;
}
if (id?.startsWith("notify-email-")) return;
const { error } = infrastructureMonitorValidation.validate(
{ [id ?? appendID]: value },
{
abortEarly: false,
}
);
setErrors((prev) => {
return buildErrors(prev, id ?? appendID, error);
});
};
// Build the thresholds for the form
const {
cpu,
usage_cpu,
memory,
usage_memory,
disk,
usage_disk,
temperature,
usage_temperature,
...rest
} = form;
const handleChange = (event, appendedId) => {
event.preventDefault();
const { value, id } = event.target;
let name = appendedId ?? idMap[id] ?? id;
if (name.includes("notification-")) {
name = name.replace("notification-", "");
let hasNotif = infrastructureMonitor.notifications.some(
(notification) => notification.type === name
);
setInfrastructureMonitor((prev) => {
const notifs = [...prev.notifications];
if (hasNotif) {
return {
...prev,
notifications: notifs.filter((notif) => notif.type !== name),
};
} else {
return {
...prev,
notifications: [
...notifs,
name === "email"
? { type: name, address: value }
: // TODO - phone number
{ type: name, phone: value },
],
};
}
});
} else {
setInfrastructureMonitor((prev) => ({
...prev,
[name]: value,
}));
}
};
const generatePayload = (form) => {
let thresholds = {};
Object.keys(form)
.filter((k) => k.startsWith(THRESHOLD_FIELD_PREFIX))
.map((k) => {
if (form[k]) thresholds[k] = form[k] / 100;
delete form[k];
delete form[k.substring(THRESHOLD_FIELD_PREFIX.length)];
});
const 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 } : {}),
};
form = {
...form,
...rest,
description: form.name,
teamId: user.teamId,
userId: user._id,
@@ -157,61 +147,76 @@ const CreateInfrastructureMonitor = () => {
notifications: infrastructureMonitor.notifications,
thresholds,
};
return form;
};
const handleCreateInfrastructureMonitor = async (event) => {
event.preventDefault();
let form = {
...infrastructureMonitor,
name:
infrastructureMonitor.name === ""
? infrastructureMonitor.url
: infrastructureMonitor.name,
interval: infrastructureMonitor.interval * MS_PER_MINUTE,
};
delete form.notifications;
if (hasValidationErrors(form, infrastructureMonitorValidation, setErrors)) {
return;
const action = await dispatch(
createInfrastructureMonitor({ authToken, monitor: form })
);
if (action.meta.requestStatus === "fulfilled") {
createToast({ body: "Infrastructure monitor created successfully!" });
navigate("/infrastructure");
} else {
const checkEndpointAction = await dispatch(
checkInfrastructureEndpointResolution({ authToken, monitorURL: form.url })
);
if (checkEndpointAction.meta.requestStatus === "rejected") {
createToast({
body: "The endpoint you entered doesn't resolve. Check the URL again.",
});
setErrors({ url: "The entered URL is not reachable." });
return;
}
const action = await dispatch(
createInfrastructureMonitor({ authToken, monitor: generatePayload(form) })
);
if (action.meta.requestStatus === "fulfilled") {
createToast({ body: "Infrastructure monitor created successfully!" });
navigate("/infrastructure");
} else {
createToast({ body: "Failed to create monitor." });
}
createToast({ body: "Failed to create monitor." });
}
};
//select values
const frequencies = [
{ _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" },
];
const handleChange = (event) => {
const { value, name } = event.target;
setInfrastructureMonitor({
...infrastructureMonitor,
[name]: value,
});
const { error } = infrastructureMonitorValidation.validate(
{ [name]: value },
{ abortEarly: false }
);
setErrors((prev) => ({
...prev,
...(error ? { [name]: error.details[0].message } : { [name]: undefined }),
}));
};
const handleCheckboxChange = (event) => {
const { name } = event.target;
const { checked } = event.target;
setInfrastructureMonitor({
...infrastructureMonitor,
[name]: checked,
});
};
const handleNotifications = (event, type) => {
const { value } = event.target;
let notifications = [...infrastructureMonitor.notifications];
const notificationExists = notifications.some((notification) => {
if (notification.type === type && notification.address === value) {
return true;
}
return false;
});
if (notificationExists) {
notifications = notifications.filter((notification) => {
if (notification.type === type && notification.address === value) {
return false;
}
return true;
});
} else {
notifications.push({ type, address: value });
}
setInfrastructureMonitor((prev) => ({
...prev,
notifications,
}));
};
return (
<Box className="create-infrastructure-monitor">
<Breadcrumbs
list={[
{ name: "Infrastructure monitors", path: "infrastructure" },
{ name: "create", path: `infrastructure/create` },
{ name: "Infrastructure monitors", path: "/infrastructure" },
{ name: "create", path: `/infrastructure/create` },
]}
/>
<Stack
@@ -239,56 +244,75 @@ const CreateInfrastructureMonitor = () => {
fontSize="inherit"
fontWeight="inherit"
>
infrastructure monitor
monitor
</Typography>
</Typography>
<ConfigBox>
<Box>
<Stack gap={theme.spacing(6)}>
<Typography component="h2">General settings</Typography>
<Typography component="p">
Here you can select the URL of the host, together with the friendly name
and authorization secret to connect to the server agent.
</Typography>
<Typography component="p">
The server you are monitoring must be running the{" "}
<Link
level="primary"
url="https://github.com/bluewave-labs/checkmate-agent"
label="Checkmate Monitoring Agent"
/>
</Typography>
</Stack>
</Box>
<Stack gap={theme.spacing(6)}>
<Typography component="h2">General settings</Typography>
<Typography component="p">
Here you can select the URL of the host, together with the friendly name and
authorization secret to connect to the server agent.
</Typography>
<Typography component="p">
The server you are monitoring must be running the{" "}
<Link
level="primary"
url="https://github.com/bluewave-labs/checkmate-agent"
label="Checkmate Monitoring Agent"
/>
</Typography>
</Stack>
<Stack gap={theme.spacing(15)}>
<TextInput
type="text"
type="url"
id="url"
name="url"
startAdornment={<HttpAdornment https={https} />}
placeholder={"localhost:59232/api/v1/metrics"}
label="Server URL"
placeholder="https://"
https={https}
value={infrastructureMonitor.url}
onBlur={handleBlur}
onChange={handleChange}
error={errors["url"] ? true : false}
helperText={errors["url"]}
/>
<Box>
<Typography component="p">Protocol</Typography>
<ButtonGroup>
<Button
variant="group"
filled={https.toString()}
onClick={() => setHttps(true)}
>
HTTPS
</Button>
<Button
variant="group"
filled={(!https).toString()}
onClick={() => setHttps(false)}
>
HTTP
</Button>
</ButtonGroup>
</Box>
<TextInput
type="text"
id="name"
name="name"
label="Display name"
placeholder="Google"
isOptional={true}
value={infrastructureMonitor.name}
onBlur={handleBlur}
onChange={handleChange}
error={errors["name"]}
/>
<TextInput
type="text"
id="secret"
name="secret"
label="Authorization secret"
value={infrastructureMonitor.secret}
onBlur={handleBlur}
onChange={handleChange}
error={errors["secret"] ? true : false}
helperText={errors["secret"]}
@@ -303,7 +327,6 @@ const CreateInfrastructureMonitor = () => {
</Typography>
</Box>
<Stack gap={theme.spacing(6)}>
<Typography component="p">When there is a new incident,</Typography>
<Checkbox
id="notify-email-default"
label={`Notify via email (to ${user.email})`}
@@ -311,12 +334,10 @@ const CreateInfrastructureMonitor = () => {
(notification) => notification.type === "email"
)}
value={user?.email}
onChange={(e) => handleChange(e)}
onBlur={handleBlur}
onChange={(event) => handleNotifications(event, "email")}
/>
</Stack>
</ConfigBox>
<ConfigBox>
<Box>
<Typography component="h2">Customize alerts</Typography>
@@ -326,25 +347,31 @@ const CreateInfrastructureMonitor = () => {
</Typography>
</Box>
<Stack gap={theme.spacing(6)}>
{HARDWARE_MONITOR_TYPES.map((type, idx) => (
<CustomThreshold
key={idx}
checkboxId={type}
checkboxLabel={
type !== "cpu" ? capitalizeFirstLetter(type) : type.toUpperCase()
}
onCheckboxChange={handleCustomAlertCheckChange}
fieldId={THRESHOLD_FIELD_PREFIX + type}
fieldValue={infrastructureMonitor[THRESHOLD_FIELD_PREFIX + type] ?? ""}
onFieldChange={handleChange}
onFieldBlur={handleBlur}
// TODO: need BE, maybe in another PR
alertUnit={type == "temperature" ? "°C" : "%"}
infrastructureMonitor={infrastructureMonitor}
errors={errors}
/>
))}
{alertErrKeyLen > 0 && (
{METRICS.map((metric) => {
return (
<CustomThreshold
key={metric}
infrastructureMonitor={infrastructureMonitor}
errors={errors}
checkboxId={metric}
checkboxName={metric}
checkboxLabel={
metric !== "cpu"
? capitalizeFirstLetter(metric)
: metric.toUpperCase()
}
onCheckboxChange={handleCheckboxChange}
isChecked={infrastructureMonitor[metric]}
fieldId={METRIC_PREFIX + metric}
fieldName={METRIC_PREFIX + metric}
fieldValue={infrastructureMonitor[METRIC_PREFIX + metric]}
onFieldChange={handleChange}
alertUnit={metric == "temperature" ? "°C" : "%"}
/>
);
})}
{/* Error text */}
{hasAlertError(errors) && (
<Typography
component="span"
className="input-error"
@@ -354,14 +381,7 @@ const CreateInfrastructureMonitor = () => {
opacity: 0.8,
}}
>
{
errors[
THRESHOLD_FIELD_PREFIX +
HARDWARE_MONITOR_TYPES.filter(
(type) => errors[THRESHOLD_FIELD_PREFIX + type]
)[0]
]
}
{getAlertError(errors)}
</Typography>
)}
</Stack>
@@ -373,11 +393,11 @@ const CreateInfrastructureMonitor = () => {
<Stack gap={theme.spacing(12)}>
<Select
id="interval"
name="interval"
label="Check frequency"
value={infrastructureMonitor.interval || 15}
onChange={(e) => handleChange(e, "interval")}
onBlur={(e) => handleBlur(e, "interval")}
items={frequencies}
onChange={handleChange}
items={SELECT_VALUES}
/>
</Stack>
</ConfigBox>
@@ -387,7 +407,7 @@ const CreateInfrastructureMonitor = () => {
>
<LoadingButton
variant="contained"
color="primary"
color="accent"
onClick={handleCreateInfrastructureMonitor}
loading={monitorState?.isLoading}
>
@@ -19,10 +19,9 @@ const Empty = ({ styles }) => {
<Typography variant="h2">Your infrastructure dashboard will show here</Typography>
<Typography
textAlign="center"
color={theme.palette.text.secondary}
color={theme.palette.primary.contrastTextSecondary}
>
Hang tight! When we receive data, we'll show it here. Please check back in a few
minutes.
Hang tight! Data is loading
</Typography>
</Stack>
</Box>
+212 -119
View File
@@ -1,25 +1,25 @@
import { useParams } from "react-router-dom";
import { useEffect, useState } from "react";
import Breadcrumbs from "../../../Components/Breadcrumbs";
import { Stack, Box, Typography } from "@mui/material";
import { Button, ButtonGroup, Stack, Box, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import CustomGauge from "../../../Components/Charts/CustomGauge";
import AreaChart from "../../../Components/Charts/AreaChart";
import { useSelector } from "react-redux";
import { networkService } from "../../../main";
import PulseDot from "../../../Components/Animated/PulseDot";
import useUtils from "../../Monitors/utils";
import useUtils from "../../Uptime/utils";
import { useNavigate } from "react-router-dom";
import Empty from "./empty";
import { logger } from "../../../Utils/Logger";
import { formatDurationRounded, formatDurationSplit } from "../../../Utils/timeUtils";
import { TzTick, PercentTick } from "../../../Components/Charts/Utils/chartUtils";
import {
TzTick,
PercentTick,
InfrastructureTooltip,
TemperatureTooltip,
} from "../../../Components/Charts/Utils/chartUtils";
import PropTypes from "prop-types";
import StatBox from "../../../Components/StatBox";
const BASE_BOX_PADDING_VERTICAL = 4;
const BASE_BOX_PADDING_HORIZONTAL = 8;
@@ -29,18 +29,50 @@ const TYPOGRAPHY_PADDING = 8;
* @param {number} bytes - Number of bytes to convert
* @returns {number} Converted value in gigabytes
*/
const formatBytes = (bytes) => {
if (bytes === undefined || bytes === null) return "0 GB";
if (typeof bytes !== "number") return "0 GB";
if (bytes === 0) return "0 GB";
const formatBytes = (bytes, space = false) => {
if (bytes === undefined || bytes === null)
return (
<>
{0}
{space ? " " : ""}
<Typography component="span">GB</Typography>
</>
);
if (typeof bytes !== "number")
return (
<>
{0}
{space ? " " : ""}
<Typography component="span">GB</Typography>
</>
);
if (bytes === 0)
return (
<>
{0}
{space ? " " : ""}
<Typography component="span">GB</Typography>
</>
);
const GB = bytes / (1024 * 1024 * 1024);
const MB = bytes / (1024 * 1024);
if (GB >= 1) {
return `${Number(GB.toFixed(0))} GB`;
return (
<>
{Number(GB.toFixed(0))}
{space ? " " : ""}
<Typography component="span">GB</Typography>
</>
);
} else {
return `${Number(MB.toFixed(0))} MB`;
return (
<>
{Number(MB.toFixed(0))}
<Typography component="span">MB</Typography>
</>
);
}
};
@@ -76,10 +108,10 @@ const BaseBox = ({ children, sx = {} }) => {
padding: `${theme.spacing(BASE_BOX_PADDING_VERTICAL)} ${theme.spacing(BASE_BOX_PADDING_HORIZONTAL)}`,
minWidth: 200,
width: 225,
backgroundColor: theme.palette.background.main,
backgroundColor: theme.palette.primary.main,
border: 1,
borderStyle: "solid",
borderColor: theme.palette.border.light,
borderColor: theme.palette.primary.lowContrast,
...sx,
}}
>
@@ -93,27 +125,6 @@ BaseBox.propTypes = {
sx: PropTypes.object,
};
/**
* Renders a statistic box with a heading and subheading
* @param {Object} props - Component properties
* @param {string} props.heading - Primary heading text
* @param {string} props.subHeading - Secondary heading text
* @returns {React.ReactElement} Stat box component
*/
const StatBox = ({ heading, subHeading }) => {
return (
<BaseBox>
<Typography component="h2">{heading}</Typography>
<Typography>{subHeading}</Typography>
</BaseBox>
);
};
StatBox.propTypes = {
heading: PropTypes.string.isRequired,
subHeading: PropTypes.string.isRequired,
};
/**
* Renders a gauge box with usage visualization
* @param {Object} props - Component properties
@@ -144,7 +155,7 @@ const GaugeBox = ({ value, heading, metricOne, valueOne, metricTwo, valueTwo })
<Box
sx={{
width: "100%",
borderTop: `1px solid ${theme.palette.border.light}`,
borderTop: `1px solid ${theme.palette.primary.lowContrast}`,
}}
>
<Stack
@@ -173,9 +184,11 @@ GaugeBox.propTypes = {
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
heading: PropTypes.string.isRequired,
metricOne: PropTypes.string.isRequired,
valueOne: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
valueOne: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.element])
.isRequired,
metricTwo: PropTypes.string.isRequired,
valueTwo: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
valueTwo: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.element])
.isRequired,
};
/**
@@ -192,8 +205,8 @@ const InfrastructureDetails = () => {
];
const [monitor, setMonitor] = useState(null);
const { authToken } = useSelector((state) => state.auth);
const [dateRange, setDateRange] = useState("all");
const { statusColor, determineState } = useUtils();
const [dateRange, setDateRange] = useState("day");
const { statusColor, statusStyles, determineState } = useUtils();
// These calculations are needed because ResponsiveContainer
// doesn't take padding of parent/siblings into account
// when calculating height.
@@ -205,8 +218,9 @@ const InfrastructureDetails = () => {
(chartContainerHeight - totalChartContainerPadding - totalTypographyPadding) * 0.95;
// end height calculations
const buildStatBoxes = (checks) => {
let latestCheck = checks[0] ?? null;
const buildStatBoxes = (stats, uptime) => {
if (Object.keys(stats).length === 0) return [];
let latestCheck = stats?.aggregateData?.latestCheck ?? null;
if (latestCheck === null) return [];
// Extract values from latest check
@@ -224,25 +238,51 @@ const InfrastructureDetails = () => {
const platform = latestCheck?.host?.platform ?? null;
const osPlatform = os === null && platform === null ? null : `${os} ${platform}`;
return [
{
id: 7,
sx: statusStyles[determineState(monitor)],
heading: "Status",
subHeading: monitor?.status === true ? "Active" : "Inactive",
},
{
id: 0,
heading: "CPU (Physical)",
subHeading: `${physicalCores} cores`,
subHeading: (
<>
{physicalCores}
<Typography component="span">cores</Typography>
</>
),
},
{
id: 1,
heading: "CPU (Logical)",
subHeading: `${logicalCores} cores`,
subHeading: (
<>
{logicalCores}
<Typography component="span">cores</Typography>
</>
),
},
{
id: 2,
heading: "CPU Frequency",
subHeading: `${(cpuFrequency / 1000).toFixed(2)} Ghz`,
subHeading: (
<>
{(cpuFrequency / 1000).toFixed(2)}
<Typography component="span">Ghz</Typography>
</>
),
},
{
id: 3,
heading: "Average CPU Temperature",
subHeading: `${cpuTemperature.toFixed(2)} C`,
subHeading: (
<>
{cpuTemperature.toFixed(2)}
<Typography component="span">C</Typography>
</>
),
},
{
id: 4,
@@ -254,12 +294,17 @@ const InfrastructureDetails = () => {
heading: "Disk",
subHeading: formatBytes(diskTotalBytes),
},
{ id: 6, heading: "Uptime", subHeading: "100%" },
{
id: 7,
heading: "Status",
subHeading: monitor?.status === true ? "Active" : "Inactive",
id: 6,
heading: "Uptime",
subHeading: (
<>
{(uptime * 100).toFixed(2)}
<Typography component="span">%</Typography>
</>
),
},
{
id: 8,
heading: "OS",
@@ -268,8 +313,10 @@ const InfrastructureDetails = () => {
];
};
const buildGaugeBoxConfigs = (checks) => {
let latestCheck = checks[0] ?? null;
const buildGaugeBoxConfigs = (stats) => {
if (Object.keys(stats).length === 0) return [];
let latestCheck = stats?.aggregateData?.latestCheck ?? null;
if (latestCheck === null) return [];
// Extract values from latest check
@@ -283,16 +330,16 @@ const InfrastructureDetails = () => {
{
type: "memory",
value: decimalToPercentage(memoryUsagePercent),
heading: "Memory Usage",
heading: "Memory usage",
metricOne: "Used",
valueOne: formatBytes(memoryUsedBytes),
valueOne: formatBytes(memoryUsedBytes, true),
metricTwo: "Total",
valueTwo: formatBytes(memoryTotalBytes),
valueTwo: formatBytes(memoryTotalBytes, true),
},
{
type: "cpu",
value: decimalToPercentage(cpuUsagePercent),
heading: "CPU Usage",
heading: "CPU usage",
metricOne: "Cores",
valueOne: cpuPhysicalCores ?? 0,
metricTwo: "Frequency",
@@ -304,9 +351,9 @@ const InfrastructureDetails = () => {
value: decimalToPercentage(disk.usage_percent),
heading: `Disk${idx} usage`,
metricOne: "Used",
valueOne: formatBytes(disk.total_bytes - disk.free_bytes),
valueOne: formatBytes(disk.total_bytes - disk.free_bytes, true),
metricTwo: "Total",
valueTwo: formatBytes(disk.total_bytes),
valueTwo: formatBytes(disk.total_bytes, true),
})),
];
};
@@ -316,27 +363,27 @@ const InfrastructureDetails = () => {
if (checks === null) return { temps: [], tempKeys: [] };
for (const check of checks) {
if (check?.cpu?.temperature?.length > numCores) {
numCores = check.cpu.temperature.length;
if (check?.avgTemperature?.length > numCores) {
numCores = check.avgTemperature.length;
break;
}
}
const temps = checks.map((check) => {
// If there's no data, set the temperature to 0
if (
check?.cpu?.temperature?.length === 0 ||
check?.cpu?.temperature === undefined ||
check?.cpu?.temperature === null
check?.avgTemperature?.length === 0 ||
check?.avgTemperature === undefined ||
check?.avgTemperature === null
) {
check.cpu.temperature = Array(numCores).fill(0);
check.avgTemperature = Array(numCores).fill(0);
}
const res = check?.cpu?.temperature?.reduce(
const res = check?.avgTemperature?.reduce(
(acc, cur, idx) => {
acc[`core${idx + 1}`] = cur;
return acc;
},
{
createdAt: check.createdAt,
_id: check._id,
}
);
return res;
@@ -346,7 +393,7 @@ const InfrastructureDetails = () => {
}
return {
tempKeys: Object.keys(temps[0] || {}).filter((key) => key !== "createdAt"),
tempKeys: Object.keys(temps[0] || {}).filter((key) => key !== "_id"),
temps,
};
};
@@ -354,90 +401,91 @@ const InfrastructureDetails = () => {
const buildAreaChartConfigs = (checks) => {
let latestCheck = checks[0] ?? null;
if (latestCheck === null) return [];
const reversedChecks = checks.toReversed();
const tempData = buildTemps(reversedChecks);
const { temps, tempKeys } = buildTemps(checks);
return [
{
type: "memory",
data: reversedChecks,
dataKeys: ["memory.usage_percent"],
data: checks,
dataKeys: ["avgMemoryUsage"],
heading: "Memory usage",
strokeColor: theme.palette.primary.main,
gradientStartColor: theme.palette.primary.main,
yLabel: "Memory Usage",
strokeColor: theme.palette.accent.main, // CAIO_REVIEW
gradientStartColor: theme.palette.accent.main, // CAIO_REVIEW
yLabel: "Memory usage",
yDomain: [0, 1],
yTick: <PercentTick />,
xTick: <TzTick />,
xTick: <TzTick dateRange={dateRange} />,
toolTip: (
<InfrastructureTooltip
dotColor={theme.palette.primary.main}
yKey={"memory.usage_percent"}
yLabel={"Memory Usage"}
yKey={"avgMemoryUsage"}
yLabel={"Memory usage"}
dateRange={dateRange}
/>
),
},
{
type: "cpu",
data: reversedChecks,
dataKeys: ["cpu.usage_percent"],
data: checks,
dataKeys: ["avgCpuUsage"],
heading: "CPU usage",
strokeColor: theme.palette.success.main,
gradientStartColor: theme.palette.success.main,
yLabel: "CPU Usage",
yLabel: "CPU usage",
yDomain: [0, 1],
yTick: <PercentTick />,
xTick: <TzTick />,
xTick: <TzTick dateRange={dateRange} />,
toolTip: (
<InfrastructureTooltip
dotColor={theme.palette.success.main}
yKey={"cpu.usage_percent"}
yLabel={"CPU Usage"}
yKey={"avgCpuUsage"}
yLabel={"CPU usage"}
dateRange={dateRange}
/>
),
},
{
type: "temperature",
data: tempData.temps,
dataKeys: tempData.tempKeys,
data: temps,
dataKeys: tempKeys,
strokeColor: theme.palette.error.main,
gradientStartColor: theme.palette.error.main,
heading: "CPU Temperature",
yLabel: "Temperature",
xTick: <TzTick />,
xTick: <TzTick dateRange={dateRange} />,
yDomain: [
0,
Math.max(
Math.max(
...tempData.temps.flatMap((t) => tempData.tempKeys.map((k) => t[k]))
) * 1.1,
Math.max(...temps.flatMap((t) => tempKeys.map((k) => t[k]))) * 1.1,
200
),
],
toolTip: (
<TemperatureTooltip
keys={tempData.tempKeys}
keys={tempKeys}
dotColor={theme.palette.error.main}
dateRange={dateRange}
/>
),
},
...(latestCheck?.disk?.map((disk, idx) => ({
...(latestCheck?.disks?.map((disk, idx) => ({
type: "disk",
data: reversedChecks,
data: checks,
diskIndex: idx,
dataKeys: [`disk[${idx}].usage_percent`],
dataKeys: [`disks[${idx}].usagePercent`],
heading: `Disk${idx} usage`,
strokeColor: theme.palette.warning.main,
gradientStartColor: theme.palette.warning.main,
yLabel: "Disk Usage",
yDomain: [0, 1],
yTick: <PercentTick />,
xTick: <TzTick />,
xTick: <TzTick dateRange={dateRange} />,
toolTip: (
<InfrastructureTooltip
dotColor={theme.palette.warning.main}
yKey={`disk.usage_percent`}
yKey={`disks.usagePercent`}
yLabel={"Disc usage"}
yIdx={idx}
dateRange={dateRange}
/>
),
})) || []),
@@ -448,15 +496,12 @@ const InfrastructureDetails = () => {
useEffect(() => {
const fetchData = async () => {
try {
const response = await networkService.getStatsByMonitorId({
const response = await networkService.getHardwareDetailsByMonitorId({
authToken: authToken,
monitorId: monitorId,
sortOrder: null,
limit: null,
dateRange: dateRange,
numToDisplay: 50,
normalize: false,
});
response.data.data;
setMonitor(response.data.data);
} catch (error) {
navigate("/not-found", { replace: true });
@@ -466,14 +511,18 @@ const InfrastructureDetails = () => {
fetchData();
}, [authToken, monitorId, dateRange, navigate]);
const statBoxConfigs = buildStatBoxes(monitor?.checks ?? []);
const gaugeBoxConfigs = buildGaugeBoxConfigs(monitor?.checks ?? []);
const areaChartConfigs = buildAreaChartConfigs(monitor?.checks ?? []);
const statBoxConfigs = buildStatBoxes(
monitor?.stats ?? {},
monitor?.uptimePercentage ?? "Unknown"
);
const gaugeBoxConfigs = buildGaugeBoxConfigs(monitor?.stats ?? {});
const areaChartConfigs = buildAreaChartConfigs(monitor?.stats?.checks ?? []);
const lastChecked =
Date.now() - new Date(monitor?.stats?.aggregateData?.latestCheck?.createdAt);
return (
<Box>
<Breadcrumbs list={navList} />
{monitor?.checks?.length > 0 ? (
{monitor?.stats.checks?.length > 0 ? (
<Stack
direction="column"
gap={theme.spacing(10)}
@@ -499,10 +548,11 @@ const InfrastructureDetails = () => {
Checking every {formatDurationRounded(monitor?.interval)}
</Typography>
<Typography alignSelf="end">
Last checked {formatDurationSplit(monitor?.lastChecked).time}{" "}
{formatDurationSplit(monitor?.lastChecked).format} ago
Last checked {formatDurationSplit(lastChecked).time}{" "}
{formatDurationSplit(lastChecked).format} ago
</Typography>
</Stack>
<Stack
direction="row"
flexWrap="wrap"
@@ -515,21 +565,64 @@ const InfrastructureDetails = () => {
/>
))}
</Stack>
<Stack
direction="row"
gap={theme.spacing(8)}
>
{gaugeBoxConfigs.map((config) => (
<GaugeBox
key={`${config.type}-${config.diskIndex ?? ""}`}
value={config.value}
heading={config.heading}
metricOne={config.metricOne}
valueOne={config.valueOne}
metricTwo={config.metricTwo}
valueTwo={config.valueTwo}
/>
))}
{gaugeBoxConfigs.map((config) => {
return (
<GaugeBox
key={`${config.type}-${config.diskIndex ?? ""}`}
value={config.value}
heading={config.heading}
metricOne={config.metricOne}
valueOne={config.valueOne}
metricTwo={config.metricTwo}
valueTwo={config.valueTwo}
/>
);
})}
</Stack>
<Stack
direction="row"
justifyContent="space-between"
alignItems="flex-end"
gap={theme.spacing(8)}
mb={theme.spacing(8)}
>
<Typography variant="body2">
Showing statistics for past{" "}
{dateRange === "day"
? "24 hours"
: dateRange === "week"
? "7 days"
: "30 days"}
.
</Typography>
<ButtonGroup sx={{ height: 32 }}>
<Button
variant="group"
filled={(dateRange === "day").toString()}
onClick={() => setDateRange("day")}
>
Day
</Button>
<Button
variant="group"
filled={(dateRange === "week").toString()}
onClick={() => setDateRange("week")}
>
Week
</Button>
<Button
variant="group"
filled={(dateRange === "month").toString()}
onClick={() => setDateRange("month")}
>
Month
</Button>
</ButtonGroup>
</Stack>
<Stack
direction={"row"}
@@ -556,7 +649,7 @@ const InfrastructureDetails = () => {
height={areaChartHeight}
data={config.data}
dataKeys={config.dataKeys}
xKey="createdAt"
xKey="_id"
yDomain={config.yDomain}
customTooltip={config.toolTip}
xTick={config.xTick}
@@ -575,9 +668,9 @@ const InfrastructureDetails = () => {
<Empty
styles={{
border: 1,
borderColor: theme.palette.border.light,
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
backgroundColor: theme.palette.background.main,
backgroundColor: theme.palette.primary.main,
p: theme.spacing(30),
}}
/>
@@ -83,7 +83,7 @@ const InfrastructureMenu = ({ monitor, isAdmin, updateCallback }) => {
outline: "none",
},
"& svg path": {
stroke: theme.palette.other.icon,
stroke: theme.palette.primary.contrastTextTertiary,
},
}}
ref={anchor}
@@ -109,88 +109,6 @@ const InfrastructureMenu = ({ monitor, isAdmin, updateCallback }) => {
},
}}
>
{/*
Open site action. Not necessary for infrastructure?
{actions.url !== null ? (
<MenuItem
onClick={(e) => {
closeMenu(e);
e.stopPropagation();
window.open(actions.url, "_blank", "noreferrer");
}}
>
Open site
</MenuItem>
) : (
""
)}
*/}
<MenuItem onClick={() => openDetails(monitor.id)}>Details</MenuItem>
{/*
Incidents. Necessary?
<MenuItem
onClick={(e) => {
e.stopPropagation();
navigate(`/incidents/${actions.id}`);
}}
>
Incidents
</MenuItem> */}
{/*
Configure. Necessary?
{isAdmin && (
<MenuItem
onClick={(e) => {
e.stopPropagation();
navigate(`/monitors/configure/${actions.id}`);
}}
>
Configure
</MenuItem>
)} */}
{/*
Clone. Necessary?
{isAdmin && (
<MenuItem
onClick={(e) => {
e.stopPropagation();
navigate(`/monitors/create/${actions.id}`);
}}
>
Clone
</MenuItem>
)} */}
{/*
Pause. Necessary?
const handlePause = async () => {
try {
const action = await dispatch(
pauseUptimeMonitor({ authToken, monitorId: monitor._id })
);
if (pauseUptimeMonitor.fulfilled.match(action)) {
updateCallback();
const state = action?.payload?.data.isActive === false ? "paused" : "resumed";
createToast({ body: `Monitor ${state} successfully.` });
} else {
throw new Error(action?.error?.message ?? "Failed to pause monitor.");
}
} catch (error) {
logger.error("Error pausing monitor:", monitor._id, error);
createToast({ body: "Failed to pause monitor." });
}
};
{isAdmin && (
<MenuItem
onClick={(e) => {
e.stopPropagation();
handlePause(e);
}}
>
{monitor?.isActive === true ? "Pause" : "Resume"}
</MenuItem>
)} */}
{isAdmin && <MenuItem onClick={openRemove}>Remove</MenuItem>}
</Menu>
<Dialog
+128 -198
View File
@@ -1,73 +1,28 @@
import { useEffect, useState } from "react";
import { useEffect, useState, useCallback } from "react";
import { useNavigate } from "react-router-dom";
import { /* useDispatch, */ useSelector } from "react-redux";
import { useTheme } from "@emotion/react";
import useUtils from "../Monitors/utils";
import useUtils from "../Uptime/utils.jsx";
import { jwtDecode } from "jwt-decode";
import SkeletonLayout from "./skeleton";
import Fallback from "../../Components/Fallback";
// import GearIcon from "../../Assets/icons/settings-bold.svg?react";
import CPUChipIcon from "../../assets/icons/cpu-chip.svg?react";
import {
Box,
Button,
IconButton,
Paper,
Stack,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
} from "@mui/material";
import DataTable from "../../Components/Table";
import { Box, Button, IconButton, Stack } from "@mui/material";
import Breadcrumbs from "../../Components/Breadcrumbs";
import { StatusLabel } from "../../Components/Label";
import { Heading } from "../../Components/Heading";
import { Pagination } from "./components/TablePagination";
import Pagination from "../../Components/Table/TablePagination/index.jsx";
// import { getInfrastructureMonitorsByTeamId } from "../../Features/InfrastructureMonitors/infrastructureMonitorsSlice";
import { networkService } from "../../Utils/NetworkService.js";
import CustomGauge from "../../Components/Charts/CustomGauge/index.jsx";
import Host from "../Monitors/Home/host.jsx";
import Host from "../Uptime/Home/host.jsx";
import { useIsAdmin } from "../../Hooks/useIsAdmin.js";
import { InfrastructureMenu } from "./components/Menu";
const columns = [
{ label: "Host" },
{ label: "Status" },
{ label: "Frequency" },
{ label: "CPU" },
{ label: "Mem" },
{ label: "Disk" },
{ label: "Actions" },
];
const BREADCRUMBS = [{ name: `infrastructure`, path: "/infrastructure" }];
/* TODO
Create reusable table component.
It should receive as a parameter the following object:
tableData = [
columns = [
{
id: example,
label: Example Extendable,
align: "center" | "left" (default)
}
],
rows: [
{
**Number of keys will be equal to number of columns**
key1: string,
key2: number,
key3: Component
}
]
]
Apply to Monitor Table, and Account/Team.
Analyze existing BasicTable
*/
/**
* This is the Infrastructure monitoring page. This is a work in progress
*
@@ -95,12 +50,13 @@ function Infrastructure() {
setRowsPerPage(parseInt(event.target.value));
setPage(0);
};
const [monitorState, setMonitorState] = useState({ monitors: [], total: 0 });
const [monitors, setMonitors] = useState([]);
const [summary, setSummary] = useState({});
const { authToken } = useSelector((state) => state.auth);
const user = jwtDecode(authToken);
const fetchMonitors = async () => {
const fetchMonitors = useCallback(async () => {
try {
setIsLoading(true);
const response = await networkService.getMonitorsByTeamId({
@@ -108,29 +64,23 @@ function Infrastructure() {
teamId: user.teamId,
limit: 1,
types: ["hardware"],
status: null,
checkOrder: "desc",
normalize: true,
page: page,
rowsPerPage: rowsPerPage,
});
setMonitorState({
monitors: response?.data?.data?.monitors ?? [],
total: response?.data?.data?.monitorCount ?? 0,
});
setMonitors(response?.data?.data?.filteredMonitors ?? []);
setSummary(response?.data?.data?.summary ?? {});
} catch (error) {
console.error(error);
} finally {
setIsLoading(false);
}
};
}, [page, rowsPerPage, authToken, user.teamId]);
useEffect(() => {
fetchMonitors();
}, [page, rowsPerPage]);
}, [fetchMonitors]);
const { determineState } = useUtils();
const { monitors, total: totalMonitors } = monitorState;
// do it here
function openDetails(id) {
navigate(`/infrastructure/${id}`);
@@ -139,6 +89,65 @@ function Infrastructure() {
fetchMonitors();
}
const headers = [
{
id: "host",
content: "Host",
render: (row) => (
<Host
title={row.name}
url={row.url}
percentage={row.uptimePercentage}
percentageColor={row.percentageColor}
/>
),
},
{
id: "status",
content: "Status",
render: (row) => (
<StatusLabel
status={row.status}
text={row.status}
/>
),
},
{
id: "frequency",
content: "Frequency",
render: (row) => (
<Stack
direction={"row"}
justifyContent={"center"}
alignItems={"center"}
gap=".25rem"
>
<CPUChipIcon
width={20}
height={20}
/>
{row.processor}
</Stack>
),
},
{ id: "cpu", content: "CPU", render: (row) => <CustomGauge progress={row.cpu} /> },
{ id: "mem", content: "Mem", render: (row) => <CustomGauge progress={row.mem} /> },
{ id: "disk", content: "Disk", render: (row) => <CustomGauge progress={row.disk} /> },
{
id: "actions",
content: "Actions",
render: (row) => (
<IconButton>
<InfrastructureMenu
monitor={row}
isAdmin={isAdmin}
updateCallback={handleActionMenuDelete}
/>
</IconButton>
),
},
];
const monitorsAsRows = monitors.map((monitor) => {
const processor =
((monitor.checks[0]?.cpu?.usage_frequency ?? 0) / 1000).toFixed(2) + " GHz";
@@ -151,12 +160,12 @@ function Infrastructure() {
.toString();
const percentageColor =
monitor.uptimePercentage < 0.25
? theme.palette.percentage.uptimePoor
? theme.palette.error.main
: monitor.uptimePercentage < 0.5
? theme.palette.percentage.uptimeFair
: monitor.uptimePercentage < 0.75
? theme.palette.percentage.uptimeGood
: theme.palette.percentage.uptimeExcellent;
: theme.palette.success.lowContrast;
return {
id: monitor._id,
name: monitor.name,
@@ -171,7 +180,7 @@ function Infrastructure() {
};
});
let isActuallyLoading = isLoading && monitorState.monitors?.length === 0;
let isActuallyLoading = isLoading && monitors?.length === 0;
return (
<Box
className="infrastructure-monitor"
@@ -179,43 +188,38 @@ function Infrastructure() {
':has(> [class*="fallback__"])': {
position: "relative",
border: 1,
borderColor: theme.palette.border.light,
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
borderStyle: "dashed",
backgroundColor: theme.palette.background.main,
backgroundColor: theme.palette.primary.main,
overflow: "hidden",
},
}}
>
{isActuallyLoading ? (
<SkeletonLayout />
) : monitorState.monitors?.length !== 0 ? (
) : monitors?.length !== 0 ? (
<Stack gap={theme.spacing(8)}>
<Breadcrumbs list={BREADCRUMBS} />
<Stack
direction="row"
sx={{
justifyContent: "end",
alignItems: "center",
gap: "1rem",
flexWrap: "wrap",
marginBottom: "2rem",
}}
>
{/*
This will be removed from here, but keeping the commented code to remind me to add a max width to the greeting component
<Box style={{ maxWidth: "65ch" }}>
<Greeting type="uptime" />
</Box> */}
<Button
variant="contained"
color="primary"
onClick={navigateToCreate}
sx={{ fontWeight: 500 }}
<Box>
<Breadcrumbs list={BREADCRUMBS} />
<Stack
direction="row"
justifyContent="end"
alignItems="center"
mt={theme.spacing(5)}
>
Create infrastructure monitor
</Button>
</Stack>
{isAdmin && (
<Button
variant="contained"
color="accent"
onClick={navigateToCreate}
sx={{ fontWeight: 500, whiteSpace: "nowrap" }}
>
Create new
</Button>
)}
</Stack>
</Box>
<Stack
sx={{
gap: "1rem",
@@ -230,117 +234,43 @@ function Infrastructure() {
}}
>
<Heading component="h2">Infrastructure monitors</Heading>
{/* TODO Correct the class current-monitors-counter, there are some unnecessary things there */}
{/* TODO Same as the one in UptimaDataTable. Create component */}
<Box
component="span"
className="current-monitors-counter"
color={theme.palette.text.primary}
border={1}
borderColor={theme.palette.border.light}
backgroundColor={theme.palette.background.accent}
color={theme.palette.tertiary.contrastText}
border={2}
borderColor={theme.palette.accent.main}
backgroundColor={theme.palette.tertiary.main}
sx={{
padding: ".25em .75em",
borderRadius: "50%",
fontSize: "12px",
fontWeight: 500,
}}
>
{totalMonitors}
{summary?.totalMonitors ?? 0}
</Box>
</Stack>
<TableContainer component={Paper}>
<Table stickyHeader>
<TableHead sx={{ backgroundColor: theme.palette.background.accent }}>
<TableRow>
{columns.map((column, index) => (
<TableCell
key={index}
align={index === 0 ? "left" : "center"}
sx={{
backgroundColor: theme.palette.background.accent,
}}
>
{column.label}
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{monitorsAsRows.map((row) => {
return (
<TableRow
key={row.id}
onClick={() => openDetails(row.id)}
sx={{
cursor: "pointer",
"&:hover": {
backgroundColor: theme.palette.background.accent,
},
}}
>
{/* TODO iterate over column and get column id, applying row[column.id] */}
<TableCell>
<Host
title={row.name}
url={row.url}
percentage={row.uptimePercentage}
percentageColor={row.percentageColor}
/>
</TableCell>
<TableCell align="center">
<StatusLabel
status={row.status}
text={row.status}
/* Use capitalize inside of Status Label */
/* Update component so we don't need to pass text and status separately*/
customStyles={{ textTransform: "capitalize" }}
/>
</TableCell>
<TableCell align="center">
<Stack
direction={"row"}
justifyContent={"center"}
alignItems={"center"}
gap=".25rem"
>
<CPUChipIcon
width={20}
height={20}
/>
{row.processor}
</Stack>
</TableCell>
<TableCell align="center">
<CustomGauge progress={row.cpu} />
</TableCell>
<TableCell align="center">
<CustomGauge progress={row.mem} />
</TableCell>
<TableCell align="center">
<CustomGauge progress={row.disk} />
</TableCell>
<TableCell align="center">
{/* Get ActionsMenu from Monitor Table and create a component */}
<IconButton
sx={{
"& svg path": {
stroke: theme.palette.other.icon,
},
}}
>
<InfrastructureMenu
monitor={row}
isAdmin={isAdmin}
updateCallback={handleActionMenuDelete}
/>
{/* <GearIcon
width={20}
height={20}
/> */}
</IconButton>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
<DataTable
config={{
/* TODO this behavior seems to be repeated. Put it on the root table? */
rowSX: {
cursor: "pointer",
"&:hover td": {
backgroundColor: theme.palette.tertiary.main,
transition: "background-color .3s ease",
},
},
onRowClick: (row) => openDetails(row.id),
}}
headers={headers}
data={monitorsAsRows}
/>
<Pagination
monitorCount={totalMonitors}
itemCount={summary?.totalMonitors ?? 0}
paginationLabel="monitors"
page={page}
rowsPerPage={rowsPerPage}
handleChangePage={handleChangePage}
+5 -5
View File
@@ -33,9 +33,9 @@ const IntegrationsComponent = ({ icon, header, info, onClick }) => {
pl={theme.spacing(12)}
height="100%"
border={1}
borderColor={theme.palette.border.light}
borderColor={theme.palette.primary.lowContrast}
borderRadius={theme.shape.borderRadius}
backgroundColor={theme.palette.background.main}
backgroundColor={theme.palette.primary.main}
>
{icon}
<Stack
@@ -54,7 +54,7 @@ const IntegrationsComponent = ({ icon, header, info, onClick }) => {
</Stack>
<Button
variant="contained"
color="primary"
color="accent"
onClick={onClick}
sx={{ alignSelf: "center" }}
disabled={true}
@@ -126,13 +126,13 @@ const Integrations = () => {
gap={theme.spacing(2)}
sx={{
"& h1, & p": {
color: theme.palette.text.secondary,
color: theme.palette.primary.contrastTextSecondary,
},
}}
>
<Typography component="h1">Integrations</Typography>
<Typography mb={theme.spacing(12)}>
Connect BlueWave Uptime to your favorite service.
Connect Checkmate to your favorite service.
</Typography>
<Grid
container
@@ -2,7 +2,7 @@ import { Box, Button, Stack, Typography } from "@mui/material";
import { useSelector } from "react-redux";
import { useTheme } from "@emotion/react";
import { useEffect, useState } from "react";
import { ConfigBox } from "./styled";
import ConfigBox from "../../../Components/ConfigBox";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import { LocalizationProvider } from "@mui/x-date-pickers";
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
@@ -136,7 +136,7 @@ const CreateMaintenance = () => {
const response = await networkService.getMonitorsByTeamId({
authToken: authToken,
teamId: user.teamId,
limit: -1,
limit: null,
types: ["http", "ping", "pagespeed"],
});
const monitors = response.data.data.monitors;
@@ -321,18 +321,27 @@ const CreateMaintenance = () => {
Your pings won&apos;t be sent during this time frame
</Typography>
</Box>
<ConfigBox direction="row">
<Typography
component="h2"
variant="h2"
>
General Settings
</Typography>
<Box
px={theme.spacing(14)}
borderLeft={1}
borderLeftColor={theme.palette.border.light}
>
<ConfigBox>
<Box>
<Typography
component="h2"
variant="h2"
>
General Settings
</Typography>
</Box>
<Stack gap={theme.spacing(15)}>
<TextInput
id="name"
label="Friendly name"
placeholder="Maintenance at __ : __ for ___ minutes"
value={form.name}
onChange={(event) => {
handleFormChange("name", event.target.value);
}}
error={errors["name"] ? true : false}
helperText={errors["name"]}
/>
<Select
id="repeat"
name="maintenance-repeat"
@@ -346,11 +355,7 @@ const CreateMaintenance = () => {
}}
items={repeatConfig}
/>
<Stack
gap={theme.spacing(2)}
mt={theme.spacing(16)}
>
<Typography component="h3">Date</Typography>
<Stack>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DatePicker
id="startDate"
@@ -368,17 +373,17 @@ const CreateMaintenance = () => {
flexDirection: "row-reverse",
},
"& input": {
height: 34,
minHeight: 34,
p: 0,
pr: theme.spacing(5),
},
"& fieldset": {
borderColor: theme.palette.border.dark,
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
},
"&:not(:has(.Mui-disabled)):not(:has(.Mui-error)) .MuiOutlinedInput-root:not(:has(input:focus)):hover fieldset":
{
borderColor: theme.palette.border.dark,
borderColor: theme.palette.primary.lowContrast,
},
},
},
@@ -388,12 +393,36 @@ const CreateMaintenance = () => {
py: 0,
mr: 0,
"& path": {
stroke: theme.palette.other.icon,
stroke: theme.palette.primary.contrastTextTertiary,
strokeWidth: 1.1,
},
"&:hover": { backgroundColor: "transparent" },
},
},
// CAIO_REVIEW, entire popper
popper: {
sx: {
"& .MuiPickersDay-root": {
color: theme.palette.primary.contrastText,
"&.Mui-selected": {
backgroundColor: theme.palette.accent.main, // Selected day background
color: theme.palette.primary.contrastText, // Selected day text color
},
"&:hover": {
backgroundColor: theme.palette.accent.light, // Hover background
},
"&.Mui-disabled": {
color: theme.palette.primary.ContrastTextTertiary, // Disabled day color
},
},
"& .MuiDayCalendar-weekDayLabel": {
color: theme.palette.primary.contrastText,
},
"& .MuiPickersCalendarHeader-label": {
color: theme.palette.primary.contrastText,
},
},
},
}}
sx={{}}
onChange={(newDate) => {
@@ -403,72 +432,64 @@ const CreateMaintenance = () => {
/>
</LocalizationProvider>
</Stack>
</Box>
</Stack>
</ConfigBox>
<ConfigBox>
<Stack direction="row">
<Box>
<Typography
component="h2"
variant="h2"
>
Start time
</Typography>
<Typography>All dates and times are in GMT+0 time zone.</Typography>
</Box>
<Stack direction="row">
<LocalizationProvider dateAdapter={AdapterDayjs}>
<MobileTimePicker
id="startTime"
value={form.startTime}
onChange={(newTime) => {
handleTimeChange("startTime", newTime);
}}
slotProps={{
nextIconButton: { sx: { ml: theme.spacing(2) } },
field: {
sx: {
width: "fit-content",
"& > .MuiOutlinedInput-root": {
flexDirection: "row-reverse",
},
"& input": {
height: 34,
p: 0,
pl: theme.spacing(5),
},
"& fieldset": {
borderColor: theme.palette.border.dark,
borderRadius: theme.shape.borderRadius,
},
"&:not(:has(.Mui-disabled)):not(:has(.Mui-error)) .MuiOutlinedInput-root:not(:has(input:focus)):hover fieldset":
{
borderColor: theme.palette.border.dark,
},
<Box>
<Typography
component="h2"
variant="h2"
>
Start time
</Typography>
<Typography>All dates and times are in GMT+0 time zone.</Typography>
</Box>
<Stack gap={theme.spacing(15)}>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<MobileTimePicker
id="startTime"
label="Start time"
value={form.startTime}
onChange={(newTime) => {
handleTimeChange("startTime", newTime);
}}
slotProps={{
nextIconButton: { sx: { ml: theme.spacing(2) } },
field: {
sx: {
width: "fit-content",
"& > .MuiOutlinedInput-root": {
flexDirection: "row-reverse",
},
"& input": {
minHeight: 34,
p: 0,
pl: theme.spacing(5),
},
"& fieldset": {
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
},
"&:not(:has(.Mui-disabled)):not(:has(.Mui-error)) .MuiOutlinedInput-root:not(:has(input:focus)):hover fieldset":
{
borderColor: theme.palette.primary.lowContrast,
},
},
}}
error={errors["startTime"]}
/>
</LocalizationProvider>
</Stack>
</Stack>
<Stack direction="row">
<Box>
<Typography
component="h2"
variant="h2"
>
Duration
</Typography>
</Box>
},
}}
error={errors["startTime"]}
/>
</LocalizationProvider>
<Stack
direction="row"
alignItems="end"
spacing={theme.spacing(8)}
>
<TextInput
type="number"
id="duration"
label="Duration"
value={form.duration}
onChange={(event) => {
handleFormChange("duration", event.target.value);
@@ -491,66 +512,34 @@ const CreateMaintenance = () => {
</Stack>
</Stack>
</ConfigBox>
<Box>
<Typography
component="h2"
variant="h2"
fontSize={16}
my={theme.spacing(6)}
>
Monitor related settings
</Typography>
<ConfigBox>
<Stack direction="row">
<Box>
<Typography
component="h2"
variant="h2"
>
Friendly name
</Typography>
</Box>
<Box>
<TextInput
id="name"
placeholder="Maintenance at __ : __ for ___ minutes"
value={form.name}
onChange={(event) => {
handleFormChange("name", event.target.value);
}}
error={errors["name"] ? true : false}
helperText={errors["name"]}
/>
</Box>
</Stack>
<Stack direction="row">
<Box>
<Typography
component="h2"
variant="h2"
>
Add monitors
</Typography>
</Box>
<Box>
<Search
id={"monitors"}
multiple={true}
isAdorned={false}
options={monitors ? monitors : []}
filteredBy="name"
secondaryLabel={"type"}
inputValue={search}
value={form.monitors}
handleInputChange={handleSearch}
handleChange={handleSelectMonitors}
error={errors["monitors"]}
disabled={maintenanceWindowId !== undefined}
/>
</Box>
</Stack>
</ConfigBox>
</Box>
<ConfigBox>
<Box>
<Typography
component="h2"
variant="h2"
>
Monitors to apply maintenance window to
</Typography>
</Box>
<Stack gap={theme.spacing(15)}>
<Search
id={"monitors"}
label="Add monitors"
multiple={true}
isAdorned={false}
options={monitors ? monitors : []}
filteredBy="name"
secondaryLabel={"type"}
inputValue={search}
value={form.monitors}
handleInputChange={handleSearch}
handleChange={handleSelectMonitors}
error={errors["monitors"]}
disabled={maintenanceWindowId !== undefined}
/>
</Stack>
</ConfigBox>
<Box
ml="auto"
display="inline-block"
@@ -566,7 +555,7 @@ const CreateMaintenance = () => {
<LoadingButton
loading={isLoading}
variant="contained"
color="primary"
color="accent"
onClick={handleSubmit}
disabled={false}
>
@@ -1,25 +0,0 @@
import { Stack, styled } from "@mui/material";
export const ConfigBox = styled(Stack)(({ theme }) => ({
border: 1,
borderStyle: "solid",
borderColor: theme.palette.border.light,
borderRadius: theme.spacing(2),
backgroundColor: theme.palette.background.main,
"& > *": { padding: theme.spacing(14) },
"& > :first-of-type, & > .MuiStack-root > div:first-of-type": {
flex: 0.6,
},
"& > div:last-of-type, & > .MuiStack-root > div:last-of-type": {
flex: 1,
},
"& > .MuiStack-root > div:first-of-type": { paddingRight: theme.spacing(14) },
"& > .MuiStack-root > div:last-of-type": {
paddingLeft: theme.spacing(14),
},
"& h2": { fontSize: 13.5, fontWeight: 500 },
"& h3, & p": {
color: theme.palette.text.tertiary,
},
"& h3": { fontWeight: 500 },
}));
@@ -95,7 +95,7 @@ const ActionsMenu = ({ /* isAdmin, */ maintenanceWindow, updateCallback }) => {
outline: "none",
},
"& svg path": {
stroke: theme.palette.other.icon,
stroke: theme.palette.primary.contrastTextTertiary,
},
}}
>
@@ -1,34 +1,16 @@
import PropTypes from "prop-types";
import {
TableContainer,
Table,
TableHead,
TableRow,
TableCell,
TableBody,
Paper,
Box,
TablePagination,
Stack,
Typography,
Button,
} from "@mui/material";
import { Box } from "@mui/material";
import DataTable from "../../../Components/Table";
import Pagination from "../../../Components/Table/TablePagination";
import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded";
import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded";
import ActionsMenu from "./ActionsMenu";
import { useState, useEffect, memo, useCallback, useRef } from "react";
import { memo } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useTheme } from "@emotion/react";
import LeftArrowDouble from "../../../assets/icons/left-arrow-double.svg?react";
import RightArrowDouble from "../../../assets/icons/right-arrow-double.svg?react";
import LeftArrow from "../../../assets/icons/left-arrow.svg?react";
import RightArrow from "../../../assets/icons/right-arrow.svg?react";
import SelectorVertical from "../../../assets/icons/selector-vertical.svg?react";
import { formatDurationRounded } from "../../../Utils/timeUtils";
import { StatusLabel } from "../../../Components/Label";
import { setRowsPerPage } from "../../../Features/UI/uiSlice";
import dayjs from "dayjs";
/**
* Component for pagination actions (first, previous, next, last).
*
@@ -41,65 +23,6 @@ import dayjs from "dayjs";
*
* @returns {JSX.Element} Pagination actions component.
*/
const TablePaginationActions = (props) => {
const { count, page, rowsPerPage, onPageChange } = props;
const handleFirstPageButtonClick = (event) => {
onPageChange(event, 0);
};
const handleBackButtonClick = (event) => {
onPageChange(event, page - 1);
};
const handleNextButtonClick = (event) => {
onPageChange(event, page + 1);
};
const handleLastPageButtonClick = (event) => {
onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1));
};
return (
<Box sx={{ flexShrink: 0, ml: "24px" }}>
<Button
variant="group"
onClick={handleFirstPageButtonClick}
disabled={page === 0}
aria-label="first page"
>
<LeftArrowDouble />
</Button>
<Button
variant="group"
onClick={handleBackButtonClick}
disabled={page === 0}
aria-label="previous page"
>
<LeftArrow />
</Button>
<Button
variant="group"
onClick={handleNextButtonClick}
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
aria-label="next page"
>
<RightArrow />
</Button>
<Button
variant="group"
onClick={handleLastPageButtonClick}
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
aria-label="last page"
>
<RightArrowDouble />
</Button>
</Box>
);
};
TablePaginationActions.propTypes = {
count: PropTypes.number.isRequired,
page: PropTypes.number.isRequired,
rowsPerPage: PropTypes.number.isRequired,
onPageChange: PropTypes.func.isRequired,
};
const MaintenanceTable = ({
page,
@@ -111,13 +34,102 @@ const MaintenanceTable = ({
updateCallback,
}) => {
const { rowsPerPage } = useSelector((state) => state.ui.maintenance);
const theme = useTheme();
console.log(rowsPerPage);
const dispatch = useDispatch();
const handleChangePage = (event, newPage) => {
setPage(newPage);
};
const handleChangeRowsPerPage = (event) => {
dispatch(
setRowsPerPage({
value: parseInt(event.target.value, 10),
table: "maintenance",
})
);
setPage(0);
};
const headers = [
{
id: "name",
content: (
<Box onClick={() => handleSort("name")}>
Maintenance Window Name
<span
style={{
visibility: sort.field === "name" ? "visible" : "hidden",
}}
>
{sort.order === "asc" ? (
<ArrowUpwardRoundedIcon />
) : (
<ArrowDownwardRoundedIcon />
)}
</span>
</Box>
),
render: (row) => row.name,
},
{
id: "status",
content: (
<Box onClick={() => handleSort("status")}>
{" "}
Status
<span
style={{
visibility: sort.field === "active" ? "visible" : "hidden",
}}
>
{sort.order === "asc" ? (
<ArrowUpwardRoundedIcon />
) : (
<ArrowDownwardRoundedIcon />
)}
</span>
</Box>
),
render: (row) => {
const status = row.active ? "up" : "paused";
const text = row.active ? "active" : "paused";
return (
<StatusLabel
status={status}
text={text}
customStyles={{ textTransform: "capitalize" }}
/>
);
},
},
{
id: "nextWindow",
content: "Next window",
render: (row) => {
return getTimeToNextWindow(row.start, row.end, row.repeat);
},
},
{
id: "repeat",
content: "Repeat",
render: (row) => {
return row.repeat === 0 ? "N/A" : formatDurationRounded(row.repeat);
},
},
{
id: "actions",
content: "Actions",
render: (row) => (
<ActionsMenu
maintenanceWindow={row}
updateCallback={updateCallback}
/>
),
},
];
const getTimeToNextWindow = (startTime, endTime, repeat) => {
//1. Advance time closest to next window as possible
const now = dayjs();
@@ -153,26 +165,6 @@ const MaintenanceTable = ({
}
};
const handleChangeRowsPerPage = (event) => {
dispatch(
setRowsPerPage({
value: parseInt(event.target.value, 10),
table: "maintenance",
})
);
setPage(0);
};
/**
* Helper function to calculate the range of displayed rows.
* @returns {string}
*/
const getRange = () => {
let start = page * rowsPerPage + 1;
let end = Math.min(page * rowsPerPage + rowsPerPage, maintenanceWindowCount);
return `${start} - ${end}`;
};
const handleSort = async (field) => {
let order = "";
if (sort.field !== field) {
@@ -183,165 +175,21 @@ const MaintenanceTable = ({
setSort({ field, order });
};
console.log(handleChangePage);
return (
<>
<TableContainer component={Paper}>
<Table>
<TableHead>
<TableRow>
<TableCell
sx={{ cursor: "pointer" }}
onClick={() => handleSort("name")}
>
<Box>
Maintenance Window Name
<span
style={{
visibility: sort.field === "name" ? "visible" : "hidden",
}}
>
{sort.order === "asc" ? (
<ArrowUpwardRoundedIcon />
) : (
<ArrowDownwardRoundedIcon />
)}
</span>
</Box>
</TableCell>
<TableCell
sx={{ cursor: "pointer" }}
onClick={() => handleSort("status")}
>
{" "}
<Box width="max-content">
{" "}
Status
<span
style={{
visibility: sort.field === "active" ? "visible" : "hidden",
}}
>
{sort.order === "asc" ? (
<ArrowUpwardRoundedIcon />
) : (
<ArrowDownwardRoundedIcon />
)}
</span>
</Box>
</TableCell>
<TableCell>Next Window</TableCell>
<TableCell>Repeat</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{maintenanceWindows.map((maintenanceWindow) => {
const text = maintenanceWindow.active ? "active" : "paused";
const status = maintenanceWindow.active ? "up" : "paused";
return (
<TableRow key={maintenanceWindow._id}>
<TableCell>{maintenanceWindow.name}</TableCell>
<TableCell>
<StatusLabel
status={status}
text={text}
customStyles={{ textTransform: "capitalize" }}
/>
</TableCell>
<TableCell>
{getTimeToNextWindow(
maintenanceWindow.start,
maintenanceWindow.end,
maintenanceWindow.repeat
)}
</TableCell>
<TableCell>
{maintenanceWindow.repeat === 0
? "N/A"
: formatDurationRounded(maintenanceWindow.repeat)}
</TableCell>
<TableCell>
<ActionsMenu
maintenanceWindow={maintenanceWindow}
updateCallback={updateCallback}
/>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
<Stack
direction="row"
alignItems="center"
justifyContent="space-between"
px={theme.spacing(4)}
>
<Typography
px={theme.spacing(2)}
variant="body2"
sx={{ opacity: 0.7 }}
>
Showing {getRange()} of {maintenanceWindowCount} maintenance window(s)
</Typography>
<TablePagination
component="div"
count={maintenanceWindowCount}
page={page}
onPageChange={handleChangePage}
rowsPerPage={rowsPerPage}
rowsPerPageOptions={[5, 10, 15, 25]}
onRowsPerPageChange={handleChangeRowsPerPage}
ActionsComponent={TablePaginationActions}
labelRowsPerPage="Rows per page"
labelDisplayedRows={({ page, count }) =>
`Page ${page + 1} of ${Math.max(0, Math.ceil(count / rowsPerPage))}`
}
slotProps={{
select: {
MenuProps: {
keepMounted: true,
disableScrollLock: true,
PaperProps: {
className: "pagination-dropdown",
sx: {
mt: 0,
mb: theme.spacing(2),
},
},
transformOrigin: { vertical: "bottom", horizontal: "left" },
anchorOrigin: { vertical: "top", horizontal: "left" },
sx: { mt: theme.spacing(-2) },
},
inputProps: { id: "pagination-dropdown" },
IconComponent: SelectorVertical,
sx: {
ml: theme.spacing(4),
mr: theme.spacing(12),
minWidth: theme.spacing(20),
textAlign: "left",
"&.Mui-focused > div": {
backgroundColor: theme.palette.background.main,
},
},
},
}}
sx={{
mt: theme.spacing(6),
color: theme.palette.text.secondary,
"& svg path": {
stroke: theme.palette.text.tertiary,
strokeWidth: 1.3,
},
"& .MuiSelect-select": {
border: 1,
borderColor: theme.palette.border.light,
borderRadius: theme.shape.borderRadius,
},
}}
/>
</Stack>
<DataTable
headers={headers}
data={maintenanceWindows}
/>
<Pagination
itemCount={maintenanceWindowCount}
page={page}
rowsPerPage={rowsPerPage}
handleChangePage={handleChangePage}
handleChangeRowsPerPage={handleChangeRowsPerPage}
/>
</>
);
};
+7 -6
View File
@@ -8,13 +8,14 @@ import { useSelector } from "react-redux";
import { networkService } from "../../main";
import Breadcrumbs from "../../Components/Breadcrumbs";
import { useNavigate } from "react-router-dom";
import { useIsAdmin } from "../../Hooks/useIsAdmin";
const Maintenance = ({ isAdmin }) => {
const Maintenance = () => {
const theme = useTheme();
const navigate = useNavigate();
const { authToken } = useSelector((state) => state.auth);
const { rowsPerPage } = useSelector((state) => state.ui.maintenance);
const isAdmin = useIsAdmin();
const [maintenanceWindows, setMaintenanceWindows] = useState([]);
const [maintenanceWindowCount, setMaintenanceWindowCount] = useState(0);
const [page, setPage] = useState(0);
@@ -50,10 +51,10 @@ const Maintenance = ({ isAdmin }) => {
':has(> [class*="fallback__"])': {
position: "relative",
border: 1,
borderColor: theme.palette.border.light,
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
borderStyle: "dashed",
backgroundColor: theme.palette.background.main,
backgroundColor: theme.palette.primary.main,
overflow: "hidden",
},
}}
@@ -69,7 +70,7 @@ const Maintenance = ({ isAdmin }) => {
<Breadcrumbs list={[{ name: "maintenance", path: "/maintenance" }]} />
<Button
variant="contained"
color="primary"
color="accent"
onClick={() => {
navigate("/maintenance/create");
}}
@@ -99,7 +100,7 @@ const Maintenance = ({ isAdmin }) => {
"Stop sending alerts in maintenance windows",
]}
link="/maintenance/create"
isAdmin={true}
isAdmin={isAdmin}
/>
)}
</Box>
@@ -1,346 +0,0 @@
import { useTheme } from "@emotion/react";
import PropTypes from "prop-types";
import {
BarChart,
Bar,
XAxis,
ResponsiveContainer,
Cell,
RadialBarChart,
RadialBar,
} from "recharts";
import { memo, useMemo, useState } from "react";
import { useSelector } from "react-redux";
import { formatDateWithTz } from "../../../../Utils/timeUtils";
const CustomLabels = ({ x, width, height, firstDataPoint, lastDataPoint, type }) => {
const uiTimezone = useSelector((state) => state.ui.timezone);
const dateFormat = type === "day" ? "MMM D, h:mm A" : "MMM D";
return (
<>
<text
x={x}
y={height}
dy={-3}
textAnchor="start"
fontSize={11}
>
{formatDateWithTz(new Date(firstDataPoint.time), dateFormat, uiTimezone)}
</text>
<text
x={width}
y={height}
dy={-3}
textAnchor="end"
fontSize={11}
>
{formatDateWithTz(new Date(lastDataPoint.time), dateFormat, uiTimezone)}
</text>
</>
);
};
CustomLabels.propTypes = {
x: PropTypes.number.isRequired,
width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
firstDataPoint: PropTypes.object.isRequired,
lastDataPoint: PropTypes.object.isRequired,
type: PropTypes.string.isRequired,
};
const UpBarChart = memo(({ data, type, onBarHover }) => {
const theme = useTheme();
const [chartHovered, setChartHovered] = useState(false);
const [hoveredBarIndex, setHoveredBarIndex] = useState(null);
const getColorRange = (uptime) => {
return uptime > 80
? { main: theme.palette.success.main, light: theme.palette.success.light }
: uptime > 50
? { main: theme.palette.warning.main, light: theme.palette.warning.light }
: { main: theme.palette.error.contrastText, light: theme.palette.error.light };
};
// TODO - REMOVE THIS LATER
const reversedData = useMemo(() => [...data].reverse(), [data]);
return (
<ResponsiveContainer
width="100%"
minWidth={210}
height={155}
>
<BarChart
width="100%"
height="100%"
data={reversedData}
onMouseEnter={() => {
setChartHovered(true);
onBarHover({ time: null, totalChecks: 0, uptimePercentage: 0 });
}}
onMouseLeave={() => {
setChartHovered(false);
setHoveredBarIndex(null);
onBarHover(null);
}}
>
<XAxis
stroke={theme.palette.border.dark}
height={15}
tick={false}
label={
<CustomLabels
x={0}
y={0}
width="100%"
height="100%"
firstDataPoint={reversedData[0]}
lastDataPoint={reversedData[reversedData.length - 1]}
type={type}
/>
}
/>
<Bar
dataKey="totalChecks"
maxBarSize={7}
background={{ fill: "transparent" }}
>
{reversedData.map((entry, index) => {
let { main, light } = getColorRange(entry.uptimePercentage);
return (
<Cell
key={`cell-${entry.time}`}
fill={hoveredBarIndex === index ? main : chartHovered ? light : main}
onMouseEnter={() => {
setHoveredBarIndex(index);
onBarHover(entry);
}}
onMouseLeave={() => {
setHoveredBarIndex(null);
onBarHover({
time: null,
totalChecks: 0,
uptimePercentage: 0,
});
}}
/>
);
})}
</Bar>
</BarChart>
</ResponsiveContainer>
);
});
// Add display name for the component
UpBarChart.displayName = "UpBarChart";
// Validate props using PropTypes
UpBarChart.propTypes = {
data: PropTypes.arrayOf(PropTypes.object),
type: PropTypes.string,
onBarHover: PropTypes.func,
};
export { UpBarChart };
const DownBarChart = memo(({ data, type, onBarHover }) => {
const theme = useTheme();
const [chartHovered, setChartHovered] = useState(false);
const [hoveredBarIndex, setHoveredBarIndex] = useState(null);
// TODO - REMOVE THIS LATER
const reversedData = useMemo(() => [...data].reverse(), [data]);
return (
<ResponsiveContainer
width="100%"
minWidth={250}
height={155}
>
<BarChart
width="100%"
height="100%"
data={reversedData}
onMouseEnter={() => {
setChartHovered(true);
onBarHover({ time: null, totalIncidents: 0 });
}}
onMouseLeave={() => {
setChartHovered(false);
setHoveredBarIndex(null);
onBarHover(null);
}}
>
<XAxis
stroke={theme.palette.border.dark}
height={15}
tick={false}
label={
<CustomLabels
x={0}
y={0}
width="100%"
height="100%"
firstDataPoint={reversedData[0]}
lastDataPoint={reversedData[reversedData.length - 1]}
type={type}
/>
}
/>
<Bar
dataKey="totalIncidents"
maxBarSize={7}
background={{ fill: "transparent" }}
>
{reversedData.map((entry, index) => (
<Cell
key={`cell-${entry.time}`}
fill={
hoveredBarIndex === index
? theme.palette.error.contrastText
: chartHovered
? theme.palette.error.light
: theme.palette.error.contrastText
}
onMouseEnter={() => {
setHoveredBarIndex(index);
onBarHover(entry);
}}
onMouseLeave={() => {
setHoveredBarIndex(null);
onBarHover({ time: null, totalIncidents: 0 });
}}
/>
))}
</Bar>
</BarChart>
</ResponsiveContainer>
);
});
DownBarChart.displayName = "DownBarChart";
DownBarChart.propTypes = {
data: PropTypes.arrayOf(PropTypes.object),
type: PropTypes.string,
onBarHover: PropTypes.func,
};
export { DownBarChart };
const ResponseGaugeChart = ({ data }) => {
const theme = useTheme();
let max = 1000; // max ms
const memoizedData = useMemo(
() => [{ response: max, fill: "transparent", background: false }, ...data],
[data[0].response]
);
let responseTime = Math.floor(memoizedData[1].response);
let responseProps =
responseTime <= 200
? {
category: "Excellent",
main: theme.palette.success.main,
bg: theme.palette.success.contrastText,
}
: responseTime <= 500
? {
category: "Fair",
main: theme.palette.success.main,
bg: theme.palette.success.contrastText,
}
: responseTime <= 600
? {
category: "Acceptable",
main: theme.palette.warning.main,
bg: theme.palette.warning.dark,
}
: {
category: "Poor",
main: theme.palette.error.contrastText,
bg: theme.palette.error.dark,
};
return (
<ResponsiveContainer
width="100%"
minWidth={210}
height={155}
>
<RadialBarChart
width="100%"
height="100%"
cy="89%"
data={memoizedData}
startAngle={180}
endAngle={0}
innerRadius={100}
outerRadius={150}
>
<text
x={0}
y="100%"
dx="5%"
dy={-2}
textAnchor="start"
fontSize={11}
>
low
</text>
<text
x="100%"
y="100%"
dx="-3%"
dy={-2}
textAnchor="end"
fontSize={11}
>
high
</text>
<text
x="50%"
y="45%"
textAnchor="middle"
dominantBaseline="middle"
fontSize={18}
fontWeight={400}
>
{responseProps.category}
</text>
<text
x="50%"
y="55%"
textAnchor="middle"
dominantBaseline="hanging"
fontSize={25}
>
<tspan fontWeight={600}>{responseTime}</tspan> <tspan opacity={0.8}>ms</tspan>
</text>
<RadialBar
background={{ fill: responseProps.bg }}
clockWise
dataKey="response"
stroke="none"
>
<Cell
fill="transparent"
background={false}
barSize={0}
/>
<Cell fill={responseProps.main} />
</RadialBar>
</RadialBarChart>
</ResponsiveContainer>
);
};
ResponseGaugeChart.propTypes = {
data: PropTypes.arrayOf(PropTypes.object).isRequired,
};
export { ResponseGaugeChart };
Whitespace-only changes.
@@ -1,96 +0,0 @@
import { Box, Stack, styled } from "@mui/material";
export const ChartBox = styled(Stack)(({ theme }) => ({
flex: "1 30%",
gap: theme.spacing(8),
height: 300,
minWidth: 250,
padding: theme.spacing(8),
border: 1,
borderStyle: "solid",
borderColor: theme.palette.border.light,
borderRadius: 4,
backgroundColor: theme.palette.background.main,
"& h2": {
color: theme.palette.text.secondary,
fontSize: 15,
fontWeight: 500,
},
"& .MuiBox-root:not(.area-tooltip) p": {
color: theme.palette.text.tertiary,
fontSize: 13,
},
"& .MuiBox-root > span": {
color: theme.palette.text.primary,
fontSize: 20,
"& span": {
opacity: 0.8,
marginLeft: 2,
fontSize: 15,
},
},
"& .MuiStack-root": {
flexDirection: "row",
gap: theme.spacing(6),
},
"& .MuiStack-root:first-of-type": {
alignItems: "center",
},
"& tspan, & text": {
fill: theme.palette.text.tertiary,
},
"& path": {
transition: "fill 300ms ease, stroke-width 400ms ease",
},
}));
export const IconBox = styled(Box)(({ theme }) => ({
height: 34,
minWidth: 34,
width: 34,
position: "relative",
border: 1,
borderStyle: "solid",
borderColor: theme.palette.border.dark,
borderRadius: 4,
backgroundColor: theme.palette.background.accent,
"& svg": {
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 20,
height: 20,
"& path": {
stroke: theme.palette.text.tertiary,
},
},
}));
export const StatBox = styled(Box)(({ theme }) => ({
padding: `${theme.spacing(4)} ${theme.spacing(8)}`,
minWidth: 200,
width: 225,
border: 1,
borderStyle: "solid",
borderColor: theme.palette.border.light,
borderRadius: 4,
backgroundColor: theme.palette.background.main,
background: `linear-gradient(340deg, ${theme.palette.background.accent} 20%, ${theme.palette.background.main} 45%)`,
"& h2": {
fontSize: 13,
fontWeight: 500,
color: theme.palette.text.secondary,
textTransform: "uppercase",
},
"& p": {
fontSize: 18,
color: theme.palette.text.primary,
marginTop: theme.spacing(2),
"& span": {
color: theme.palette.text.tertiary,
marginLeft: theme.spacing(2),
fontSize: 15,
},
},
}));
@@ -1,69 +0,0 @@
import { useTheme } from "@emotion/react";
import { Box, Stack } from "@mui/material";
import Search from "../../../../Components/Inputs/Search";
import MemoizedMonitorTable from "../MonitorTable";
import { useState } from "react";
import useDebounce from "../../../../Utils/debounce";
import PropTypes from "prop-types";
import { Heading } from "../../../../Components/Heading";
const CurrentMonitoring = ({ totalMonitors, monitors, isAdmin }) => {
const theme = useTheme();
const [search, setSearch] = useState("");
const [isSearching, setIsSearching] = useState(false);
const debouncedFilter = useDebounce(search, 500);
const handleSearch = (value) => {
setIsSearching(true);
setSearch(value);
};
return (
<Box
flex={1}
py={theme.spacing(8)}
>
<Stack
direction="row"
alignItems="center"
mb={theme.spacing(8)}
>
<Heading component="h2">Actively monitoring</Heading>
<Box
className="current-monitors-counter"
color={theme.palette.text.primary}
border={1}
borderColor={theme.palette.border.light}
backgroundColor={theme.palette.background.accent}
>
{totalMonitors}
</Box>
<Box
width="25%"
minWidth={150}
ml="auto"
>
<Search
options={monitors}
filteredBy="name"
inputValue={search}
handleInputChange={handleSearch}
/>
</Box>
</Stack>
<MemoizedMonitorTable
isAdmin={isAdmin}
filter={debouncedFilter}
setIsSearching={setIsSearching}
isSearching={isSearching}
/>
</Box>
);
};
CurrentMonitoring.propTypes = {
totalMonitors: PropTypes.number,
monitors: PropTypes.array,
isAdmin: PropTypes.bool,
};
export { CurrentMonitoring };
@@ -1,32 +0,0 @@
import { Skeleton, TableCell, TableRow } from "@mui/material";
const ROWS_NUMBER = 7;
const ROWS_ARRAY = Array.from({ length: ROWS_NUMBER }, (_, i) => i);
const TableBodySkeleton = () => {
/* TODO Skeleton does not follow light and dark theme */
return (
<>
{ROWS_ARRAY.map((row) => (
<TableRow key={row}>
<TableCell>
<Skeleton />
</TableCell>
<TableCell>
<Skeleton />
</TableCell>
<TableCell>
<Skeleton />
</TableCell>
<TableCell>
<Skeleton />
</TableCell>
<TableCell>
<Skeleton />
</TableCell>
</TableRow>
))}
</>
);
};
export { TableBodySkeleton };
@@ -1,331 +0,0 @@
import PropTypes from "prop-types";
import { useState, useEffect, memo, useCallback, useRef } from "react";
import { useNavigate } from "react-router";
import { useDispatch, useSelector } from "react-redux";
import { useTheme } from "@emotion/react";
import useUtils from "../../utils";
import { setRowsPerPage } from "../../../../Features/UI/uiSlice";
import { logger } from "../../../../Utils/Logger";
import { jwtDecode } from "jwt-decode";
import { networkService } from "../../../../main";
import {
TableContainer,
Table,
TableHead,
TableRow,
TableCell,
TableBody,
Paper,
Box,
CircularProgress,
} from "@mui/material";
import ActionsMenu from "../actionsMenu";
import Host from "../host";
import { StatusLabel } from "../../../../Components/Label";
import { TableBodySkeleton } from "./Skeleton";
import BarChart from "../../../../Components/Charts/BarChart";
import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded";
import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded";
import { Pagination } from "../../../Infrastructure/components/TablePagination";
const MonitorTable = ({ isAdmin, filter, setIsSearching, isSearching }) => {
const theme = useTheme();
const navigate = useNavigate();
const dispatch = useDispatch();
const { determineState } = useUtils();
const { rowsPerPage } = useSelector((state) => state.ui.monitors);
const authState = useSelector((state) => state.auth);
const [page, setPage] = useState(0);
const [monitors, setMonitors] = useState([]);
const [monitorCount, setMonitorCount] = useState(0);
const [updateTrigger, setUpdateTrigger] = useState(false);
const [sort, setSort] = useState({});
const prevFilter = useRef(filter);
const handleActionMenuDelete = () => {
setUpdateTrigger((prev) => !prev);
};
const handleChangePage = (event, newPage) => {
setPage(newPage);
};
const handleChangeRowsPerPage = (event) => {
dispatch(
setRowsPerPage({
value: parseInt(event.target.value, 10),
table: "monitors",
})
);
setPage(0);
};
const fetchPage = useCallback(async () => {
try {
const { authToken } = authState;
const user = jwtDecode(authToken);
const res = await networkService.getMonitorsByTeamId({
authToken,
teamId: user.teamId,
limit: 25,
types: ["http", "ping", "docker"],
status: null,
checkOrder: "desc",
normalize: true,
page: page,
rowsPerPage: rowsPerPage,
filter: filter,
field: sort.field,
order: sort.order,
});
setMonitors(res?.data?.data?.monitors ?? []);
setMonitorCount(res?.data?.data?.monitorCount ?? 0);
} catch (error) {
logger.error(error);
} finally {
setIsSearching(false);
}
}, [authState, page, rowsPerPage, filter, sort, setIsSearching]);
useEffect(() => {
fetchPage();
}, [
updateTrigger,
authState,
page,
rowsPerPage,
filter,
sort,
setIsSearching,
fetchPage,
]);
// Listen for changes in filter, if new value reset the page
useEffect(() => {
if (prevFilter.current !== filter) {
setPage(0);
fetchPage();
}
prevFilter.current = filter;
}, [filter, fetchPage]);
const handleSort = async (field) => {
let order = "";
if (sort.field !== field) {
order = "desc";
} else {
order = sort.order === "asc" ? "desc" : "asc";
}
setSort({ field, order });
const { authToken } = authState;
const user = jwtDecode(authToken);
const res = await networkService.getMonitorsByTeamId({
authToken,
teamId: user.teamId,
limit: 25,
types: ["http", "ping"],
status: null,
checkOrder: "desc",
normalize: true,
page: page,
rowsPerPage: rowsPerPage,
filter: null,
field: field,
order: order,
});
setMonitors(res?.data?.data?.monitors ?? []);
setMonitorCount(res?.data?.data?.monitorCount ?? 0);
};
return (
<Box position="relative">
{isSearching && (
<>
<Box
width="100%"
height="100%"
position="absolute"
sx={{
backgroundColor: theme.palette.background.main,
opacity: 0.8,
zIndex: 100,
}}
/>
<Box
height="100%"
position="absolute"
top="20%"
left="50%"
sx={{
transform: "translateX(-50%)",
zIndex: 101,
}}
>
<CircularProgress
sx={{
color: theme.palette.other.icon,
}}
/>
</Box>
</>
)}
<TableContainer component={Paper}>
<Table>
<TableHead>
<TableRow>
<TableCell
sx={{ cursor: "pointer" }}
onClick={() => handleSort("name")}
>
<Box>
Host
<span
style={{
visibility: sort.field === "name" ? "visible" : "hidden",
}}
>
{sort.order === "asc" ? (
<ArrowUpwardRoundedIcon />
) : (
<ArrowDownwardRoundedIcon />
)}
</span>
</Box>
</TableCell>
<TableCell
sx={{ cursor: "pointer" }}
onClick={() => handleSort("status")}
>
{" "}
<Box width="max-content">
{" "}
Status
<span
style={{
visibility: sort.field === "status" ? "visible" : "hidden",
}}
>
{sort.order === "asc" ? (
<ArrowUpwardRoundedIcon />
) : (
<ArrowDownwardRoundedIcon />
)}
</span>
</Box>
</TableCell>
<TableCell>Response Time</TableCell>
<TableCell>Type</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{/* TODO add empty state. Check if is searching, and empty => skeleton. Is empty, not searching => skeleton */}
{monitors.length > 0 ? (
monitors.map((monitor) => {
let uptimePercentage = "";
let percentageColor = theme.palette.percentage.uptimeExcellent;
// Determine uptime percentage and color based on the monitor's uptimePercentage value
if (monitor.uptimePercentage !== undefined) {
uptimePercentage =
monitor.uptimePercentage === 0
? "0"
: (monitor.uptimePercentage * 100).toFixed(2);
percentageColor =
monitor.uptimePercentage < 0.25
? theme.palette.percentage.uptimePoor
: monitor.uptimePercentage < 0.5
? theme.palette.percentage.uptimeFair
: monitor.uptimePercentage < 0.75
? theme.palette.percentage.uptimeGood
: theme.palette.percentage.uptimeExcellent;
}
const params = {
url: monitor.url,
title: monitor.name,
percentage: uptimePercentage,
percentageColor,
status: determineState(monitor),
};
return (
<TableRow
key={monitor._id}
sx={{
cursor: "pointer",
"&:hover": {
backgroundColor: theme.palette.background.accent,
},
}}
onClick={() => {
navigate(`/monitors/${monitor._id}`);
}}
>
<TableCell>
<Host
key={monitor._id}
url={params.url}
title={params.title}
percentageColor={params.percentageColor}
percentage={params.percentage}
/>
</TableCell>
<TableCell>
<StatusLabel
status={params.status}
text={params.status}
customStyles={{ textTransform: "capitalize" }}
/>
</TableCell>
<TableCell>
<BarChart checks={monitor.checks.slice().reverse()} />
</TableCell>
<TableCell>
<span style={{ textTransform: "uppercase" }}>{monitor.type}</span>
</TableCell>
<TableCell>
<ActionsMenu
monitor={monitor}
isAdmin={isAdmin}
updateCallback={handleActionMenuDelete}
/>
</TableCell>
</TableRow>
);
})
) : (
<TableBodySkeleton />
)}
</TableBody>
</Table>
</TableContainer>
<Pagination
monitorCount={monitorCount}
page={page}
rowsPerPage={rowsPerPage}
handleChangePage={handleChangePage}
handleChangeRowsPerPage={handleChangeRowsPerPage}
/>
</Box>
);
};
MonitorTable.propTypes = {
isAdmin: PropTypes.bool,
filter: PropTypes.string,
setIsSearching: PropTypes.func,
isSearching: PropTypes.bool,
};
const MemoizedMonitorTable = memo(MonitorTable);
export default MemoizedMonitorTable;
-20
View File
@@ -1,20 +0,0 @@
/* TODO take these from here and declare using emotion. Plus, the values should live in the theme */
.monitors .MuiStack-root > button:not(.MuiIconButton-root) {
font-size: var(--env-var-font-size-medium);
height: var(--env-var-height-2);
min-width: fit-content;
}
.current-monitors-counter {
display: flex;
justify-content: center;
align-items: center;
padding: 5px;
min-width: 25px;
min-height: 25px;
border-radius: 50%;
font-size: var(--env-var-font-size-medium);
font-weight: 500;
margin-left: 10px;
line-height: 0.8;
}
-107
View File
@@ -1,107 +0,0 @@
import "./index.css";
import { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { getUptimeMonitorsByTeamId } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice";
import { useNavigate } from "react-router-dom";
import { useTheme } from "@emotion/react";
import { Box, Button, Stack } from "@mui/material";
import PropTypes from "prop-types";
import SkeletonLayout from "./skeleton";
import Fallback from "./fallback";
import StatusBox from "./StatusBox";
import Breadcrumbs from "../../../Components/Breadcrumbs";
import Greeting from "../../../Utils/greeting";
import { CurrentMonitoring } from "./CurrentMonitoring";
const Monitors = ({ isAdmin }) => {
const theme = useTheme();
const navigate = useNavigate();
const monitorState = useSelector((state) => state.uptimeMonitors);
const authState = useSelector((state) => state.auth);
const dispatch = useDispatch({});
useEffect(() => {
dispatch(getUptimeMonitorsByTeamId(authState.authToken));
}, [authState.authToken, dispatch]);
//TODO bring fetching to this component, like on pageSpeed
const loading = monitorState?.isLoading;
const totalMonitors = monitorState?.monitorsSummary?.monitorCounts?.total;
const hasMonitors = totalMonitors > 0;
const noMonitors = !hasMonitors;
const canAddMonitor = isAdmin && hasMonitors;
return (
<Stack
className="monitors"
gap={theme.spacing(8)}
>
<Box>
<Breadcrumbs list={[{ name: `monitors`, path: "/monitors" }]} />
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
mt={theme.spacing(5)}
gap={theme.spacing(6)}
>
<Greeting type="uptime" />
{canAddMonitor && (
<Button
variant="contained"
color="primary"
onClick={() => {
navigate("/monitors/create");
}}
sx={{ fontWeight: 500 }}
>
Create monitor
</Button>
)}
</Stack>
</Box>
{noMonitors && <Fallback isAdmin={isAdmin} />}
{loading ? (
<SkeletonLayout />
) : (
<>
{hasMonitors && (
<>
<Stack
gap={theme.spacing(8)}
direction="row"
justifyContent="space-between"
>
<StatusBox
title="up"
value={monitorState?.monitorsSummary?.monitorCounts?.up ?? 0}
/>
<StatusBox
title="down"
value={monitorState?.monitorsSummary?.monitorCounts?.down ?? 0}
/>
<StatusBox
title="paused"
value={monitorState?.monitorsSummary?.monitorCounts?.paused ?? 0}
/>
</Stack>
<CurrentMonitoring
isAdmin={isAdmin}
monitors={monitorState.monitorsSummary.monitors}
totalMonitors={totalMonitors}
/>
</>
)}
</>
)}
</Stack>
);
};
Monitors.propTypes = {
isAdmin: PropTypes.bool,
};
export default Monitors;
-90
View File
@@ -1,90 +0,0 @@
import { useTheme } from "@mui/material";
const useUtils = () => {
const determineState = (monitor) => {
if (monitor.isActive === false) return "paused";
if (monitor?.status === undefined) return "pending";
return monitor?.status == true ? "up" : "down";
};
/* TODO Refactor: from here on shouldn't live in a custom hook, but on theme, or constants */
const theme = useTheme();
const statusColor = {
up: theme.palette.success.main,
down: theme.palette.error.main,
paused: theme.palette.warning.main,
pending: theme.palette.warning.main,
};
const statusMsg = {
up: "Your site is up.",
down: "Your site is down.",
paused: "Pending...",
};
const pagespeedStatusMsg = {
up: "Live (collecting data)",
down: "Inactive",
paused: "Paused",
};
const statusStyles = {
up: {
backgroundColor: theme.palette.success.dark,
background: `linear-gradient(340deg, ${theme.palette.success.dark} -60%, ${theme.palette.success.light} 35%)`,
borderColor: theme.palette.success.light,
"& h2": { color: theme.palette.success.main },
},
down: {
backgroundColor: theme.palette.error.dark,
background: `linear-gradient(340deg, ${theme.palette.error.light} -60%, ${theme.palette.error.dark} 35%)`,
borderColor: theme.palette.error.light,
"& h2": { color: theme.palette.error.main },
},
paused: {
backgroundColor: theme.palette.warning.dark,
background: `linear-gradient(340deg, ${theme.palette.warning.light} -60%, ${theme.palette.warning.dark} 35%)`,
borderColor: theme.palette.warning.light,
"& h2": { color: theme.palette.warning.main },
},
pending: {
backgroundColor: theme.palette.warning.light,
background: `linear-gradient(340deg, ${theme.palette.warning.dark} -60%, ${theme.palette.warning.light} 35%)`,
borderColor: theme.palette.warning.dark,
"& h2": { color: theme.palette.warning.main },
},
};
const pagespeedStyles = {
up: {
bg: theme.palette.success.dark,
light: theme.palette.success.light,
stroke: theme.palette.success.main,
},
down: {
bg: theme.palette.error.dark,
light: theme.palette.error.light,
stroke: theme.palette.error.main,
},
paused: {
bg: theme.palette.warning.dark,
light: theme.palette.warning.light,
stroke: theme.palette.warning.main,
},
pending: {
bg: theme.palette.warning.dark,
light: theme.palette.warning.light,
stroke: theme.palette.warning.main,
},
};
return {
determineState,
statusColor,
statusMsg,
pagespeedStatusMsg,
pagespeedStyles,
statusStyles,
};
};
export default useUtils;
Loaded 100 of 279 files, more files were not shown because too many files have changed in this diff. Show more