Compare commits

...
Author SHA1 Message Date
gorkem-bwl d3947681a2 fix: Add missing translation keys for container monitoring
Added keys: containers, noContainersAvailable, noContainerSelected,
noContainerStatsAvailable, container, diskIO, networkIO, diskRead,
diskWrite, networkIn, networkOut
2026-01-16 20:18:38 -05:00
gorkem-bwl 01084108b0 fix: Include enableProxmoxContainers in monitor update payload
The useUpdateMonitor hook was missing enableProxmoxContainers from the
hardware-specific fields sent to the API, causing the setting to not
persist when editing a monitor.
2026-01-16 19:14:29 -05:00
gorkem-bwl e9da602af6 feat: Add Proxmox LXC container monitoring support
## Summary
Add support for monitoring Proxmox LXC containers through the Capture agent's
/api/v1/metrics/proxmox endpoint. This feature is opt-in via a new setting
in the infrastructure monitor configuration.

## Backend Changes
- Add enableProxmoxContainers field to Monitor schema and types
- Update networkService to conditionally fetch Proxmox metrics
- Handle HTTP 207 multi-status responses and merge errors
- Add containers field to Check schema for storing container data
- Update validation schemas for the new setting

## Frontend Changes
- Add ContainerStats component with charts and stat boxes
- Add Containers tab to infrastructure monitor details (conditional)
- Add enableProxmoxContainers switch in monitor create/edit form
- Fix chart colors for disk I/O and network I/O to be distinct
- Add dotColor2 prop to InfrastructureTooltip for multi-series charts
- Add translation keys to all 16 locale files

## How It Works
1. User enables "Proxmox container monitoring" in monitor settings
2. Checkmate appends /proxmox to the metrics URL when fetching
3. Container data is displayed in a dedicated tab with:
   - CPU, memory, disk, swap, and uptime stat boxes
   - Interactive charts for CPU, memory, disk I/O, network I/O
4. Errors from Capture are merged and logged appropriately
2026-01-16 19:07:49 -05:00
Alexander Holliday 173a4e17d2 Merge pull request #3138 from bluewave-labs/fix/password-special-chars
fix: remove <> from allowed password characters and display allowed special chars
2026-01-16 13:17:12 -08:00
Alexander Holliday 70b197675e Merge pull request #3146 from bluewave-labs/fix/pagespeed-snapshot
remove pagespeed from snapshot array, full check list required
2026-01-16 13:16:37 -08:00
Alex Holliday 11dcccc085 remove pagespeed from snapshot array, full check list required 2026-01-16 21:16:05 +00:00
Alexander Holliday e3e08da259 Merge pull request #3145 from bluewave-labs/fix/pagespeed-stats
fix: stats fixes
2026-01-16 13:06:49 -08:00
Alex Holliday 6447c315ce stats fixes 2026-01-16 21:04:41 +00:00
Gorkem Cetin (BWL) f1a6afb860 Merge pull request #3133 from bluewave-labs/fix/ui-improvements-logs-incidents
Improve UI consistency for Logs and Incidents pages
2026-01-16 14:47:21 -05:00
gorkem-bwl 5c52a208b9 Fix build errors and formatting
- Replace CheckIcon SVG import with Lucide Icon component in
  LatestIncidentsPanel/index.jsx
- Format IncidentItem.jsx and StatisticsPanel.jsx with Prettier
2026-01-16 14:44:28 -05:00
gorkem-bwl 4fdc0bde5c Merge develop into fix/ui-improvements-logs-incidents
Resolve conflicts in IncidentItem.jsx and StatisticsPanel.jsx by
keeping the UI improvements from this branch while adopting the
new Lucide Icon component from develop (replacing old SVG imports).
2026-01-16 14:42:29 -05:00
Alexander Holliday 3d4ebe9588 Merge pull request #3143 from bluewave-labs/fix/responsive-layout-overflow
Fix horizontal overflow causing white space on narrow screens
2026-01-16 11:18:02 -08:00
gorkem-bwl 62ea7b0e5b Fix horizontal overflow causing white space on narrow screens
Add overflow-x: hidden and width: 100% to .home-layout to prevent
horizontal scrollbar. Add min-width: 0 to .home-content-wrapper to
allow flexbox child to shrink below content size at narrow widths.
2026-01-16 14:09:37 -05:00
Alexander Holliday 8747c38f40 Merge pull request #3142 from bluewave-labs/fix/infra-config
infra fix
2026-01-16 10:38:57 -08:00
Alex Holliday d633fc5ccb infra fix 2026-01-16 18:37:14 +00:00
Alexander Holliday 0d7a35367f Merge pull request #3141 from bluewave-labs/feat/job-queue-ts
feat/job queue ts
2026-01-16 10:25:40 -08:00
Alex Holliday af141ec772 build fix 2026-01-16 18:25:28 +00:00
Alex Holliday 5784fec3d1 format 2026-01-16 18:17:55 +00:00
Alex Holliday 95cd33ac06 queue and buffer refactor 2026-01-16 18:11:04 +00:00
Gorkem Cetin (BWL) 2a591ee503 Merge pull request #3139 from bluewave-labs/feat/lucide-icon-migration
Migrate icons from custom SVGs and MUI to Lucide React
2026-01-16 12:50:21 -05:00
gorkem-bwl f5aa24d77e fix: rename Image import to ImageIcon to avoid conflicts 2026-01-16 07:34:03 -05:00
gorkem-bwl 7874439e4c fix: address PR review feedback for icon migration
- Use AlertCircle for error and AlertTriangle for warning icons
- Use Icon color prop instead of CSS fill for Lucide stroke-based icons
- Replace wildcard import with explicit named imports for tree-shaking
- Replace sx prop with style prop on Icon components
- Add Trash2 to Icon component imports
2026-01-16 07:31:23 -05:00
gorkem-bwl 7140d436a5 style: apply Prettier formatting 2026-01-15 23:39:26 -05:00
gorkem-bwl ba0c89fc6c feat: migrate icons from custom SVGs and MUI to Lucide React
Replace custom SVG icons and @mui/icons-material with lucide-react for consistent,
lightweight icon management across the application.

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

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

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

Fixes #3010
2026-01-15 22:50:29 -05:00
Alexander Holliday b5caabed20 Merge pull request #3135 from bluewave-labs/fix/pagespeed-monitor-delete
Fix pagespeed monitor deletion failing with undefined ID
2026-01-15 18:29:34 -08:00
gorkem-bwl 8814596d6e Fix pagespeed monitor deletion failing with undefined ID
The delete handler was using monitor._id from state, which was undefined
because the fetched monitor object didn't have _id populated. Changed to
use monitorId directly from URL params, which is already available and
used by other handlers in the same component (e.g., handlePause).

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

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

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

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

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

## Details

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

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

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

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

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

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

Enables automatic HTTPS certificate provisioning when cert-manager
is installed in the cluster with appropriate ClusterIssuer configured.
2025-11-29 17:46:29 +00:00
Alexander Holliday 1e3ee23120 Merge pull request #3071 from mospell/feat/better_infrastructure_notifications
Feat: Add link to Infrastructure Details for Notifications
2025-11-28 11:52:04 -08:00
Alexander Holliday 74d8e0a892 Merge pull request #3074 from SuryaGanesh284/develop
Add quick setup checklist to CONTRIBUTING.md
2025-11-28 11:51:06 -08:00
Antonin 50af063f64 Merge branch 'develop' into feat/choose-disks-monitor 2025-11-26 11:58:35 +01:00
Antonin 80a66d74ca Add metric/value three & four to propTypes 2025-11-26 11:56:46 +01:00
SuryaGanesh284 6c2908681a Add quick setup checklist to CONTRIBUTING.md
Added a quick setup checklist for contributors.
2025-11-25 22:12:16 +05:30
mospell 90593c0d7c added 'go-to' links to the remaining Discord notifications 2025-11-23 17:18:09 +01:00
mospell 8fb958e772 Feat: Add link to infrastructure details for Notifications 2025-11-23 16:39:09 +01:00
Alexander Holliday fc39ab3fb9 Merge pull request #3069 from bluewave-labs/feat/JSON-export
implement JSON export
2025-11-21 10:45:32 -08:00
Alex Holliday 58e7ef2527 implement JSON export 2025-11-21 10:43:29 -08:00
Alexander Holliday fec32380ae Merge pull request #3067 from bluewave-labs/fix-incidents-creation
Incident Management System - Backend Implementation
2025-11-19 09:35:15 -08:00
karenvicent 3b8d87bd17 Merge branch 'develop' into fix-incidents-creation 2025-11-19 10:06:35 -05:00
karenvicent 131ced10fc Incidents management implementation 2025-11-18 16:01:47 -05:00
antoplt 886752effd Add metricFour and valueFour props 2025-11-18 15:29:25 +01:00
antoplt a5d54a637e Fix function naming error 2025-11-18 14:44:45 +01:00
antoplt 1b4edf16fb Add mountpoint as fourth metric 2025-11-18 14:38:03 +01:00
antoplt 4042d34585 Add mountpoint field to mongoose diskSchema 2025-11-18 14:25:02 +01:00
antoplt 135c3ff454 Merge branch 'develop' into feat/choose-disks-monitor 2025-11-18 13:55:39 +01:00
antoplt f8d5791fab Add device name formatting 2025-11-18 13:13:35 +01:00
antoplt ffa4b1695b Add device field into moogose diskSchema 2025-11-18 13:12:34 +01:00
antoplt 98214ff578 Add disk device as third metric value 2025-11-18 12:06:22 +01:00
Alexander Holliday 57ceac0c24 Merge pull request #3064 from bluewave-labs/statusPage-bars-centred-fix
Status page bars centred fix
2025-11-12 11:02:32 -08:00
Sajan a526bc5a3e Revert changes to lock files 2025-11-12 12:06:34 -06:00
Alexander Holliday fac4516353 Merge pull request #3058 from yuracoff18/doccumentation-fix-and-problem-solving-contributing
Fix the location from the docker images installation and addition of …
2025-11-12 09:21:37 -08:00
Alexander Holliday 4db13fb924 Update CONTRIBUTING.md 2025-11-12 09:21:02 -08:00
Sajan c97ee22e66 Move status validation inside the block 2025-11-12 10:54:55 -06:00
Sajan 98737f92af Add validation for the status prop 2025-11-12 10:34:22 -06:00
Sajan 74637f9d2a Remove fontSize 2025-11-12 10:21:26 -06:00
Sajan 69dbc2fe7a Move up/down chips next to the monitor name 2025-11-12 09:58:38 -06:00
Sajan d157e200f6 Use flex-end instead of right 2025-11-11 10:58:30 -06:00
Sajan 594540cff5 Fix centering issue for status page bars 2025-11-11 10:46:34 -06:00
Sajan 8e47e8cd3d fix formatting issue 2025-11-11 09:26:18 -06:00
Gorkem Cetin (BWL) c5d7625a06 Update installation instructions in README
Removed 'Coolify' from the list of alternatives for Checkmate installation.
2025-11-10 06:11:50 -05:00
Yura 7763c5e288 Fix the location from the docker images installation and addition of a solution from a problem i saw while installing the front 2025-11-09 01:15:05 -05:00
Yura 957988d977 Fix the location from the docker images installation and addition of a solution from a problem i saw while installing the front 2025-11-09 01:08:56 -05:00
Yura 0c04c06d4e centred the bars in status page UI 2025-11-09 00:45:33 -05:00
Yura a7ef014b92 centred the bars in status page UI 2025-11-09 00:41:02 -05:00
Yura 3822d971cb centred the bars in status page UI 2025-11-09 00:28:14 -05:00
karenvicent eb65c41d15 Merge branch 'develop' into fix-incidents-creation 2025-11-06 17:07:24 -05:00
karenvicent 0c3fdbc539 feat: add incident model, module and service with StatusService integration
- Create Incident model with resolution tracking fields
- Implement IncidentModule for data access
- Implement IncidentService with business logic
- Integrate with StatusService to auto-create/resolve incidents on status change
2025-11-06 17:06:33 -05:00
588 changed files with 17408 additions and 16709 deletions

No files matched your search

+3 -1
View File
@@ -1,3 +1,5 @@
.idea
.vscode
.VSCodeCounter
.VSCodeCounter
*.sh
mongo
+145
View File
@@ -0,0 +1,145 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Checkmate is an open-source uptime and infrastructure monitoring application. It monitors server hardware, uptime, response times, and incidents with real-time alerts. The companion agent [Capture](https://github.com/bluewave-labs/capture) provides infrastructure metrics (CPU, RAM, disk, temperature).
## Development Commands
### Client (React/Vite)
```bash
cd client
npm install
npm run dev # Start dev server at http://localhost:5173
npm run build # TypeScript check + production build
npm run lint # ESLint (strict, max-warnings 0)
npm run format # Prettier formatting
npm run format-check # Check formatting
```
### Server (Node.js/Express)
```bash
cd server
npm install
npm run dev # Start with hot-reload (nodemon + tsx) at http://localhost:52345
npm run build # TypeScript compile + path alias resolution
npm run test # Run Mocha tests with c8 coverage
npm run lint # ESLint v9
npm run lint-fix # Auto-fix lint issues
npm run format # Prettier formatting
```
### Docker Development
```bash
cd docker/dev
./build_images.sh
docker run -d -p 27017:27017 -v uptime_mongo_data:/data/db --name uptime_database_mongo mongo:6.0
```
## Environment Setup
### Server `.env` (minimum required)
```env
CLIENT_HOST="http://localhost:5173"
JWT_SECRET="my_secret_key_change_this"
DB_CONNECTION_STRING="mongodb://localhost:27017/uptime_db"
TOKEN_TTL="99d"
ORIGIN="localhost"
LOG_LEVEL="debug"
```
### Client `.env`
```env
VITE_APP_API_BASE_URL="http://localhost:52345/api/v1"
VITE_APP_LOG_LEVEL="debug"
```
## Architecture
### Monorepo Structure
- `/client` - React 18 + TypeScript + Vite + MUI frontend
- `/server` - Node.js 20+ + Express + TypeScript backend
- `/docker` - Multi-environment Docker configs (dev, staging, prod, arm, mono)
### Backend Layers
```
server/src/
├── controllers/ # Route handlers (authController, monitorController, etc.)
├── service/ # Business logic
│ ├── business/ # Core monitoring logic
│ ├── infrastructure/ # Server/system utilities
│ └── system/ # App-level settings
├── db/
│ ├── models/ # Mongoose schemas (Monitor, Check, Incident, User, etc.)
│ ├── migration/ # Database migrations (run on startup)
│ └── modules/ # Database-specific modules
├── middleware/v1/ # verifyJWT, rateLimiter, sanitization, responseHandler
├── routes/v1/ # API route definitions
├── validation/ # Joi input validation schemas
└── repositories/ # Data access layer
```
### Frontend Structure
```
client/src/
├── Components/ # Reusable UI components
├── Pages/ # Page components (Auth, Uptime, Infrastructure, Incidents, etc.)
├── Features/ # Redux slices (Auth, UI)
├── Hooks/ # Custom React hooks
├── Utils/ # Utilities (NetworkService.js is main API client)
├── Validation/ # Input validation
└── locales/ # i18n translations
```
### API
- Base URL: `/api/v1`
- Documentation: `http://localhost:52345/api-docs` (Swagger UI)
- OpenAPI spec: `/server/openapi.json`
### Key Technologies
- **State Management**: Redux Toolkit + Redux-Persist
- **Data Fetching**: SWR + Axios
- **Database**: MongoDB with Mongoose ODM
- **Queue/Cache**: Redis + BullMQ + Pulse (cron scheduling)
- **i18n**: i18next + react-i18next (translations via PoEditor)
## Code Conventions
### Internationalization
All user-facing strings must use the translation function:
```javascript
t('your.key') // Never hardcode UI strings
```
### Branching
- Always branch from `develop` (not master)
- Use descriptive names: `feat/add-alerts`, `fix/login-error`
- PRs target `develop` branch
### Formatting
- **Client**: Prettier with `printWidth: 90`, tabs, double quotes
- **Server**: Prettier with `printWidth: 150`, tabs, double quotes
- Both use ESLint with strict settings
### Testing
Server tests use Mocha + Chai + Sinon:
```bash
npm test # Run all tests with coverage
npm test -- --grep "pattern" # Run specific tests
```
Test files: `server/tests/**/*.test.js`
## Database Models
Key Mongoose models in `/server/src/db/models/`:
- **Monitor** - Monitoring configuration (website, infrastructure, port, etc.)
- **Check** - Individual monitoring check results
- **Incident** - Downtime incidents
- **User** - User accounts
- **Team** - Team/workspace management
- **StatusPage** - Public status pages
- **Notification** - Alert configuration (email, Discord, Slack, webhooks)
- **MaintenanceWindow** - Scheduled maintenance periods
- **AppSettings** - Global application settings
+29 -4
View File
@@ -10,6 +10,31 @@ PS: **We work closely with contributors on our [Discord channel](https://discord
---
## 🚀 Quick Setup Checklist
Before you dive in, make sure you have these installed:
```bash
# Check Node.js (v16+ required)
node --version
# Check npm
npm --version
# Check Docker
docker --version
# Check Git
git --version
```
**New to contributing?** Start here:
1. Pick a [`good-first-issue`](https://github.com/bluewave-labs/checkmate/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
2. Comment that you'd like to work on it
3. Follow the [setup guide](#set-up-checkmate-locally) below
4. Join our [Discord](https://discord.com/invite/NAb6H3UTjK) if you get stuck
## Table of contents
- [How do I...?](#how-do-i)
@@ -66,7 +91,7 @@ cd Checkmate
Navigate to the Docker dev directory:
```bash
cd server/docker/dev
cd docker/dev
```
Build the Docker images:
@@ -78,13 +103,13 @@ Build the Docker images:
Run MongoDB container:
```bash
docker run -d -p 27017:27017 -v $(pwd)/mongo/data:/data/db --name uptime_database_mongo uptime_database_mongo
docker run -d -p 27017:27017 -v uptime_mongo_data:/data/db --name uptime_database_mongo mongo:6.0
```
Navigate back to the root directory:
```bash
cd ../../..
cd ../..
```
#### Step 3: Set Up the Backend (Server)
@@ -161,7 +186,7 @@ Start the frontend:
npm run dev
```
The client will run at `http://localhost:5173`.
The client will run at `http://localhost:5173`.
#### Step 5: Access the Application
+2 -1
View File
@@ -52,7 +52,7 @@ Usage instructions can be found [here](https://docs.checkmate.so/checkmate-2.1).
See installation instructions in [Checkmate documentation portal](https://docs.checkmate.so/checkmate-2.1/users-guide/quickstart).
Alternatively, you can also use [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (South Africa) or [Pikapods](https://www.pikapods.com/) to quickly spin off a Checkmate instance. If you would like to monitor your server infrastructure, you'll need [Capture agent](https://github.com/bluewave-labs/capture). Capture repository also contains the installation instructions.
Alternatively, you can also use [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (South Africa) or [Pikapods](https://www.pikapods.com/) to quickly spin off a Checkmate instance. If you would like to monitor your server infrastructure, you'll need [Capture agent](https://github.com/bluewave-labs/capture). Capture repository also contains the installation instructions.
### Using a Custom CA
@@ -91,6 +91,7 @@ Feel free to ask questions or share your ideas - we'd love to hear from you!
- Website monitoring
- Page speed monitoring
- Infrastructure monitoring (memory, disk usage, CPU performance, network etc) - requires [Capture](https://github.com/bluewave-labs/capture) agent
- Selective disk monitoring with mountpoint selection
- Docker monitoring
- Ping monitoring
- SSL monitoring
+73
View File
@@ -26,6 +26,7 @@ Edit `values.yaml` to update:
- Old: `persistence.redisSize` → New: `persistence.redis.size`
- Add: `persistence.mongo.storageClass` and `persistence.redis.storageClass` (leave empty for default)
- Secrets under the `secrets` section (`JWT_SECRET`, email credentials, API keys, etc.) — replace all change_me values
- **For TLS/HTTPS**: Configure ingress TLS settings (see section below)
### 3. Deploy the Helm chart
```bash
@@ -41,3 +42,75 @@ kubectl get svc
```
Once all pods are `Running` and `Ready`, you can access Checkmate via the configured ingress hosts.
## Enabling TLS/HTTPS with cert-manager
If you have [cert-manager](https://cert-manager.io/) installed in your cluster, you can enable automatic TLS certificate provisioning using Let's Encrypt or other certificate issuers.
### Prerequisites
- cert-manager installed in your cluster
- A ClusterIssuer or Issuer configured (e.g., `letsencrypt-prod`)
### Configuration
Edit `values.yaml` to enable TLS (and update protocols to https):
```yaml
client:
protocol: https
ingress:
enabled: true
host: checkmate.example.com
className: nginx
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
tls:
enabled: true
secretName: checkmate-client-tls
server:
protocol: https
ingress:
enabled: true
host: checkmate.example.com
className: nginx
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
tls:
enabled: true
secretName: checkmate-server-tls
```
### Alternative: Using --set flags
You can also enable TLS during installation using Helm's `--set` flags:
```bash
helm install checkmate ./charts/helm/checkmate \
--set client.protocol=https \
--set server.protocol=https \
--set client.ingress.annotations."cert-manager\.io/cluster-issuer"="letsencrypt-prod" \
--set client.ingress.tls.enabled=true \
--set client.ingress.tls.secretName=checkmate-client-tls \
--set server.ingress.annotations."cert-manager\.io/cluster-issuer"="letsencrypt-prod" \
--set server.ingress.tls.enabled=true \
--set server.ingress.tls.secretName=checkmate-server-tls
```
### Verification
After deployment, cert-manager will automatically create the TLS secrets. You can verify the certificate status:
```bash
# Check certificates
kubectl get certificates
# Check certificate details
kubectl describe certificate checkmate-client-tls
kubectl describe certificate checkmate-server-tls
# Verify the secrets were created
kubectl get secrets | grep checkmate-tls
```
The ingress will automatically use these secrets to enable HTTPS access to your Checkmate instance.
@@ -1,12 +0,0 @@
{{- if eq .Values.client.ingress.host "change_me" }}
{{- fail "client.ingress.host must be overridden and not set to 'change_me'" }}
{{- end }}
{{- if eq .Values.server.ingress.host "change_me" }}
{{- fail "server.ingress.host must be overridden and not set to 'change_me'" }}
{{- end }}
{{- $protocol := .Values.server.protocol }}
{{- if not (or (eq $protocol "http") (eq $protocol "https")) }}
{{- fail "server.protocol must be either 'http' or 'https'" }}
{{- end }}
@@ -11,6 +11,12 @@ metadata:
{{- end }}
spec:
ingressClassName: {{ .Values.client.ingress.className }}
{{- if .Values.client.ingress.tls.enabled }}
tls:
- hosts:
- {{ .Values.client.ingress.host }}
secretName: {{ default (printf "%s-client-tls" .Release.Name) .Values.client.ingress.tls.secretName }}
{{- end }}
rules:
- host: {{ .Values.client.ingress.host }}
http:
@@ -0,0 +1,48 @@
{{- if eq .Values.client.ingress.host "change_me" }}
{{- fail "client.ingress.host must be overridden and not set to 'change_me'" }}
{{- end }}
{{- if eq .Values.server.ingress.host "change_me" }}
{{- fail "server.ingress.host must be overridden and not set to 'change_me'" }}
{{- end }}
{{- $serverProtocol := .Values.server.protocol }}
{{- if not (or (eq $serverProtocol "http") (eq $serverProtocol "https")) }}
{{- fail "server.protocol must be either 'http' or 'https'" }}
{{- end }}
{{- $clientProtocol := .Values.client.protocol }}
{{- if not (or (eq $clientProtocol "http") (eq $clientProtocol "https")) }}
{{- fail "client.protocol must be either 'http' or 'https'" }}
{{- end }}
{{/* Enforce protocol when TLS is enabled to avoid mixed-content */}}
{{- if and .Values.client.ingress.tls.enabled (ne $clientProtocol "https") }}
{{- fail "client.ingress.tls.enabled is true but client.protocol is not 'https'. Set client.protocol: https to avoid mixed content." }}
{{- end }}
{{- if and .Values.server.ingress.tls.enabled (ne $serverProtocol "https") }}
{{- fail "server.ingress.tls.enabled is true but server.protocol is not 'https'. Set server.protocol: https to ensure correct API base URL." }}
{{- end }}
{{/* If client runs on https, API must also be https to avoid mixed content */}}
{{- if and (eq $clientProtocol "https") (ne $serverProtocol "https") }}
{{- fail "client.protocol is 'https' but server.protocol is not. Set server.protocol: https to prevent browser mixed-content issues." }}
{{- end }}
{{/* Fail early if TLS enabled without cert-manager annotations (cluster-issuer or issuer) */}}
{{- $cAnn := .Values.client.ingress.annotations | default dict }}
{{- $sAnn := .Values.server.ingress.annotations | default dict }}
{{- $clientHasIssuer := or (hasKey $cAnn "cert-manager.io/cluster-issuer") (hasKey $cAnn "cert-manager.io/issuer") }}
{{- $serverHasIssuer := or (hasKey $sAnn "cert-manager.io/cluster-issuer") (hasKey $sAnn "cert-manager.io/issuer") }}
{{- if and .Values.client.ingress.tls.enabled (not $clientHasIssuer) }}
{{- fail "client.ingress.tls.enabled is true but no cert-manager issuer annotation found. Add 'cert-manager.io/cluster-issuer' or 'cert-manager.io/issuer'." }}
{{- end }}
{{- if and .Values.server.ingress.tls.enabled (not $serverHasIssuer) }}
{{- fail "server.ingress.tls.enabled is true but no cert-manager issuer annotation found. Add 'cert-manager.io/cluster-issuer' or 'cert-manager.io/issuer'." }}
{{- end }}
{{/* Secret name can be omitted; we default to <release>-client|server-tls in templates */}}
@@ -18,6 +18,12 @@ metadata:
#nginx.ingress.kubernetes.io/cors-allow-credentials: "true"*/}}
spec:
ingressClassName: {{ .Values.server.ingress.className }}
{{- if .Values.server.ingress.tls.enabled }}
tls:
- hosts:
- {{ .Values.server.ingress.host }}
secretName: {{ default (printf "%s-server-tls" .Release.Name) .Values.server.ingress.tls.secretName }}
{{- end }}
rules:
- host: {{ .Values.server.ingress.host }}
http:
+18
View File
@@ -7,6 +7,15 @@ client:
host: change_me
className: nginx
annotations: {}
# Example annotations for cert-manager:
# annotations:
# cert-manager.io/cluster-issuer: "letsencrypt-prod"
tls:
enabled: false
# secretName: {{ .Release.Name }}-client-tls # Optional; defaults to <release>-client-tls if omitted
# Note: when enabling TLS, also set client.protocol: https and add
# a cert-manager issuer annotation (e.g. cert-manager.io/cluster-issuer: "letsencrypt-prod").
# The secret will be automatically created by cert-manager when using the cert-manager.io/cluster-issuer annotation
server:
image: ghcr.io/bluewave-labs/checkmate-backend:v3.2.0
@@ -17,6 +26,15 @@ server:
host: change_me
className: nginx
annotations: {}
# Example annotations for cert-manager:
# annotations:
# cert-manager.io/cluster-issuer: "letsencrypt-prod"
tls:
enabled: false
# secretName: {{ .Release.Name }}-server-tls # Optional; defaults to <release>-server-tls if omitted
# Note: when enabling TLS, also set server.protocol: https and add
# a cert-manager issuer annotation (e.g. cert-manager.io/cluster-issuer: "letsencrypt-prod").
# The secret will be automatically created by cert-manager when using the cert-manager.io/cluster-issuer annotation
redis:
enabled: false
+2 -1
View File
@@ -28,4 +28,5 @@ dist-ssr
# Scripts
*.sh
!env.sh
!env.sh
.env.development
+649 -62
View File
@@ -12,7 +12,6 @@
"@emotion/styled": "^11.13.0",
"@hello-pangea/dnd": "^18.0.0",
"@hookform/resolvers": "5.2.2",
"@mui/icons-material": "6.4.11",
"@mui/lab": "6.0.0-dev.240424162023-9968b4889d",
"@mui/material": "6.4.11",
"@mui/x-charts": "^7.5.1",
@@ -25,6 +24,7 @@
"human-interval": "2.0.1",
"i18next": "25.4.2",
"joi": "17.13.3",
"lucide-react": "^0.562.0",
"mui-color-input": "^6.0.0",
"pretty-ms": "9.3.0",
"react": "18.3.1",
@@ -97,6 +97,7 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz",
"integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.27.1",
@@ -408,6 +409,7 @@
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5",
@@ -451,6 +453,7 @@
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz",
"integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5",
@@ -496,6 +499,262 @@
"integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
"license": "MIT"
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz",
"integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==",
"cpu": [
"ppc64"
],
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz",
"integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz",
"integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz",
"integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz",
"integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz",
"integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz",
"integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz",
"integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz",
"integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz",
"integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz",
"integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==",
"cpu": [
"ia32"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz",
"integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==",
"cpu": [
"loong64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz",
"integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==",
"cpu": [
"mips64el"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz",
"integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==",
"cpu": [
"ppc64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz",
"integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==",
"cpu": [
"riscv64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz",
"integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==",
"cpu": [
"s390x"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz",
@@ -512,6 +771,134 @@
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz",
"integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz",
"integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz",
"integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz",
"integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz",
"integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz",
"integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz",
"integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==",
"cpu": [
"ia32"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz",
"integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@eslint-community/eslint-utils": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
@@ -802,32 +1189,6 @@
"url": "https://opencollective.com/mui-org"
}
},
"node_modules/@mui/icons-material": {
"version": "6.4.11",
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.11.tgz",
"integrity": "sha512-+jjJGIrB1awNbMv4ZVPPdN/p7O1UKFZ+xqRvNIQ8B1KnlID5hPMPBLM6UUbRF4bu3UDCbu79rn9Nye5LGNzmeA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@mui/material": "^6.4.11",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/lab": {
"version": "6.0.0-dev.240424162023-9968b4889d",
"resolved": "https://registry.npmjs.org/@mui/lab/-/lab-6.0.0-dev.240424162023-9968b4889d.tgz",
@@ -989,6 +1350,7 @@
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.11.tgz",
"integrity": "sha512-k2D3FLJS+/qD0qnd6ZlAjGFvaaxe1Dl10NyvpeDzIebMuYdn8VqYe6XBgGueEAtnzSJM4V03VD9kb5Fi24dnTA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.26.0",
"@mui/core-downloads-tracker": "^6.4.11",
@@ -1153,7 +1515,6 @@
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.1.0.tgz",
"integrity": "sha512-4Kck4jxhqF6YxNwJdSae1WgDfXVg0lIH6JVJ7gtuFfuKcQCgomJxPvUEOySTFRPz1IZzwz5OAcToskRdffElDA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.27.1",
"@mui/utils": "^7.1.0",
@@ -1181,7 +1542,6 @@
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.1.0.tgz",
"integrity": "sha512-/OM3S8kSHHmWNOP+NH9xEtpYSG10upXeQ0wLZnfDgmgadTAk5F4MQfFLyZ5FCRJENB3eRzltMmaNl6UtDnPovw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.27.1",
"@mui/types": "^7.4.2",
@@ -1212,7 +1572,6 @@
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.1.0.tgz",
"integrity": "sha512-m0mJ0c6iRC+f9hMeRe0W7zZX1wme3oUX0+XTVHjPG7DJz6OdQ6K/ggEOq7ZdwilcpdsDUwwMfOmvO71qDkYd2w==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.27.1",
"@emotion/cache": "^11.13.5",
@@ -1288,7 +1647,6 @@
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.1.0.tgz",
"integrity": "sha512-/OM3S8kSHHmWNOP+NH9xEtpYSG10upXeQ0wLZnfDgmgadTAk5F4MQfFLyZ5FCRJENB3eRzltMmaNl6UtDnPovw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.27.1",
"@mui/types": "^7.4.2",
@@ -1666,9 +2024,9 @@
}
},
"node_modules/@remix-run/router": {
"version": "1.23.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz",
"integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==",
"version": "1.23.2",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz",
"integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==",
"license": "MIT",
"engines": {
"node": ">=14.0.0"
@@ -1696,6 +2054,123 @@
}
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.0.tgz",
"integrity": "sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.0.tgz",
"integrity": "sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.0.tgz",
"integrity": "sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.0.tgz",
"integrity": "sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.0.tgz",
"integrity": "sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.0.tgz",
"integrity": "sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.0.tgz",
"integrity": "sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.0.tgz",
"integrity": "sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.0.tgz",
"integrity": "sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.0.tgz",
@@ -1709,6 +2184,71 @@
"linux"
]
},
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.0.tgz",
"integrity": "sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==",
"cpu": [
"loong64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.0.tgz",
"integrity": "sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==",
"cpu": [
"ppc64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.0.tgz",
"integrity": "sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==",
"cpu": [
"riscv64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.0.tgz",
"integrity": "sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==",
"cpu": [
"riscv64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.0.tgz",
"integrity": "sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==",
"cpu": [
"s390x"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.0.tgz",
@@ -1735,6 +2275,45 @@
"linux"
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.0.tgz",
"integrity": "sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.0.tgz",
"integrity": "sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==",
"cpu": [
"ia32"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.0.tgz",
"integrity": "sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@sideway/address": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
@@ -2133,6 +2712,7 @@
"integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~7.12.0"
}
@@ -2154,6 +2734,7 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.22.tgz",
"integrity": "sha512-vUhG0YmQZ7kL/tmKLrD3g5zXbXXreZXB3pmROW8bg3CnLnpjkRVwUlLne7Ufa2r9yJ8+/6B73RzhAek5TBKh2Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
@@ -2217,6 +2798,7 @@
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2523,6 +3105,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001716",
"electron-to-chromium": "^1.5.149",
@@ -2935,7 +3518,8 @@
"version": "1.11.13",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/debug": {
"version": "4.4.1",
@@ -3342,6 +3926,7 @@
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
@@ -4149,6 +4734,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.27.6"
},
@@ -4685,9 +5271,9 @@
"license": "MIT"
},
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
@@ -4852,6 +5438,15 @@
"yallist": "^3.0.2"
}
},
"node_modules/lucide-react": {
"version": "0.562.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.562.0.tgz",
"integrity": "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==",
"license": "ISC",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -5416,6 +6011,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
@@ -5429,6 +6025,7 @@
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.63.0.tgz",
"integrity": "sha512-ZwueDMvUeucovM2VjkCf7zIHcs1aAlDimZu2Hvel5C5907gUzMpm4xCrQXtRzCvsBqFjonB4m3x4LzCFI1ZKWA==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18.0.0"
},
@@ -5515,12 +6112,12 @@
}
},
"node_modules/react-router": {
"version": "6.30.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz",
"integrity": "sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==",
"version": "6.30.3",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz",
"integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==",
"license": "MIT",
"dependencies": {
"@remix-run/router": "1.23.0"
"@remix-run/router": "1.23.2"
},
"engines": {
"node": ">=14.0.0"
@@ -5530,13 +6127,13 @@
}
},
"node_modules/react-router-dom": {
"version": "6.30.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.1.tgz",
"integrity": "sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==",
"version": "6.30.3",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz",
"integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==",
"license": "MIT",
"dependencies": {
"@remix-run/router": "1.23.0",
"react-router": "6.30.1"
"@remix-run/router": "1.23.2",
"react-router": "6.30.3"
},
"engines": {
"node": ">=14.0.0"
@@ -5632,7 +6229,8 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/redux-persist": {
"version": "6.0.0",
@@ -5770,6 +6368,7 @@
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.0.tgz",
"integrity": "sha512-HqMFpUbWlf/tvcxBFNKnJyzc7Lk+XO3FGc3pbNBLqEbOz0gPLRgcrlS3UF4MfUrVlstOaP/q0kM6GVvi+LrLRg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/estree": "1.0.7"
},
@@ -6405,6 +7004,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -6524,6 +7124,7 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz",
"integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==",
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
@@ -6744,20 +7345,6 @@
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"license": "ISC"
},
"node_modules/yaml": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz",
"integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==",
"license": "ISC",
"optional": true,
"peer": true,
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14.6"
}
},
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+1 -1
View File
@@ -17,7 +17,6 @@
"@emotion/styled": "^11.13.0",
"@hello-pangea/dnd": "^18.0.0",
"@hookform/resolvers": "5.2.2",
"@mui/icons-material": "6.4.11",
"@mui/lab": "6.0.0-dev.240424162023-9968b4889d",
"@mui/material": "6.4.11",
"@mui/x-charts": "^7.5.1",
@@ -30,6 +29,7 @@
"human-interval": "2.0.1",
"i18next": "25.4.2",
"joi": "17.13.3",
"lucide-react": "^0.562.0",
"mui-color-input": "^6.0.0",
"pretty-ms": "9.3.0",
"react": "18.3.1",
+1 -1
View File
@@ -16,7 +16,7 @@ const langMap = {
const LanguageSelector = () => {
const { i18n } = useTranslation();
const theme = useTheme();
const { language } = useSelector((state) => state.ui);
const { language = "en" } = useSelector((state) => state.ui);
const dispatch = useDispatch();
const handleChange = (event) => {
const newLang = event.target.value;
+12 -7
View File
@@ -2,7 +2,7 @@
import IconButton from "@mui/material/IconButton";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import Settings from "../../../assets/icons/settings-bold.svg?react";
import Icon from "../Icon";
import Dialog from "../Dialog/index.jsx";
// Utils
@@ -12,7 +12,7 @@ import { useNavigate } from "react-router-dom";
import { createToast } from "../../../Utils/toastUtils.jsx";
import PropTypes from "prop-types";
import { usePauseMonitor, useDeleteMonitor } from "../../../Hooks/v1/monitorHooks.js";
import { usePauseMonitor, useDeleteMonitor } from "../../../Hooks/monitorHooks.js";
const ActionsMenu = ({
monitor,
@@ -31,7 +31,8 @@ const ActionsMenu = ({
const handleRemove = async (event) => {
event.preventDefault();
event.stopPropagation();
let monitor = { _id: actions.id };
let monitor = { id: actions.id };
console.log(monitor);
await deleteMonitor({ monitor });
updateRowCallback();
};
@@ -39,7 +40,7 @@ const ActionsMenu = ({
const handlePause = async () => {
try {
setIsLoading(true);
await pauseMonitor({ monitorId: monitor._id });
await pauseMonitor({ monitorId: monitor.id });
pauseCallback();
} catch (error) {
createToast({ body: "Failed to pause monitor." });
@@ -72,7 +73,7 @@ const ActionsMenu = ({
aria-label="monitor actions"
onClick={(event) => {
event.stopPropagation();
openMenu(event, monitor._id, monitor.type === "ping" ? null : monitor.url);
openMenu(event, monitor.id, monitor.type === "ping" ? null : monitor.url);
}}
sx={{
"&:focus": {
@@ -83,7 +84,10 @@ const ActionsMenu = ({
},
}}
>
<Settings />
<Icon
name="Settings"
size={20}
/>
</IconButton>
<Menu
@@ -197,6 +201,7 @@ const ActionsMenu = ({
confirmationButtonLabel="Delete"
/* Do we need stop propagation? */
onConfirm={(e) => {
console.log(e);
e.stopPropagation();
handleRemove(e);
}}
@@ -210,7 +215,7 @@ const ActionsMenu = ({
ActionsMenu.propTypes = {
monitor: PropTypes.shape({
_id: PropTypes.string,
id: PropTypes.string,
url: PropTypes.string,
type: PropTypes.string,
isActive: PropTypes.bool,
+22 -11
View File
@@ -1,10 +1,7 @@
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { Box, Button, IconButton, Stack, Typography } from "@mui/material";
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
import ErrorOutlineOutlinedIcon from "@mui/icons-material/ErrorOutlineOutlined";
import WarningAmberOutlinedIcon from "@mui/icons-material/WarningAmberOutlined";
import CloseIcon from "@mui/icons-material/Close";
import Icon from "../Icon";
import "./index.css";
/**
@@ -13,9 +10,24 @@ import "./index.css";
*/
const icons = {
info: <InfoOutlinedIcon />,
error: <ErrorOutlineOutlinedIcon />,
warning: <WarningAmberOutlinedIcon />,
info: (
<Icon
name="Info"
size={24}
/>
),
error: (
<Icon
name="AlertCircle"
size={24}
/>
),
warning: (
<Icon
name="AlertTriangle"
size={24}
/>
),
};
/**
@@ -97,10 +109,9 @@ const Alert = ({ variant, title, body, isToast, hasIcon = true, onClick }) => {
},
}}
>
<CloseIcon
sx={{
fontSize: "20px",
}}
<Icon
name="X"
size={20}
/>
</IconButton>
)}
+10 -9
View File
@@ -1,27 +1,28 @@
import LeftArrow from "../../../assets/icons/left-arrow.svg?react";
import LeftArrowDouble from "../../../assets/icons/left-arrow-double.svg?react";
import LeftArrowLong from "../../../assets/icons/left-arrow-long.svg?react";
import Icon from "../Icon";
import PropTypes from "prop-types";
const ArrowLeft = ({ type, color = "#667085", ...props }) => {
if (type === "double") {
return (
<LeftArrowDouble
style={{ color }}
<Icon
name="ChevronsLeft"
color={color}
{...props}
/>
);
} else if (type === "long") {
return (
<LeftArrowLong
style={{ color }}
<Icon
name="ArrowLeft"
color={color}
{...props}
/>
);
} else {
return (
<LeftArrow
style={{ color }}
<Icon
name="ChevronLeft"
color={color}
{...props}
/>
);
@@ -1,19 +1,20 @@
import RightArrow from "../../../assets/icons/right-arrow.svg?react";
import RightArrowDouble from "../../../assets/icons/right-arrow-double.svg?react";
import Icon from "../Icon";
import PropTypes from "prop-types";
const ArrowRight = ({ type, color = "#667085", ...props }) => {
if (type === "double") {
return (
<RightArrowDouble
style={{ color }}
<Icon
name="ChevronsRight"
color={color}
{...props}
/>
);
} else {
return (
<RightArrow
style={{ color }}
<Icon
name="ChevronRight"
color={color}
{...props}
/>
);
@@ -103,7 +103,7 @@ const BarChart = ({ checks = [] }) => {
</>
}
placement="top"
key={`check-${check?._id}`}
key={`check-${check?.id}`}
slotProps={{
popper: {
className: "bar-tooltip",
@@ -145,7 +145,7 @@ const BarChart = ({ checks = [] }) => {
position="relative"
width="9px"
height="100%"
backgroundColor={theme.palette.primary.lowContrast} // CAIO_REVIEW
backgroundColor={theme.palette.primary.lowContrast}
sx={{
borderRadius: theme.spacing(1.5),
/*
@@ -154,7 +154,10 @@ export const InfrastructureTooltip = ({
yKey,
yIdx = -1,
yLabel,
yKey2,
yLabel2,
dotColor,
dotColor2,
dateRange,
formatter = getFormattedPercentage,
}) => {
@@ -216,7 +219,37 @@ export const InfrastructureTooltip = ({
</Typography>
</Stack>
</Box>
{/* Display original value */}
{yKey2 && yLabel2 && (
<Box mt={theme.spacing(1)}>
<Box
display="inline-block"
width={theme.spacing(4)}
height={theme.spacing(4)}
backgroundColor={dotColor2 || dotColor}
sx={{ borderRadius: "50%" }}
/>
<Stack
display="inline-flex"
direction="row"
justifyContent="space-between"
ml={theme.spacing(3)}
sx={{
"& span": {
color: theme.palette.primary.contrastTextTertiary,
fontSize: 11,
fontWeight: 500,
},
}}
>
<Typography
component="span"
sx={{ opacity: 0.8 }}
>
{`${yLabel2} ${formatter(payload[0].payload[yKey2])}`}
</Typography>
</Stack>
</Box>
)}
</Box>
);
}
@@ -234,7 +267,10 @@ InfrastructureTooltip.propTypes = {
yKey: PropTypes.string,
yIdx: PropTypes.number,
yLabel: PropTypes.string,
yKey2: PropTypes.string,
yLabel2: PropTypes.string,
dotColor: PropTypes.string,
dotColor2: PropTypes.string,
dateRange: PropTypes.string,
};
+10 -11
View File
@@ -1,7 +1,6 @@
import "./check.css";
import PropTypes from "prop-types";
import CheckGrey from "../../../assets/icons/check.svg?react";
import CheckOutlined from "../../../assets/icons/check-outlined.svg?react";
import Icon from "../Icon";
import { Box, Stack, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
@@ -35,16 +34,16 @@ const Check = ({ text, noHighlightText, variant = "info", outlined = false }) =>
alignItems="center"
>
{outlined ? (
<CheckOutlined alt="check" />
<Icon
name="CheckCircle"
size={20}
/>
) : (
<Box
lineHeight={0}
sx={{
"& svg > path": { fill: colors[variant] },
}}
>
<CheckGrey alt="form checks" />
</Box>
<Icon
name="Check"
size={16}
color={colors[variant]}
/>
)}
<Typography
component="span"
+11 -4
View File
@@ -8,8 +8,7 @@ import IconButton from "@mui/material/IconButton";
import Container from "@mui/material/Container";
import MenuItem from "@mui/material/MenuItem";
import Drawer from "@mui/material/Drawer";
import MenuIcon from "@mui/icons-material/Menu";
import CloseRoundedIcon from "@mui/icons-material/CloseRounded";
import Icon from "../Icon";
import ThemeSwitch from "../ThemeSwitch/index.jsx";
import { useTheme } from "@mui/material/styles";
import { useSelector } from "react-redux";
@@ -153,7 +152,11 @@ const AppAppBar = () => {
aria-label="Menu button"
onClick={toggleDrawer(true)}
>
<MenuIcon sx={{ color: theme.palette.text.primary }} />
<Icon
name="Menu"
size={24}
color={theme.palette.text.primary}
/>
</IconButton>
<Drawer
anchor="top"
@@ -176,7 +179,11 @@ const AppAppBar = () => {
}}
>
<IconButton onClick={toggleDrawer(false)}>
<CloseRoundedIcon sx={{ color: theme.palette.text.primary }} />
<Icon
name="X"
size={24}
color={theme.palette.text.primary}
/>
</IconButton>
</Box>
@@ -1,7 +1,7 @@
import { Checkbox, FormControl, ListItemText, MenuItem, Select } from "@mui/material";
import { useTheme } from "@emotion/react";
import PropTypes from "prop-types";
import AddCircleOutlineIcon from "@mui/icons-material/AddCircleOutline";
import Icon from "../Icon";
/**
* A reusable filter header component that displays a dropdown menu with selectable options.
@@ -29,9 +29,10 @@ const FilterHeader = ({ header, options, value, onChange, multiple = true }) =>
<Select
multiple={multiple}
IconComponent={(props) => (
<AddCircleOutlineIcon
<Icon
{...props}
sx={{ fontSize: "medium" }}
name="PlusCircle"
size={18}
/>
)}
displayEmpty
@@ -40,13 +40,13 @@ const GenericFallback = ({ children }) => {
<Box
component="img"
src={mode === "light" ? OutputAnimation : DarkmodeOutput}
Background="transparent"
alt="Loading animation"
sx={{
zIndex: 1,
border: "none",
borderRadius: theme.spacing(8),
width: "100%",
backgroundColor: "transparent",
}}
/>
<Box
+20 -3
View File
@@ -2,6 +2,7 @@ import { Stack, Typography } from "@mui/material";
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import Dot from "../Dot/index.jsx";
import { StatusLabel } from "@/Components/v1/Label/index.jsx";
/**
* Host component.
* This subcomponent receives a params object and displays the host details.
@@ -14,7 +15,7 @@ import Dot from "../Dot/index.jsx";
* @param {number} params.percentage - The percentage to display.
* @returns {React.ElementType} Returns a div element with the host details.
*/
const Host = ({ url, title, percentageColor, percentage, showURL }) => {
const Host = ({ url, title, percentageColor, percentage, showURL, status }) => {
const theme = useTheme();
return (
<Stack>
@@ -24,19 +25,34 @@ const Host = ({ url, title, percentageColor, percentage, showURL }) => {
alignItems="center"
gap={theme.spacing(4)}
>
{title}
<Typography
component="span"
sx={{
fontWeight: 500,
}}
>
{title}
</Typography>
{percentageColor && percentage && (
<>
<Dot />
<Typography
component="span"
sx={{
color: percentageColor,
fontWeight: 500,
color: percentageColor,
}}
>
{percentage}%
</Typography>
{status && (
<StatusLabel
status={status}
text={status}
customStyles={{ textTransform: "capitalize" }}
/>
)}
</>
)}
</Stack>
@@ -51,6 +67,7 @@ Host.propTypes = {
percentage: PropTypes.string,
url: PropTypes.string,
showURL: PropTypes.bool,
status: PropTypes.string,
};
export default Host;
@@ -2,7 +2,7 @@ import i18n from "../../../Utils/i18n.js";
import { useSelector } from "react-redux";
import { useEffect } from "react";
const I18nLoader = () => {
const language = useSelector((state) => state.ui.language);
const language = useSelector((state) => state.ui.language ?? "en");
useEffect(() => {
if (language && i18n.language !== language) {
+120
View File
@@ -0,0 +1,120 @@
/**
* Maps legacy icon names to Lucide icon names
* Use: import { iconMap } from './iconMap'; LucideIcons[iconMap['monitors']]
*/
export const iconMap = {
// Navigation/Sidebar icons
monitors: "Globe",
incidents: "AlertTriangle",
integrations: "Link",
"page-speed": "Gauge",
settings: "Settings",
notifications: "Bell",
maintenance: "Wrench",
"status-pages": "Wifi",
docs: "FileText",
discussions: "MessageCircle",
support: "HelpCircle",
changeLog: "Code",
logs: "Database",
// User icons
user: "User",
"user-two": "Users",
"user-edit": "UserPen",
groups: "Users",
lock: "Lock",
key: "Key",
logout: "LogOut",
// Action icons
edit: "Pencil",
"trash-bin": "Trash2",
search: "Search",
"close-icon": "X",
check: "Check",
"check-icon": "CheckCircle",
"check-outlined": "CheckCircle",
// Arrow icons
"left-arrow": "ChevronLeft",
"right-arrow": "ChevronRight",
"up-arrow": "ChevronUp",
"down-arrow": "ChevronDown",
"left-arrow-double": "ChevronsLeft",
"right-arrow-double": "ChevronsRight",
"left-arrow-long": "ArrowLeft",
"top-right-arrow": "ArrowUpRight",
"open-in-new-page": "ExternalLink",
"selector-vertical": "ChevronsUpDown",
// Status/Alert icons
"alert-icon": "Bell",
"warning-icon": "AlertCircle",
"history-icon": "History",
// Data/Chart icons
dashboard: "LayoutGrid",
"monitor-graph-line": "TrendingUp",
"response-time-icon": "TrendingUp",
"uptime-icon": "Activity",
"speedometer-icon": "Gauge",
"spedometer-icon": "Gauge",
"ruler-icon": "Ruler",
"performance-report": "Layers",
"average-response-icon": "BarChart3",
"distributed-uptime": "Network",
// Misc icons
calendar: "Calendar",
"calendar-check": "CalendarCheck",
"clock-snooze": "Clock",
mail: "Mail",
email: "Mail",
folder: "Folder",
"dots-vertical": "MoreVertical",
"pause-icon": "PauseCircle",
"resume-icon": "PlayCircle",
"checkbox-filled": "CheckSquare",
"checkbox-outline": "Square",
"radio-checked": "Circle",
"cpu-chip": "Cpu",
certificate: "Award",
"interval-check": "RefreshCw",
};
/**
* MUI icon to Lucide mapping
*/
export const muiToLucideMap = {
AddCircleOutline: "PlusCircle",
ArrowDownwardRounded: "ArrowDown",
ArrowUpwardRounded: "ArrowUp",
ArrowDropDown: "ChevronDown",
ArrowOutward: "ExternalLink",
CheckCircle: "CheckCircle2",
Clear: "X",
Close: "X",
CloseRounded: "X",
CloudUpload: "Upload",
Delete: "Trash2",
DeleteOutlineRounded: "Trash2",
Email: "Mail",
ErrorOutline: "AlertTriangle",
ErrorOutlineOutlined: "AlertTriangle",
Image: "ImageIcon",
InfoOutlined: "Info",
KeyboardArrowDown: "ChevronDown",
Menu: "Menu",
PauseCircleOutline: "PauseCircle",
PauseOutlined: "Pause",
PlayArrowOutlined: "Play",
PlayCircleOutlineRounded: "PlayCircle",
RadioButtonChecked: "Circle",
ReorderRounded: "GripVertical",
SettingsOutlined: "Settings",
Share: "Share2",
Visibility: "Eye",
VisibilityOff: "EyeOff",
WarningAmberOutlined: "AlertTriangle",
};
+196
View File
@@ -0,0 +1,196 @@
import { useTheme } from "@mui/material";
import PropTypes from "prop-types";
import {
Activity,
AlertCircle,
AlertTriangle,
ArrowDown,
ArrowLeft,
ArrowUp,
ArrowUpRight,
Bell,
Calendar,
Check,
CheckCircle,
CheckSquare,
ChevronDown,
ChevronLeft,
ChevronRight,
ChevronsLeft,
ChevronsRight,
ChevronsUpDown,
Circle,
CircleDot,
Clock,
Code,
Cpu,
Database,
ExternalLink,
Eye,
EyeOff,
FileText,
Gauge,
Globe,
GripVertical,
HelpCircle,
History,
Image as ImageIcon,
Info,
Key,
Layers,
Link,
Lock,
LogOut,
Mail,
Menu,
MessageCircle,
MoreVertical,
Pause,
PauseCircle,
Play,
PlayCircle,
PlusCircle,
RefreshCw,
Ruler,
Search,
Settings,
Square,
Trash2,
TrendingUp,
Upload,
User,
Users,
Wifi,
Wrench,
X,
} from "lucide-react";
/**
* Map of icon names to Lucide icon components.
* Only icons explicitly imported here will be included in the bundle (tree-shaking).
* To add a new icon: import it above and add it to this object.
*/
const iconComponents = {
Activity,
AlertCircle,
AlertTriangle,
ArrowDown,
ArrowLeft,
ArrowUp,
ArrowUpRight,
Bell,
Calendar,
Check,
CheckCircle,
CheckSquare,
ChevronDown,
ChevronLeft,
ChevronRight,
ChevronsLeft,
ChevronsRight,
ChevronsUpDown,
Circle,
CircleDot,
Clock,
Code,
Cpu,
Database,
ExternalLink,
Eye,
EyeOff,
FileText,
Gauge,
Globe,
GripVertical,
HelpCircle,
History,
Image: ImageIcon,
Info,
Key,
Layers,
Link,
Lock,
LogOut,
Mail,
Menu,
MessageCircle,
MoreVertical,
Pause,
PauseCircle,
Play,
PlayCircle,
PlusCircle,
RefreshCw,
Ruler,
Search,
Settings,
Square,
Trash2,
TrendingUp,
Upload,
User,
Users,
Wifi,
Wrench,
X,
};
/**
* Theme-aware icon component wrapping Lucide icons
*
* @param {string} name - Lucide icon name (e.g., "Bell", "Check", "ArrowLeft")
* @param {number} size - Icon size in pixels (default: 20)
* @param {string} color - Direct color value OR theme path like "primary.contrastText"
* @param {number} strokeWidth - Stroke width (default: 1.5)
* @param {string} fill - Fill color (default: "none")
*/
const Icon = ({ name, size = 20, color, strokeWidth = 1.5, fill = "none", ...props }) => {
const theme = useTheme();
// Resolve color from theme path or use direct value
const resolveColor = (colorValue) => {
if (!colorValue) {
return theme.palette.primary.contrastTextTertiary; // Default icon color
}
// If it's a theme path like "primary.contrastText"
if (typeof colorValue === "string" && colorValue.includes(".")) {
const parts = colorValue.split(".");
let resolved = theme.palette;
for (const part of parts) {
resolved = resolved?.[part];
}
return resolved || colorValue;
}
// Direct color value
return colorValue;
};
const LucideIcon = iconComponents[name];
if (!LucideIcon) {
console.warn(`Icon "${name}" not found in Icon component`);
return null;
}
return (
<LucideIcon
size={size}
color={resolveColor(color)}
strokeWidth={strokeWidth}
fill={fill}
{...props}
/>
);
};
Icon.propTypes = {
name: PropTypes.string.isRequired,
size: PropTypes.number,
color: PropTypes.string,
strokeWidth: PropTypes.number,
fill: PropTypes.string,
};
export default Icon;
@@ -1,8 +1,7 @@
import PropTypes from "prop-types";
import { FormControlLabel, Checkbox as MuiCheckbox } from "@mui/material";
import { useTheme } from "@emotion/react";
import CheckboxOutline from "../../../../assets/icons/checkbox-outline.svg?react";
import CheckboxFilled from "../../../../assets/icons/checkbox-filled.svg?react";
import Icon from "../../Icon";
/**
* Checkbox Component
@@ -65,8 +64,8 @@ const Checkbox = ({
name={name}
value={value}
onChange={onChange}
icon={<CheckboxOutline />}
checkedIcon={<CheckboxFilled />}
icon={<Icon name="Square" />}
checkedIcon={<Icon name="CheckSquare" />}
inputProps={{
"aria-label": "controlled checkbox",
id: id,
@@ -1,11 +1,10 @@
// Components
import { Box, Stack, Typography } from "@mui/material";
import CloudUploadIcon from "@mui/icons-material/CloudUpload";
import Icon from "../../Icon";
import Image from "../../Image/index.jsx";
import TextField from "@mui/material/TextField";
import IconButton from "@mui/material/IconButton";
import ProgressUpload from "../../ProgressBars/index.jsx";
import ImageIcon from "@mui/icons-material/Image";
// Utils
import PropTypes from "prop-types";
@@ -176,7 +175,10 @@ const ImageUpload = ({
boxShadow: theme.shape.boxShadow,
}}
>
<CloudUploadIcon />
<Icon
name="Upload"
size={24}
/>
</IconButton>
<Typography
component="h2"
@@ -203,7 +205,12 @@ const ImageUpload = ({
</Box>
{(localError || progress.isLoading || progress.value !== 0) && (
<ProgressUpload
icon={<ImageIcon />}
icon={
<Icon
name="Image"
size={20}
/>
}
label={file?.name || "Upload failed"}
size={file?.size}
progress={progress.value}
@@ -1,7 +1,7 @@
import PropTypes from "prop-types";
import { FormControlLabel, Radio as MUIRadio, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import RadioChecked from "../../../../assets/icons/radio-checked.svg?react";
import Icon from "../../Icon";
import "./index.css";
/**
@@ -47,7 +47,12 @@ const Radio = ({
<MUIRadio
id={id}
size={size}
checkedIcon={<RadioChecked />}
checkedIcon={
<Icon
name="CircleDot"
size={16}
/>
}
sx={{
color: "transparent",
width: 16,
@@ -8,7 +8,7 @@ import {
Checkbox,
} from "@mui/material";
import { useTheme } from "@emotion/react";
import SearchIcon from "../../../../assets/icons/search.svg?react";
import Icon from "../../Icon";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import FieldWrapper from "../FieldWrapper/index.jsx";
@@ -31,20 +31,13 @@ import FieldWrapper from "../FieldWrapper/index.jsx";
const SearchAdornment = () => {
const theme = useTheme();
return (
<Box
height={16}
sx={{
"& svg": {
width: 16,
height: 16,
"& path": {
stroke: theme.palette.primary.contrastTextTertiary,
strokeWidth: 1.2,
},
},
}}
>
<SearchIcon />
<Box height={16}>
<Icon
name="Search"
size={16}
color="primary.contrastTextTertiary"
strokeWidth={1.2}
/>
</Box>
);
};
@@ -85,7 +78,12 @@ const Search = ({
const enhancedOptions = React.useMemo(() => {
return multiple && isAdorned
? [
{ [filteredBy]: t("selectAll"), isSelectAll: true, _id: "select_all" },
{
[filteredBy]: t("selectAll"),
isSelectAll: true,
_id: "select_all",
id: "select_all",
},
...options,
]
: options;
@@ -93,7 +91,7 @@ const Search = ({
const isOptionSelected = (option) => {
if (!multiple && !isAdorned) return false;
if (Array.isArray(value)) {
return value.some((item) => item._id === option._id);
return value.some((item) => (item._id ?? item.id) === (option._id ?? option.id));
}
return false;
};
@@ -145,7 +143,9 @@ const Search = ({
disableClearable
options={enhancedOptions}
getOptionLabel={(option) => option[filteredBy]}
isOptionEqualToValue={(option, value) => option._id === value._id} // Compare by unique identifier
isOptionEqualToValue={(option, value) =>
(option._id ?? option.id) === (value?._id ?? value?.id)
} // Compare by unique identifier
renderInput={(params) => (
<FieldWrapper
label={label}
@@ -206,7 +206,7 @@ const Search = ({
return filtered;
}}
getOptionKey={(option) => {
return option._id;
return option._id ?? option.id;
}}
renderOption={(props, option) => {
const { key, ...optionProps } = props;
@@ -1,7 +1,7 @@
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { MenuItem, Select as MuiSelect, Stack, Typography } from "@mui/material";
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
import Icon from "../../Icon";
import FieldWrapper from "../FieldWrapper/index.jsx";
import "./index.css";
@@ -62,6 +62,7 @@ const Select = ({
fieldWrapperSx = {},
}) => {
const theme = useTheme();
const getItemValue = (item) => item?._id ?? item?.id;
const itemStyles = {
fontSize: "var(--env-var-font-size-medium)",
color: theme.palette.primary.contrastTextTertiary,
@@ -97,7 +98,13 @@ const Select = ({
error={error}
name={name}
inputProps={{ id: id }}
IconComponent={KeyboardArrowDownIcon}
IconComponent={(props) => (
<Icon
name="ChevronDown"
size={20}
{...props}
/>
)}
MenuProps={{ disableScrollLock: true }}
sx={{
fontSize: 13,
@@ -123,7 +130,7 @@ const Select = ({
...sx,
}}
renderValue={(selected) => {
const selectedItem = items.find((item) => item._id === selected);
const selectedItem = items.find((item) => getItemValue(item) === selected);
const displayName = selectedItem ? selectedItem.name : placeholder;
return (
<Typography
@@ -152,17 +159,25 @@ const Select = ({
{placeholder}
</MenuItem>
)}
{items.map((item) => (
<MenuItem
value={item._id}
key={`${id}-${item._id}`}
sx={{
...itemStyles,
}}
>
{item.name}
</MenuItem>
))}
{items
.map((item) => {
const itemValue = getItemValue(item);
if (itemValue === undefined || itemValue === null) {
return null;
}
return (
<MenuItem
value={itemValue}
key={`${id}-${itemValue}`}
sx={{
...itemStyles,
}}
>
{item.name}
</MenuItem>
);
})
.filter(Boolean)}
</MuiSelect>
</FieldWrapper>
);
@@ -179,8 +194,8 @@ Select.propTypes = {
.isRequired,
items: PropTypes.arrayOf(
PropTypes.shape({
_id: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool])
.isRequired,
_id: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]),
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]),
name: PropTypes.string.isRequired,
})
@@ -1,10 +1,7 @@
import { Stack, Typography, InputAdornment, IconButton } from "@mui/material";
import { useTheme } from "@mui/material/styles";
import PropTypes from "prop-types";
import VisibilityOff from "@mui/icons-material/VisibilityOff";
import Visibility from "@mui/icons-material/Visibility";
import ReorderRoundedIcon from "@mui/icons-material/ReorderRounded";
import DeleteIcon from "../../../../../assets/icons/trash-bin.svg?react";
import Icon from "../../../Icon";
export const HttpAdornment = ({ https }) => {
const theme = useTheme();
@@ -56,7 +53,17 @@ export const PasswordEndAdornment = ({ fieldType, setFieldType }) => {
},
}}
>
{fieldType === "password" ? <VisibilityOff /> : <Visibility />}
{fieldType === "password" ? (
<Icon
name="EyeOff"
size={20}
/>
) : (
<Icon
name="Eye"
size={20}
/>
)}
</IconButton>
</InputAdornment>
);
+2 -4
View File
@@ -147,11 +147,9 @@ const StatusLabel = ({ status, text, customStyles }) => {
}}
>
<Box
width={7}
height={7}
bgcolor={theme.palette[themeColor].lowContrast}
borderRadius="50%"
marginRight="5px"
borderRadius="0%"
marginRight="1px"
/>
</BaseLabel>
);
@@ -3,6 +3,8 @@
min-height: 100vh;
margin: 0 auto;
padding: 0;
overflow-x: hidden;
width: 100%;
}
/* TODO go for this approach for responsiveness. The aside needs to be taken care of */
@@ -30,4 +32,6 @@
max-width: 1400px;
margin: 0 auto;
flex: 1;
min-width: 0;
overflow-x: hidden;
}
@@ -1,7 +1,7 @@
import * as React from "react";
import Button from "@mui/material/Button";
import ButtonGroup from "@mui/material/ButtonGroup";
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
import Icon from "../Icon";
import ClickAwayListener from "@mui/material/ClickAwayListener";
import Grow from "@mui/material/Grow";
import Paper from "@mui/material/Paper";
@@ -11,7 +11,7 @@ import MenuList from "@mui/material/MenuList";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { createToast } from "../../../Utils/toastUtils.jsx";
import { useExportMonitors } from "../../../Hooks/v1/monitorHooks.js";
import { useExportMonitors } from "../../../Hooks/monitorHooks.js";
const MonitorActions = ({ isLoading }) => {
const [open, setOpen] = React.useState(false);
@@ -70,7 +70,10 @@ const MonitorActions = ({ isLoading }) => {
aria-haspopup="menu"
onClick={handleToggle}
>
<ArrowDropDownIcon />
<Icon
name="ChevronDown"
size={20}
/>
</Button>
</ButtonGroup>
<Popper
@@ -3,19 +3,16 @@ import Status from "./status.jsx";
import Skeleton from "./skeleton.jsx";
import Button from "@mui/material/Button";
import { Tooltip } from "@mui/material";
import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined";
import PauseOutlinedIcon from "@mui/icons-material/PauseOutlined";
import PlayArrowOutlinedIcon from "@mui/icons-material/PlayArrowOutlined";
import EmailIcon from "@mui/icons-material/Email";
import Icon from "../Icon";
// Utils
import PropTypes from "prop-types";
import { useNavigate } from "react-router-dom";
import { useTheme } from "@mui/material/styles";
import { usePauseMonitor } from "../../../Hooks/v1/monitorHooks.js";
import { useSendTestEmail } from "../../../Hooks/v1/useSendTestEmail.js";
import { usePauseMonitor } from "../../../Hooks/monitorHooks.js";
import { useSendTestEmail } from "../../../Hooks/useSendTestEmail.js";
import { useTranslation } from "react-i18next";
import { useTestAllNotifications } from "../../../Hooks/v1/useNotifications.js";
import { useTestAllNotifications } from "../../../Hooks/useNotifications.js";
/**
* MonitorDetailsControlHeader component displays the control header for monitor details.
* It includes status display, pause/resume button, and a configure button for admins.
@@ -66,7 +63,7 @@ const MonitorDetailsControlHeader = ({
gap={theme.spacing(2)}
>
<Tooltip
key={monitor?._id}
key={monitor?.id}
placement="bottom"
title={tooltipTitle}
>
@@ -75,10 +72,15 @@ const MonitorDetailsControlHeader = ({
variant="contained"
color="secondary"
loading={isSending}
startIcon={<EmailIcon />}
startIcon={
<Icon
name="Mail"
size={18}
/>
}
disabled={isTestNotificationsDisabled}
onClick={() => {
testAllNotifications({ monitorId: monitor?._id });
testAllNotifications({ monitorId: monitor?.id });
}}
sx={{
whiteSpace: "nowrap",
@@ -92,7 +94,7 @@ const MonitorDetailsControlHeader = ({
variant="contained"
color="secondary"
onClick={(e) => {
navigate(`/incidents/${monitor?._id}`);
navigate(`/incidents/${monitor?.id}`);
}}
>
{t("menu.incidents")}
@@ -103,11 +105,21 @@ const MonitorDetailsControlHeader = ({
color="secondary"
loading={isPausing}
startIcon={
monitor?.isActive ? <PauseOutlinedIcon /> : <PlayArrowOutlinedIcon />
monitor?.isActive ? (
<Icon
name="Pause"
size={18}
/>
) : (
<Icon
name="Play"
size={18}
/>
)
}
onClick={() => {
pauseMonitor({
monitorId: monitor?._id,
monitorId: monitor?.id,
triggerUpdate,
});
}}
@@ -119,8 +131,13 @@ const MonitorDetailsControlHeader = ({
<Button
variant="contained"
color="secondary"
startIcon={<SettingsOutlinedIcon />}
onClick={() => navigate(`/${path}/configure/${monitor._id}`)}
startIcon={
<Icon
name="Settings"
size={18}
/>
}
onClick={() => navigate(`/${path}/configure/${monitor.id}`)}
>
Configure
</Button>
@@ -7,7 +7,7 @@ import Dot from "../Dot/index.jsx";
import { formatDurationRounded } from "../../../Utils/timeUtils.js";
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { useMonitorUtils } from "../../../Hooks/v1/useMonitorUtils.js";
import { useMonitorUtils } from "../../../Hooks/useMonitorUtils.js";
import { formatMonitorUrl } from "../../../Utils/utils.js";
/**
* Status component displays the status information of a monitor.
@@ -2,7 +2,7 @@
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import Divider from "@mui/material/Divider";
import DeleteOutlineRoundedIcon from "@mui/icons-material/DeleteOutlineRounded";
import Icon from "../Icon";
import Search from "../Inputs/Search/index.jsx";
// Utils
@@ -102,11 +102,13 @@ const NotificationConfig = ({
>
{notification.notificationName}
</Typography>
<DeleteOutlineRoundedIcon
<Icon
name="Trash2"
size={20}
onClick={() => {
handleDelete(notification._id);
}}
sx={{ cursor: "pointer" }}
style={{ cursor: "pointer" }}
/>
{index < selectedNotifications.length - 1 && <Divider />}
</Stack>
@@ -3,7 +3,7 @@ import Fallback from "../Fallback/index.jsx";
import { Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
import { useIsAdmin } from "../../../Hooks/v1/useIsAdmin.js";
import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
import PropTypes from "prop-types";
const PageStateWrapper = ({
networkError,
@@ -1,8 +1,7 @@
import { useTheme } from "@emotion/react";
import PropTypes from "prop-types";
import { Box, IconButton, LinearProgress, Stack, Typography } from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
import ErrorOutlineOutlinedIcon from "@mui/icons-material/ErrorOutlineOutlined";
import Icon from "../Icon";
import "./index.css";
/**
@@ -51,7 +50,10 @@ const ProgressUpload = ({ icon, label, size, progress = 0, onClick, error }) =>
alignItems={error ? "center" : "flex-start"}
>
{error ? (
<ErrorOutlineOutlinedIcon />
<Icon
name="AlertTriangle"
size={20}
/>
) : icon ? (
<Box
sx={{
@@ -127,10 +129,9 @@ const ProgressUpload = ({ icon, label, size, progress = 0, onClick, error }) =>
}
}
>
<CloseIcon
sx={{
fontSize: "20px",
}}
<Icon
name="X"
size={20}
/>
</IconButton>
</Stack>
@@ -1,15 +1,22 @@
import React from "react";
import { Step, Stepper, StepLabel, Typography } from "@mui/material";
import RadioButtonCheckedIcon from "@mui/icons-material/RadioButtonChecked";
import CheckCircle from "@mui/icons-material/CheckCircle";
import Icon from "../Icon";
import PropTypes from "prop-types";
const CustomStepIcon = (props) => {
const { completed, active } = props;
return completed ? (
<CheckCircle color="accent" />
<Icon
name="CheckCircle2"
size={24}
color="accent.main"
/>
) : (
<RadioButtonCheckedIcon color={active ? "accent" : "disabled"} />
<Icon
name="Circle"
size={24}
color={active ? "accent.main" : "text.disabled"}
/>
);
};
@@ -1,5 +1,5 @@
import html2canvas from "html2canvas";
import ShareIcon from "@mui/icons-material/Share";
import Icon from "../Icon";
import { Button } from "@mui/material";
import { useTheme } from "@emotion/react";
@@ -55,7 +55,13 @@ const ShareComponent = ({ elementToCapture, fileName = "screenshot" }) => {
return (
<Button
variant="outlined"
startIcon={<ShareIcon sx={{ color: theme.palette.success.main }} />}
startIcon={
<Icon
name="Share2"
size={18}
color="success.main"
/>
}
color="success"
onClick={captureAndShare}
>
@@ -7,8 +7,7 @@ import Avatar from "../../Avatar/index.jsx";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import Divider from "@mui/material/Divider";
import DotsVertical from "../../../../assets/icons/dots-vertical.svg?react";
import LogoutSvg from "../../../../assets/icons/logout.svg?react";
import Icon from "../../Icon";
import { useTheme } from "@emotion/react";
import { useSelector } from "react-redux";
@@ -81,6 +80,10 @@ const AuthFooter = ({ collapsed, accountMenuItems }) => {
gap: theme.spacing(2),
borderRadius: theme.shape.borderRadius,
pl: theme.spacing(4),
color: theme.palette.primary.contrastTextTertiary,
"& svg": {
stroke: theme.palette.primary.contrastTextTertiary,
},
}}
>
{item.icon}
@@ -160,19 +163,14 @@ const AuthFooter = ({ collapsed, accountMenuItems }) => {
ml: "50px",
"&:focus": { outline: "none" },
alignSelf: "center",
"& svg": {
width: "22px",
height: "22px",
},
"& svg path": {
/* Vertical three dots */
stroke: theme.palette.primary.contrastTextTertiary,
},
}}
onClick={(event) => openPopup(event)}
>
<DotsVertical />
<Icon
name="MoreVertical"
size={22}
color="primary.contrastTextTertiary"
/>
</IconButton>
</Tooltip>
</Stack>
@@ -256,12 +254,13 @@ const AuthFooter = ({ collapsed, accountMenuItems }) => {
gap: theme.spacing(4),
borderRadius: theme.shape.borderRadius,
pl: theme.spacing(4),
"& svg path": {
stroke: theme.palette.primary.contrastTextTertiary,
},
}}
>
<LogoutSvg />
<Icon
name="LogOut"
size={20}
color="primary.contrastTextTertiary"
/>
{t("menu.logOut", "Log out")}
</MenuItem>
</Menu>
@@ -52,12 +52,11 @@ const NavItem = ({ item, collapsed, selected, onClick }) => {
<ListItemIcon
sx={{
minWidth: 0,
color: iconStroke,
"& svg": {
height: 20,
width: 20,
opacity: 0.81,
},
"& svg path": {
stroke: iconStroke,
},
}}
+19 -36
View File
@@ -8,22 +8,7 @@ import NavItem from "./components/navItem.jsx";
import AuthFooter from "./components/authFooter.jsx";
import StarPrompt from "../StarPrompt/index.jsx";
import LockSvg from "../../../assets/icons/lock.svg?react";
import UserSvg from "../../../assets/icons/user.svg?react";
import TeamSvg from "../../../assets/icons/user-two.svg?react";
import Support from "../../../assets/icons/support.svg?react";
import Maintenance from "../../../assets/icons/maintenance.svg?react";
import Monitors from "../../../assets/icons/monitors.svg?react";
import Incidents from "../../../assets/icons/incidents.svg?react";
import Integrations from "../../../assets/icons/integrations.svg?react";
import PageSpeed from "../../../assets/icons/page-speed.svg?react";
import Settings from "../../../assets/icons/settings.svg?react";
import ChangeLog from "../../../assets/icons/changeLog.svg?react";
import Docs from "../../../assets/icons/docs.svg?react";
import StatusPages from "../../../assets/icons/status-pages.svg?react";
import Discussions from "../../../assets/icons/discussions.svg?react";
import Notifications from "../../../assets/icons/notifications.svg?react";
import Logs from "../../../assets/icons/logs.svg?react";
import Icon from "../Icon";
// Utils
import { useTheme } from "@mui/material/styles";
@@ -39,43 +24,41 @@ const URL_MAP = {
};
const getMenu = (t) => [
{ name: t("menu.uptime"), path: "uptime", icon: <Monitors /> },
{ name: t("menu.pagespeed"), path: "pagespeed", icon: <PageSpeed /> },
{ name: t("menu.infrastructure"), path: "infrastructure", icon: <Integrations /> },
{ name: t("menu.uptime"), path: "uptime", icon: <Icon name="Globe" /> },
{ name: t("menu.pagespeed"), path: "pagespeed", icon: <Icon name="Gauge" /> },
{ name: t("menu.infrastructure"), path: "infrastructure", icon: <Icon name="Link" /> },
{
name: t("menu.notifications"),
path: "notifications",
icon: <Notifications />,
icon: <Icon name="Bell" />,
},
{ name: t("menu.incidents"), path: "incidents", icon: <Incidents /> },
{ name: t("menu.statusPages"), path: "status", icon: <StatusPages /> },
{ name: t("menu.maintenance"), path: "maintenance", icon: <Maintenance /> },
{ name: t("menu.logs"), path: "logs", icon: <Logs /> },
{ name: t("menu.checks"), path: "checks", icon: <Icon name="FileText" /> },
{ name: t("menu.incidents"), path: "incidents", icon: <Icon name="AlertTriangle" /> },
{ name: t("menu.statusPages"), path: "status", icon: <Icon name="Wifi" /> },
{ name: t("menu.maintenance"), path: "maintenance", icon: <Icon name="Wrench" /> },
{ name: t("menu.logs"), path: "logs", icon: <Icon name="Database" /> },
{
name: t("menu.settings"),
icon: <Settings />,
icon: <Icon name="Settings" />,
path: "settings",
},
];
const getOtherMenuItems = (t) => [
{ name: t("menu.support"), path: "support", icon: <Support /> },
{ name: t("menu.support"), path: "support", icon: <Icon name="HelpCircle" /> },
{
name: t("menu.discussions"),
path: "discussions",
icon: <Discussions />,
icon: <Icon name="MessageCircle" />,
},
{ name: t("menu.docs"), path: "docs", icon: <Docs /> },
{ name: t("menu.changelog"), path: "changelog", icon: <ChangeLog /> },
{ name: t("menu.docs"), path: "docs", icon: <Icon name="FileText" /> },
{ name: t("menu.changelog"), path: "changelog", icon: <Icon name="Code" /> },
];
const getAccountMenuItems = (t) => [
{ name: t("menu.profile"), path: "account/profile", icon: <UserSvg /> },
{ name: t("menu.password"), path: "account/password", icon: <LockSvg /> },
{ name: t("menu.team"), path: "account/team", icon: <TeamSvg /> },
{ name: t("menu.profile"), path: "account/profile", icon: <Icon name="User" /> },
{ name: t("menu.password"), path: "account/password", icon: <Icon name="Lock" /> },
{ name: t("menu.team"), path: "account/team", icon: <Icon name="Users" /> },
];
const Sidebar = () => {
@@ -83,7 +66,7 @@ const Sidebar = () => {
const { t } = useTranslation();
const navigate = useNavigate();
// Redux state
const collapsed = useSelector((state) => state.ui.sidebar.collapsed);
const collapsed = useSelector((state) => state.ui.sidebar?.collapsed ?? false);
const menu = getMenu(t);
const otherMenuItems = getOtherMenuItems(t);
@@ -1,6 +1,6 @@
import React from "react";
import { Typography, IconButton, Stack, Box } from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
import Icon from "../Icon";
import { useTheme } from "@emotion/react";
import { useSelector, useDispatch } from "react-redux";
import { useTranslation } from "react-i18next";
@@ -67,7 +67,10 @@ const StarPrompt = ({ repoUrl = "https://github.com/bluewave-labs/checkmate" })
},
}}
>
<CloseIcon sx={{ fontSize: "1.25rem" }} />
<Icon
name="X"
size={20}
/>
</IconButton>
</Stack>
+1 -1
View File
@@ -2,7 +2,7 @@ import { Stack, Typography } from "@mui/material";
import Image from "../Image/index.jsx";
import { useTheme } from "@mui/material/styles";
import PropTypes from "prop-types";
import { useMonitorUtils } from "../../../Hooks/v1/useMonitorUtils.js";
import { useMonitorUtils } from "../../../Hooks/useMonitorUtils.js";
/**
* StatBox Component
@@ -2,7 +2,7 @@ import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { Stack, TablePagination, Typography } from "@mui/material";
import { TablePaginationActions } from "./Actions/index.jsx";
import SelectorVertical from "../../../../assets/icons/selector-vertical.svg?react";
import Icon from "../../Icon";
Pagination.propTypes = {
paginationLabel: PropTypes.string, // Label for the pagination.
@@ -100,7 +100,12 @@ function Pagination({
},
},
inputProps: { id: "pagination-dropdown" },
IconComponent: SelectorVertical,
IconComponent: () => (
<Icon
name="ChevronsUpDown"
size={16}
/>
),
sx: {
ml: theme.spacing(4),
mr: theme.spacing(12),
+27 -9
View File
@@ -3,19 +3,31 @@ import IconButton from "@mui/material/IconButton";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import ToastBody from "./body.jsx";
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
import ErrorOutlineOutlinedIcon from "@mui/icons-material/ErrorOutlineOutlined";
import WarningAmberOutlinedIcon from "@mui/icons-material/WarningAmberOutlined";
import CloseIcon from "@mui/icons-material/Close";
import Icon from "../Icon";
// Utils
import { useTheme } from "@emotion/react";
import PropTypes from "prop-types";
const icons = {
info: <InfoOutlinedIcon />,
error: <ErrorOutlineOutlinedIcon />,
warning: <WarningAmberOutlinedIcon />,
info: (
<Icon
name="Info"
size={24}
/>
),
error: (
<Icon
name="AlertCircle"
size={24}
/>
),
warning: (
<Icon
name="AlertTriangle"
size={24}
/>
),
};
const Toast = ({ variant, title, body, onClick, hasDismiss, hasIcon }) => {
@@ -50,7 +62,10 @@ const Toast = ({ variant, title, body, onClick, hasDismiss, hasIcon }) => {
)}
{title && (
<IconButton onClick={onClick}>
<CloseIcon />
<Icon
name="X"
size={20}
/>
</IconButton>
)}
</Stack>
@@ -63,7 +78,10 @@ const Toast = ({ variant, title, body, onClick, hasDismiss, hasIcon }) => {
<ToastBody body={body} />
{!title && (
<IconButton onClick={onClick}>
<CloseIcon />
<Icon
name="X"
size={20}
/>
</IconButton>
)}
</Stack>
@@ -1,53 +0,0 @@
import React, { useState } from "react";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import IconButton from "@mui/material/IconButton";
import Settings from "@/assets/icons/settings-bold.svg?react";
export type ActionMenuItem = {
id: number | string;
label: React.ReactNode;
action: Function;
closeMenu?: boolean;
};
export const ActionsMenu = ({ items }: { items: ActionMenuItem[] }) => {
const [anchorEl, setAnchorEl] = useState<null | any>(null);
const open = Boolean(anchorEl);
const handleClick = (e: React.MouseEvent<any>) => {
e.stopPropagation();
setAnchorEl(e.currentTarget);
};
const handleClose = (e: React.MouseEvent<any>) => {
e.stopPropagation();
setAnchorEl(null);
};
return (
<div>
<IconButton onClick={handleClick}>
<Settings />
</IconButton>
<Menu
anchorEl={anchorEl}
open={open}
onClose={handleClose}
>
{items.map((item) => (
<MenuItem
key={item.id}
onClick={(e: React.MouseEvent<HTMLLIElement>) => {
e.stopPropagation();
if (item.closeMenu) handleClose(e);
item.action();
}}
>
{item.label}
</MenuItem>
))}
</Menu>
</div>
);
};
@@ -1,36 +0,0 @@
import LeftArrow from "@/assets/icons/left-arrow.svg?react";
import LeftArrowDouble from "@/assets/icons/left-arrow-double.svg?react";
import LeftArrowLong from "@/assets/icons/left-arrow-long.svg?react";
export const ArrowLeft = ({
type,
color = "#667085",
...props
}: {
type?: string;
color?: string | undefined;
[key: string]: any;
}) => {
if (type === "double") {
return (
<LeftArrowDouble
style={{ color }}
{...props}
/>
);
} else if (type === "long") {
return (
<LeftArrowLong
style={{ color }}
{...props}
/>
);
} else {
return (
<LeftArrow
style={{ color }}
{...props}
/>
);
}
};
@@ -1,28 +0,0 @@
import RightArrow from "@/assets/icons/right-arrow.svg?react";
import RightArrowDouble from "@/assets/icons/right-arrow-double.svg?react";
export const ArrowRight = ({
type,
color = "#667085",
...props
}: {
type?: string;
color?: string | undefined;
[key: string]: any;
}) => {
if (type === "double") {
return (
<RightArrowDouble
style={{ color }}
{...props}
/>
);
} else {
return (
<RightArrow
style={{ color }}
{...props}
/>
);
}
};
@@ -1,48 +0,0 @@
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import { HeaderAuth } from "@/Components/v2/Auth";
import Logo from "@/assets/icons/checkmate-icon.svg?react";
import type { StackProps } from "@mui/material/Stack";
import { useTheme } from "@mui/material/styles";
import { Typography } from "@mui/material";
interface AuthBasePageProps extends StackProps {
title?: string;
subtitle?: string;
children: React.ReactNode;
}
export const AuthBasePage: React.FC<AuthBasePageProps> = ({
children,
title,
subtitle,
...props
}) => {
const theme = useTheme();
return (
<Stack
gap={theme.spacing(10)}
minHeight="100vh"
{...props}
>
<HeaderAuth />
<Stack
alignItems="center"
margin="auto"
width="100%"
gap={theme.spacing(4)}
>
<Box
width={{ xs: 60, sm: 70, md: 80 }}
mb={theme.spacing(10)}
>
<Logo style={{ width: "100%", height: "100%" }} />
</Box>
<Typography variant="h1">{title}</Typography>
<Typography variant="h1">{subtitle}</Typography>
{children}
</Stack>
</Stack>
);
};
@@ -1,26 +0,0 @@
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import Logo from "@/assets/icons/checkmate-icon.svg?react";
import { useTheme } from "@mui/material/styles";
import { useTranslation } from "react-i18next";
import { LanguageSelector, ThemeSwitch } from "@/Components/v2/Inputs";
export const HeaderAuth = () => {
const theme = useTheme();
const { t } = useTranslation();
return (
<Stack
width={"100%"}
direction="row"
alignItems="center"
justifyContent="flex-end"
py={theme.spacing(4)}
px={theme.spacing(12)}
gap={theme.spacing(4)}
>
<LanguageSelector />
<ThemeSwitch color="red" />
</Stack>
);
};
-2
View File
@@ -1,2 +0,0 @@
export { HeaderAuth } from "./HeaderAuth";
export { AuthBasePage } from "./AuthBasePage";
@@ -1,23 +0,0 @@
import Box from "@mui/material/Box";
import { useTheme } from "@mui/material/styles";
import type { SxProps } from "@mui/material/styles";
type BaseBoxProps = React.PropsWithChildren<{ sx?: SxProps }>;
export const BaseBox: React.FC<BaseBoxProps> = ({ children, sx }) => {
const theme = useTheme();
return (
<Box
sx={{
backgroundColor: theme.palette.primary.main,
border: 1,
borderStyle: "solid",
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
...sx,
}}
>
{children}
</Box>
);
};
@@ -1,80 +0,0 @@
import Stack from "@mui/material/Stack";
import { ErrorFallback, EmptyFallback } from "./Fallback";
import type { StackProps } from "@mui/material/Stack";
import { useTheme } from "@mui/material/styles";
import { useTranslation } from "react-i18next";
interface BasePageProps extends StackProps {
children: React.ReactNode;
}
export const BasePage: React.FC<BasePageProps> = ({
children,
...props
}: {
children: React.ReactNode;
}) => {
const theme = useTheme();
return (
<Stack
spacing={theme.spacing(10)}
{...props}
>
{children}
</Stack>
);
};
interface BasePageWithStatesProps extends StackProps {
loading: boolean;
error: any;
items: any[];
page: string;
actionLink?: string;
children: React.ReactNode;
}
const isEmpty = (items: any[]) => {
if (!items) return true;
if (Array.isArray(items) && items.length === 0) return true;
return false;
};
export const BasePageWithStates: React.FC<BasePageWithStatesProps> = ({
loading,
error,
items,
page,
actionLink,
children,
...props
}: BasePageWithStatesProps) => {
const { t } = useTranslation();
if (loading) {
return null;
}
if (error) {
return (
<ErrorFallback
title="Something went wrong..."
subtitle="Please try again later"
/>
);
}
if (isEmpty(items)) {
return (
<EmptyFallback
page={page}
title={t(`${page}Monitor.fallback.title`)}
bullets={t(`${page}Monitor.fallback.checks`, { returnObjects: true })}
actionButtonText={t(`${page}Monitor.fallback.actionButton`)}
actionLink={actionLink || ""}
/>
);
}
return <BasePage {...props}>{children}</BasePage>;
};
@@ -1,45 +0,0 @@
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import CheckOutlined from "@/assets/icons/check-outlined.svg?react";
import { useTheme } from "@mui/material/styles";
export const BulletPointCheck = ({
text,
variant = "info",
}: {
text: string;
noHighlightText?: string;
variant?: "success" | "error" | "info";
}) => {
const theme = useTheme();
const colors: Record<string, string | undefined> = {
success: theme.palette.success.main,
error: theme.palette.error.main,
info: theme.palette.primary.contrastTextSecondary,
};
return (
<Stack
direction="row"
className="check"
gap={theme.spacing(6)}
alignItems="center"
>
<CheckOutlined />
<Typography
component="span"
color={
variant === "info"
? theme.palette.primary.contrastTextSecondary
: colors[variant]
}
fontWeight={450}
sx={{
opacity: 0.9,
}}
>
{text}
</Typography>
</Stack>
);
};
@@ -1,23 +0,0 @@
export const Dot = ({
color = "gray",
size = "4px",
style,
}: {
color?: string;
size?: string;
style?: React.CSSProperties;
}) => {
return (
<span
style={{
content: '""',
width: size,
height: size,
borderRadius: "50%",
backgroundColor: color,
opacity: 0.8,
...style,
}}
/>
);
};
@@ -1,158 +0,0 @@
import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import OutputAnimation from "@/assets/Animations/output.gif";
import DarkmodeOutput from "@/assets/Animations/darkmodeOutput.gif";
import Typography from "@mui/material/Typography";
import { BulletPointCheck } from "@/Components/v2/DesignElements";
import { Button } from "@/Components/v2/Inputs";
import { useNavigate } from "react-router";
import { useMediaQuery } from "@mui/material";
import { useTheme } from "@mui/material/styles";
import { useSelector } from "react-redux";
import type { BoxProps } from "@mui/material";
interface BaseFallbackProps extends BoxProps {
children: React.ReactNode;
}
export const BaseFallback: React.FC<BaseFallbackProps> = ({ children, ...props }) => {
const theme = useTheme();
const mode = useSelector((state: any) => state.ui.mode);
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
return (
<Box
margin={isSmall ? "inherit" : "auto"}
marginTop={isSmall ? "33%" : "auto"}
width={{
sm: "90%",
md: "70%",
lg: "50%",
xl: "40%",
}}
padding={theme.spacing(16)}
bgcolor={theme.palette.primary.main}
position="relative"
border={1}
borderColor={theme.palette.primary.lowContrast}
borderRadius={theme.shape.borderRadius}
overflow="hidden"
sx={{
borderStyle: "dashed",
}}
{...props}
>
<Stack
alignItems="center"
gap={theme.spacing(20)}
sx={{
width: "fit-content",
margin: "auto",
marginTop: "100px",
}}
>
<Box
component="img"
src={mode === "light" ? OutputAnimation : DarkmodeOutput}
bgcolor="transparent"
alt="Loading animation"
width="100%"
sx={{
zIndex: 1,
border: "none",
borderRadius: theme.spacing(8),
}}
/>
<Stack
gap={theme.spacing(4)}
alignItems="center"
maxWidth={"300px"}
zIndex={1}
>
{children}
</Stack>
</Stack>
</Box>
);
};
export const ErrorFallback = ({
title,
subtitle,
}: {
title: string;
subtitle: string;
}) => {
const theme = useTheme();
return (
<BaseFallback>
<Typography
variant="h1"
marginY={theme.spacing(4)}
color={theme.palette.primary.contrastTextTertiary}
>
{title}
</Typography>
<Typography>{subtitle}</Typography>
</BaseFallback>
);
};
export const EmptyFallback = ({
page,
title,
bullets,
actionButtonText,
actionLink,
}: {
page: string;
title: string;
bullets: any;
actionButtonText: string;
actionLink: string;
}) => {
const theme = useTheme();
const navigate = useNavigate();
return (
<BaseFallback>
<Stack
gap={theme.spacing(10)}
zIndex={1}
alignItems="center"
>
<Typography
component="h1"
color={theme.palette.primary.contrastText}
>
{title}
</Typography>
<Stack
sx={{
flexWrap: "wrap",
gap: theme.spacing(2),
maxWidth: { xs: "90%", md: "80%", lg: "75%" },
}}
>
{bullets?.map((bullet: string, index: number) => (
<BulletPointCheck
text={bullet}
key={`${(page + "Monitors").trim().split(" ")[0]}-${index}`}
/>
))}
</Stack>
<Stack>
<Button
variant="contained"
color="accent"
onClick={() => navigate(actionLink)}
>
{actionButtonText}
</Button>
</Stack>
</Stack>
</BaseFallback>
);
};
@@ -1,44 +0,0 @@
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import { useTheme } from "@mui/material/styles";
export const PulseDot = ({ color }: { color: string }) => {
const theme = useTheme();
return (
<Stack
width="26px"
height="24px"
alignItems="center"
justifyContent="center"
>
<Box
minWidth="18px"
minHeight="18px"
sx={{
position: "relative",
backgroundColor: color,
borderRadius: "50%",
"&::before": {
content: `""`,
position: "absolute",
width: "100%",
height: "100%",
backgroundColor: "inherit",
borderRadius: "50%",
animation: "ripple 1.8s ease-out infinite",
},
"&::after": {
content: `""`,
position: "absolute",
width: "7px",
height: "7px",
borderRadius: "50%",
backgroundColor: theme.palette.accent.contrastText,
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
},
}}
/>
</Stack>
);
};
@@ -1,68 +0,0 @@
import { Fragment } from "react";
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import { useTheme } from "@mui/material/styles";
import { useMediaQuery } from "@mui/material";
export const SplitBox = ({
left,
right,
}: {
left: React.ReactNode;
right: React.ReactNode;
}) => {
const theme = useTheme();
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
return (
<Stack
direction={isSmall ? "column" : "row"}
bgcolor={theme.palette.primary.main}
border={1}
borderColor={theme.palette.primary.lowContrast}
borderRadius={theme.spacing(2)}
>
<Box
padding={theme.spacing(15)}
borderRight={isSmall ? 0 : 1}
borderBottom={isSmall ? 1 : 0}
borderColor={theme.palette.primary.lowContrast}
flex={0.7}
>
{left}
</Box>
<Box
flex={1}
padding={theme.spacing(15)}
>
{right}
</Box>
</Stack>
);
};
export const ConfigBox = ({
title,
subtitle,
rightContent,
}: {
title: string;
subtitle: string;
rightContent: React.ReactNode;
}) => {
return (
<SplitBox
left={
<Fragment>
<Typography
component="h2"
variant="h2"
>
{title}
</Typography>
<Typography component="p">{subtitle}</Typography>
</Fragment>
}
right={rightContent}
/>
);
};
@@ -1,57 +0,0 @@
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { useTheme } from "@mui/material/styles";
import { useMediaQuery } from "@mui/material";
import type { PaletteKey } from "@/Utils/Theme/v2/theme";
import { BaseBox } from "@/Components/v2/DesignElements";
type GradientBox = React.PropsWithChildren<{ palette?: PaletteKey }>;
export const GradientBox: React.FC<GradientBox> = ({ children, palette }) => {
const theme = useTheme();
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
const bg = palette
? `linear-gradient(to bottom right, ${theme.palette[palette].main} 30%, ${theme.palette[palette].lowContrast} 70%)`
: `linear-gradient(340deg, ${theme.palette.tertiary.main} 10%, ${theme.palette.primary.main} 45%)`;
return (
<BaseBox
sx={{
padding: `${theme.spacing(4)} ${theme.spacing(8)}`,
width: isSmall
? `calc(50% - (1 * ${theme.spacing(8)} / 2))`
: `calc(25% - (3 * ${theme.spacing(8)} / 4))`,
background: bg,
}}
>
{children}
</BaseBox>
);
};
type StatBoxProps = React.PropsWithChildren<{
title: string;
subtitle: string;
palette?: PaletteKey;
}>;
export const StatBox: React.FC<StatBoxProps> = ({
title,
subtitle,
palette,
children,
}) => {
const theme = useTheme();
const textColor = palette ? theme.palette[palette].contrastText : "inherit";
return (
<GradientBox palette={palette}>
<Stack>
<Typography color={textColor}>{title}</Typography>
<Typography color={textColor}>{subtitle}</Typography>
{children}
</Stack>
</GradientBox>
);
};
@@ -1,100 +0,0 @@
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import Box from "@mui/material/Box";
import { BaseBox } from "@/Components/v2/DesignElements";
import Background from "@/assets/Images/background-grid.svg?react";
import { useTranslation } from "react-i18next";
import { useTheme } from "@mui/material/styles";
type StatusBoxProps = React.PropsWithChildren<{}>;
export const BGBox: React.FC<StatusBoxProps> = ({ children }) => {
const theme = useTheme();
return (
<BaseBox
sx={{
overflow: "hidden",
position: "relative",
flex: 1,
padding: theme.spacing(8),
}}
>
<Box
position="absolute"
top="-10%"
left="5%"
>
<Background />
</Box>
{children}
</BaseBox>
);
};
const StatusBox = ({
label,
n,
color,
}: {
label: string;
n: number;
color: string | undefined;
}) => {
const theme = useTheme();
return (
<BGBox>
<Stack spacing={theme.spacing(8)}>
<Typography
variant={"h2"}
textTransform="uppercase"
color={theme.palette.primary.contrastTextTertiary}
>
{label}
</Typography>
<Typography
variant="h1"
color={color}
>
{n}
</Typography>
</Stack>
</BGBox>
);
};
export const UpStatusBox = ({ n }: { n: number }) => {
const theme = useTheme();
const { t } = useTranslation();
return (
<StatusBox
label={t("monitorStatus.up")}
n={n}
color={theme.palette.success.lowContrast}
/>
);
};
export const DownStatusBox = ({ n }: { n: number }) => {
const theme = useTheme();
const { t } = useTranslation();
return (
<StatusBox
label={t("monitorStatus.down")}
n={n}
color={theme.palette.error.lowContrast}
/>
);
};
export const PausedStatusBox = ({ n }: { n: number }) => {
const theme = useTheme();
const { t } = useTranslation();
return (
<StatusBox
label={t("monitorStatus.paused")}
n={n}
color={theme.palette.warning.lowContrast}
/>
);
};
@@ -1,41 +0,0 @@
import Box from "@mui/material/Box";
import { BaseBox } from "@/Components/v2/DesignElements";
import type { MonitorStatus } from "@/Types/Monitor";
import { getStatusPalette } from "@/Utils/v2/MonitorUtils";
import { useTheme } from "@mui/material/styles";
export const StatusLabel = ({
status,
isActive,
}: {
status: MonitorStatus;
isActive?: boolean;
}) => {
const theme = useTheme();
const palette = getStatusPalette(status);
const transformedText = status.charAt(0).toUpperCase() + status.slice(1).toLowerCase();
return (
<BaseBox
sx={{
display: "inline-flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
padding: theme.spacing(3, 5),
color: theme.palette[palette].main,
borderColor: theme.palette[palette].lowContrast,
}}
>
<Box
width={7}
height={7}
bgcolor={theme.palette[palette].lowContrast}
borderRadius="50%"
marginRight="5px"
/>
{isActive === false ? "Paused" : transformedText}
</BaseBox>
);
};
@@ -1,215 +0,0 @@
import Paper from "@mui/material/Paper";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import IconButton from "@mui/material/IconButton";
import LastPageIcon from "@mui/icons-material/LastPage";
import FirstPageIcon from "@mui/icons-material/FirstPage";
import KeyboardArrowLeft from "@mui/icons-material/KeyboardArrowLeft";
import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight";
import Box from "@mui/material/Box";
import TablePagination from "@mui/material/TablePagination";
import type { TablePaginationProps } from "@mui/material/TablePagination";
import { useTheme } from "@mui/material/styles";
import { useMediaQuery } from "@mui/material";
export type Header<T> = {
id: number | string;
content: React.ReactNode;
onClick?: (event: React.MouseEvent<HTMLTableCellElement | null>, row: T) => void;
render: (row: T) => React.ReactNode;
};
type DataTableProps<T extends { id?: string | number; _id?: string | number }> = {
headers: Header<T>[];
data: T[];
onRowClick?: (row: T) => void;
};
export function DataTable<
T extends {
id?: string | number;
_id?: string | number;
onRowClick?: (row: T) => void;
},
>({ headers, data, onRowClick }: DataTableProps<T>) {
const theme = useTheme();
if (data.length === 0 || headers.length === 0) return <div>No data</div>;
return (
<TableContainer component={Paper}>
<Table
stickyHeader
sx={{
"&.MuiTable-root :is(.MuiTableHead-root, .MuiTableBody-root) :is(th, td)": {
paddingLeft: theme.spacing(8),
},
"& :is(th)": {
backgroundColor: theme.palette.secondary.main,
color: theme.palette.secondary.contrastText,
fontWeight: 600,
},
"& :is(td)": {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastTextSecondary,
},
"& .MuiTableBody-root .MuiTableRow-root:last-child .MuiTableCell-root": {
borderBottom: "none",
},
}}
>
<TableHead>
<TableRow>
{headers.map((header, idx) => {
return (
<TableCell
align={idx === 0 ? "left" : "center"}
key={header.id}
>
{header.content}
</TableCell>
);
})}
</TableRow>
</TableHead>
<TableBody>
{data.map((row) => {
const key = row.id || row._id || Math.random();
return (
<TableRow
key={key}
sx={{ cursor: onRowClick ? "pointer" : "default" }}
onClick={() => (onRowClick ? onRowClick(row) : null)}
>
{headers.map((header, index) => {
return (
<TableCell
align={index === 0 ? "left" : "center"}
key={header.id}
onClick={
header.onClick ? (e) => header.onClick!(e, row) : undefined
}
>
{header.render(row)}
</TableCell>
);
})}
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
);
}
interface TablePaginationActionsProps {
count: number;
page: number;
rowsPerPage: number;
onPageChange: (event: React.MouseEvent<HTMLButtonElement>, newPage: number) => void;
}
function TablePaginationActions(props: TablePaginationActionsProps) {
const theme = useTheme();
const { count, page, rowsPerPage, onPageChange } = props;
const handleFirstPageButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
onPageChange(event, 0);
};
const handleBackButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
onPageChange(event, page - 1);
};
const handleNextButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
onPageChange(event, page + 1);
};
const handleLastPageButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1));
};
return (
<Box
sx={{ flexShrink: 0, ml: 2.5 }}
className="table-pagination-actions"
>
<IconButton
onClick={handleFirstPageButtonClick}
disabled={page === 0}
aria-label="first page"
>
{theme.direction === "rtl" ? <LastPageIcon /> : <FirstPageIcon />}
</IconButton>
<IconButton
onClick={handleBackButtonClick}
disabled={page === 0}
aria-label="previous page"
>
{theme.direction === "rtl" ? <KeyboardArrowRight /> : <KeyboardArrowLeft />}
</IconButton>
<IconButton
onClick={handleNextButtonClick}
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
aria-label="next page"
>
{theme.direction === "rtl" ? <KeyboardArrowLeft /> : <KeyboardArrowRight />}
</IconButton>
<IconButton
onClick={handleLastPageButtonClick}
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
aria-label="last page"
>
{theme.direction === "rtl" ? <FirstPageIcon /> : <LastPageIcon />}
</IconButton>
</Box>
);
}
export const Pagination: React.FC<TablePaginationProps> = ({ ...props }) => {
const isSmall = useMediaQuery((theme: any) => theme.breakpoints.down("sm"));
const theme = useTheme();
return (
<TablePagination
ActionsComponent={TablePaginationActions}
rowsPerPageOptions={[5, 10, 25]}
{...props}
sx={{
"& .MuiTablePagination-toolbar": {
display: isSmall ? "grid" : "flex",
},
"& .MuiTablePagination-selectLabel": {
gridColumn: "1",
gridRow: "1",
justifySelf: "center",
},
"& .MuiTablePagination-select": {
gridColumn: "2",
gridRow: "1",
justifySelf: "center",
},
"& .MuiTablePagination-displayedRows": {
gridColumn: "2",
gridRow: "2",
justifySelf: "center ",
},
"& .table-pagination-actions": {
gridColumn: "1",
gridRow: "2",
justifySelf: "center",
},
"& .MuiSelect-select": {
border: 1,
borderColor: theme.palette.primary.lowContrast,
borderRadius: theme.shape.borderRadius,
},
}}
/>
);
};
@@ -1,9 +0,0 @@
export { SplitBox as HorizontalSplitBox, ConfigBox } from "./SplitBox";
export { BasePage, BasePageWithStates } from "./BasePage";
export { BGBox, UpStatusBox, DownStatusBox, PausedStatusBox } from "./StatusBox";
export { DataTable as Table, Pagination } from "./Table";
export { GradientBox, StatBox } from "./StatBox";
export { BaseBox } from "./BaseBox";
export { StatusLabel } from "./StatusLabel";
export { BaseFallback, ErrorFallback, EmptyFallback } from "./Fallback";
export { BulletPointCheck } from "./BulletPointCheck";
@@ -1,48 +0,0 @@
import Autocomplete from "@mui/material/Autocomplete";
import type { AutocompleteProps } from "@mui/material/Autocomplete";
import { TextInput } from "@/Components/v2/Inputs/TextInput";
import { CheckboxInput } from "@/Components/v2/Inputs/Checkbox";
import ListItem from "@mui/material/ListItem";
import { useTheme } from "@mui/material/styles";
type AutoCompleteInputProps = Omit<
AutocompleteProps<any, boolean, boolean, boolean>,
"renderInput"
> & {
renderInput?: AutocompleteProps<any, boolean, boolean, boolean>["renderInput"];
};
export const AutoCompleteInput: React.FC<AutoCompleteInputProps> = ({ ...props }) => {
const theme = useTheme();
return (
<Autocomplete
{...props}
disableCloseOnSelect
renderInput={(params) => (
<TextInput
{...params}
placeholder="Type to search"
/>
)}
getOptionKey={(option) => option._id}
renderTags={() => null}
renderOption={(props, option, { selected }) => {
const { key, ...optionProps } = props;
return (
<ListItem
key={key}
{...optionProps}
>
<CheckboxInput checked={selected} />
{option.name}
</ListItem>
);
}}
sx={{
"&.MuiAutocomplete-root .MuiAutocomplete-input": {
padding: `0 ${theme.spacing(5)}`,
},
}}
/>
);
};
@@ -1,11 +0,0 @@
import Button from "@mui/material/Button";
import type { ButtonProps } from "@mui/material/Button";
export const ButtonInput: React.FC<ButtonProps> = ({ sx, ...props }) => {
return (
<Button
{...props}
sx={{ textTransform: "none", height: 34, fontWeight: 400, borderRadius: 2, ...sx }}
/>
);
};
@@ -1,13 +0,0 @@
import ButtonGroup from "@mui/material/ButtonGroup";
import type { ButtonGroupProps } from "@mui/material/ButtonGroup";
export const ButtonGroupInput: React.FC<ButtonGroupProps> = ({
orientation,
...props
}) => {
return (
<ButtonGroup
orientation={orientation}
{...props}
/>
);
};
@@ -1,23 +0,0 @@
import Checkbox from "@mui/material/Checkbox";
import type { CheckboxProps } from "@mui/material/Checkbox";
import CheckboxOutline from "@/assets/icons/checkbox-outline.svg?react";
import CheckboxFilled from "@/assets/icons/checkbox-filled.svg?react";
import { useTheme } from "@mui/material/styles";
type CheckboxInputProps = CheckboxProps & {
label?: string;
};
export const CheckboxInput: React.FC<CheckboxInputProps> = ({ label, ...props }) => {
const theme = useTheme();
return (
<Checkbox
{...props}
icon={<CheckboxOutline />}
checkedIcon={<CheckboxFilled />}
sx={{
"&:hover": { backgroundColor: "transparent" },
"& svg": { width: theme.spacing(8), height: theme.spacing(8) },
}}
/>
);
};
@@ -1,64 +0,0 @@
import "flag-icons/css/flag-icons.min.css";
import { Select } from "@/Components/v2/Inputs";
import MenuItem from "@mui/material/MenuItem";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { useTranslation } from "react-i18next";
import { useTheme } from "@mui/material/styles";
import { useDispatch } from "react-redux";
import { useSelector } from "react-redux";
import { setLanguage } from "@/Features/UI/uiSlice";
import type { SelectChangeEvent } from "@mui/material/Select";
export const LanguageSelector = () => {
const { i18n } = useTranslation();
const theme = useTheme();
const dispatch = useDispatch();
const language = useSelector((state: any) => state.ui.language);
const languages = Object.keys(i18n.options.resources || {});
const languageMap: Record<string, string> = {
cs: "cz",
ja: "jp",
uk: "ua",
vi: "vn",
};
const handleChange = (event: SelectChangeEvent<unknown>) => {
const newLang = event.target.value;
dispatch(setLanguage(newLang));
};
const languagesForDisplay = languages.map((l) => {
let formattedLanguage = l === "en" ? "gb" : l;
formattedLanguage = formattedLanguage.includes("-")
? formattedLanguage.split("-")[1].toLowerCase()
: formattedLanguage;
formattedLanguage = languageMap[formattedLanguage] || formattedLanguage;
const flag = formattedLanguage ? `fi fi-${formattedLanguage}` : null;
return (
<MenuItem
key={l}
value={l}
>
<Stack
direction="row"
gap={theme.spacing(4)}
>
{flag && <span className={flag} />}
<Typography textTransform={"uppercase"}>{l}</Typography>
</Stack>
</MenuItem>
);
});
return (
<Select
value={language}
onChange={handleChange}
>
{languagesForDisplay}
</Select>
);
};
@@ -1,66 +0,0 @@
import Radio from "@mui/material/Radio";
import type { RadioProps } from "@mui/material/Radio";
import { useTheme } from "@mui/material/styles";
import RadioChecked from "@/assets/icons/radio-checked.svg?react";
import FormControlLabel from "@mui/material/FormControlLabel";
import Typography from "@mui/material/Typography";
interface RadioInputProps extends RadioProps {}
export const RadioInput: React.FC<RadioInputProps> = ({ ...props }) => {
const theme = useTheme();
return (
<Radio
{...props}
checkedIcon={<RadioChecked />}
sx={{
color: "transparent",
boxShadow: `inset 0 0 0 1px ${theme.palette.secondary.main}`,
"&:not(.Mui-checked)": {
boxShadow: `inset 0 0 0 1px ${theme.palette.primary.contrastText}70`, // Use theme text color for the outline
},
mt: theme.spacing(0.5),
padding: 0,
"& .MuiSvgIcon-root": {
fontSize: 16,
},
}}
/>
);
};
export const RadioWithDescription: React.FC<
RadioInputProps & { label: string; description: string }
> = ({ label, description, ...props }) => {
const theme = useTheme();
return (
<FormControlLabel
control={<RadioInput {...props} />}
label={
<>
<Typography component="p">{label}</Typography>
<Typography
component="h6"
color={theme.palette.primary.contrastTextSecondary}
>
{description}
</Typography>
</>
}
sx={{
alignItems: "flex-start",
p: theme.spacing(2.5),
m: theme.spacing(-2.5),
borderRadius: theme.shape.borderRadius,
"&:hover": {
backgroundColor: theme.palette.tertiary.main,
},
"& .MuiButtonBase-root": {
p: 0,
mr: theme.spacing(6),
},
}}
/>
);
};
@@ -1,88 +0,0 @@
import { Typography, Select } from "@mui/material";
import MenuItem from "@mui/material/MenuItem";
import type { SelectProps } from "@mui/material/Select";
import { useTheme } from "@mui/material/styles";
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
export const SelectInput: React.FC<SelectProps> = ({ ...props }) => {
const theme = useTheme();
return (
<Select
{...props}
sx={{
height: "34px",
"& .MuiOutlinedInput-notchedOutline": {
borderRadius: theme.shape.borderRadius,
borderColor: theme.palette.primary.lowContrast,
},
"&:hover .MuiOutlinedInput-notchedOutline": {
borderColor: theme.palette.primary.lowContrast,
},
}}
/>
);
};
type ItemTypes = string | number;
interface SelectItem {
_id: ItemTypes;
name: string;
}
export type CustomSelectProps = SelectProps & {
items: SelectItem[];
placeholder?: string;
isHidden?: boolean;
hasError?: boolean;
};
export const SelectFromItems: React.FC<CustomSelectProps> = ({
items,
placeholder,
isHidden = false,
hasError = false,
...props
}) => {
return (
<SelectInput
error={hasError}
IconComponent={KeyboardArrowDownIcon}
displayEmpty
MenuProps={{ disableScrollLock: true }}
renderValue={(selected) => {
if (!selected) {
return (
<Typography
noWrap
color="text.secondary"
>
{placeholder ?? ""}
</Typography>
);
}
const selectedItem = items.find((item) => item._id === selected);
const displayName = selectedItem ? selectedItem.name : placeholder;
return (
<Typography
noWrap
title={displayName}
>
{displayName}
</Typography>
);
}}
{...props}
>
{items.map((item) => (
<MenuItem
key={item._id}
value={item._id}
>
{item.name}
</MenuItem>
))}
</SelectInput>
);
};
SelectInput.displayName = "SelectInput";
SelectFromItems.displayName = "SelectFromItems";
@@ -1,22 +0,0 @@
import { forwardRef } from "react";
import TextField from "@mui/material/TextField";
import type { TextFieldProps } from "@mui/material";
import { typographyLevels } from "@/Utils/Theme/v2/palette";
export const TextInput = forwardRef<HTMLInputElement, TextFieldProps>(
function TextInput(props, ref) {
return (
<TextField
{...props}
inputRef={ref}
sx={{
"& .MuiOutlinedInput-root": {
height: 34,
fontSize: typographyLevels.base,
},
}}
/>
);
}
);
TextInput.displayName = "TextInput";
@@ -1,37 +0,0 @@
import Typography from "@mui/material/Typography";
import Stack from "@mui/material/Stack";
import Link from "@mui/material/Link";
import { Link as RouterLink } from "react-router-dom";
import { useTheme } from "@mui/material/styles";
export const TextLink = ({
text,
linkText,
href,
target = "_self",
}: {
text: string;
linkText: string;
href: string;
target?: string;
}) => {
const theme = useTheme();
return (
<Stack
direction="row"
gap={theme.spacing(4)}
>
<Typography>{text}</Typography>
<Link
color="accent"
to={href}
component={RouterLink}
target={target}
>
{linkText}
</Link>
</Stack>
);
};
@@ -1,135 +0,0 @@
import { useTheme } from "@mui/material/styles";
import { useDispatch, useSelector } from "react-redux";
import { setMode } from "@/Features/UI/uiSlice.js";
import { useTranslation } from "react-i18next";
import IconButton from "@mui/material/IconButton";
const SunAndMoonIcon = () => {
const theme = useTheme();
return (
<svg
className="sun-and-moon"
aria-hidden="true"
width="24"
height="24"
viewBox="0 0 24 24"
>
<mask
className="moon"
id="moon-mask"
>
<rect
x="0"
y="0"
width="100%"
height="100%"
fill="#fff"
/>
<circle
cx="24"
cy="10"
r="6"
fill="#000"
/>
</mask>
<circle
className="sun"
cx="12"
cy="12"
r="6"
fill={theme.palette.primary.contrastTextSecondary}
mask="url(#moon-mask)"
/>
<g
className="sun-beams"
stroke={theme.palette.primary.contrastTextSecondary}
>
<line
x1="12"
y1="1"
x2="12"
y2="3"
/>
<line
x1="12"
y1="21"
x2="12"
y2="23"
/>
<line
x1="4.22"
y1="4.22"
x2="5.64"
y2="5.64"
/>
<line
x1="18.36"
y1="18.36"
x2="19.78"
y2="19.78"
/>
<line
x1="1"
y1="12"
x2="3"
y2="12"
/>
<line
x1="21"
y1="12"
x2="23"
y2="12"
/>
<line
x1="4.22"
y1="19.78"
x2="5.64"
y2="18.36"
/>
<line
x1="18.36"
y1="5.64"
x2="19.78"
y2="4.22"
/>
</g>
</svg>
);
};
export const ThemeSwitch = ({
width = 48,
height = 48,
}: {
width?: number;
height?: number;
}) => {
const mode = useSelector((state: any) => state.ui.mode);
const dispatch = useDispatch();
const { t } = useTranslation();
const handleChange = () => {
dispatch(setMode(mode === "light" ? "dark" : "light"));
};
return (
<IconButton
id="theme-toggle"
title={t("common.buttons.toggleTheme")}
className={`theme-${mode}`}
aria-label="auto"
aria-live="polite"
onClick={handleChange}
sx={{
width,
height,
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<SunAndMoonIcon />
</IconButton>
);
};
@@ -1,7 +0,0 @@
export { ButtonInput as Button } from "./Button";
export { ButtonGroupInput as ButtonGroup } from "./ButtonGroup";
export { TextInput } from "./TextInput";
export { SelectInput as Select } from "./Select";
export { LanguageSelector } from "./LanguageSelector";
export { ThemeSwitch } from "./ThemeSwitch";
export { TextLink } from "./TextLink";
@@ -1,23 +0,0 @@
import { Outlet } from "react-router";
import Stack from "@mui/material/Stack";
import { SideBar } from "@/Components/v2/Layouts/Sidebar";
import { useTheme } from "@mui/material/styles";
const RootLayout = () => {
const theme = useTheme();
return (
<Stack
direction="row"
minHeight="100vh"
>
<SideBar />
<Stack
flex={1}
padding={theme.spacing(12)}
>
<Outlet />
</Stack>
</Stack>
);
};
export default RootLayout;
@@ -1,16 +0,0 @@
import { useTheme } from "@mui/material/styles";
import Stack from "@mui/material/Stack";
export const BottomControls = ({}) => {
const theme = useTheme();
return (
<Stack
direction="row"
height={50}
py={theme.spacing(4)}
px={theme.spacing(8)}
gap={theme.spacing(2)}
></Stack>
);
};
@@ -1,50 +0,0 @@
import IconButton from "@mui/material/IconButton";
import { ArrowRight } from "@/Components/v2/Arrows/ArrowRight";
import { ArrowLeft } from "@/Components/v2/Arrows/ArrowLeft";
import { useTheme } from "@mui/material/styles";
import { useDispatch } from "react-redux";
import { toggleSidebar } from "../../../../Features/UI/uiSlice.js";
export const CollapseButton = ({ collapsed }: { collapsed: boolean }) => {
const theme = useTheme();
const dispatch = useDispatch();
const arrowIcon = collapsed ? (
<ArrowRight
height={theme.spacing(8)}
width={theme.spacing(8)}
color={theme.palette.primary.contrastTextSecondary}
/>
) : (
<ArrowLeft
height={theme.spacing(8)}
width={theme.spacing(8)}
color={theme.palette.primary.contrastTextSecondary}
/>
);
return (
<IconButton
sx={{
position: "absolute",
/* TODO 60 is a magic number. if logo chnges size this might break */
top: 60,
right: 0,
transform: `translate(50%, 0)`,
backgroundColor: theme.palette.tertiary.main,
border: `1px solid ${theme.palette.primary.lowContrast}`,
p: theme.spacing(2.5),
"&:focus": { outline: "none" },
"&:hover": {
backgroundColor: theme.palette.primary.lowContrast,
borderColor: theme.palette.primary.lowContrast,
},
}}
onClick={() => {
dispatch(toggleSidebar());
}}
>
{arrowIcon}
</IconButton>
);
};
@@ -1,59 +0,0 @@
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import { useTheme } from "@mui/material/styles";
import { useNavigate } from "react-router";
import { useTranslation } from "react-i18next";
export const Logo = ({ collapsed }: { collapsed: boolean }) => {
const { t } = useTranslation();
const theme = useTheme();
const navigate = useNavigate();
return (
<Stack
pt={theme.spacing(6)}
pb={theme.spacing(12)}
pl={theme.spacing(8)}
direction="row"
alignItems="center"
gap={theme.spacing(4)}
onClick={() => navigate("/")}
sx={{ cursor: "pointer" }}
>
<Typography
pl={theme.spacing("1px")}
minWidth={theme.spacing(16)}
minHeight={theme.spacing(16)}
display={"flex"}
justifyContent={"center"}
alignItems={"center"}
bgcolor={theme.palette.accent.main}
borderRadius={theme.shape.borderRadius}
color={theme.palette.accent.contrastText}
fontSize={18}
>
C
</Typography>
<Box
overflow={"hidden"}
sx={{
transition: "opacity 900ms ease, width 900ms ease",
opacity: collapsed ? 0 : 1,
whiteSpace: "nowrap",
width: collapsed ? 0 : "100%",
}}
>
{" "}
<Typography
lineHeight={1}
mt={theme.spacing(2)}
color={theme.palette.primary.contrastText}
variant="h2"
>
{t("common.appName")}
</Typography>
</Box>
</Stack>
);
};
@@ -1,58 +0,0 @@
// import Notifications from "@/assets/icons/notifications.svg?react";
import Monitors from "@/assets/icons/monitors.svg?react";
// import PageSpeed from "@/assets/icons/page-speed.svg?react";
// import Integrations from "@/assets/icons/integrations.svg?react";
// import Incidents from "@/assets/icons/incidents.svg?react";
// import StatusPages from "@/assets/icons/status-pages.svg?react";
// import Maintenance from "@/assets/icons/maintenance.svg?react";
// import Logs from "@/assets/icons/logs.svg?react";
// import Settings from "@/assets/icons/settings.svg?react";
import Support from "@/assets/icons/support.svg?react";
import Discussions from "@/assets/icons/discussions.svg?react";
import Docs from "@/assets/icons/docs.svg?react";
import ChangeLog from "@/assets/icons/changeLog.svg?react";
export const getMenu = (t: Function) => [
{ name: t("menu.uptime"), path: "v2/uptime", icon: <Monitors /> },
// { name: t("menu.pagespeed"), path: "pagespeed", icon: <PageSpeed /> },
// { name: t("menu.infrastructure"), path: "infrastructure", icon: <Integrations /> },
// {
// name: t("menu.notifications"),
// path: "notifications",
// icon: <Notifications />,
// },
// { name: t("menu.incidents"), path: "incidents", icon: <Incidents /> },
// { name: t("menu.statusPages"), path: "status", icon: <StatusPages /> },
// { name: t("menu.maintenance"), path: "maintenance", icon: <Maintenance /> },
// { name: t("menu.logs"), path: "logs", icon: <Logs /> },
// {
// name: t("menu.settings"),
// icon: <Settings />,
// path: "settings",
// },
];
export const getBottomMenu = (t: Function) => [
{ name: t("menu.support"), path: "support", icon: <Support />, url: "invite" },
{
name: t("menu.discussions"),
path: "discussions",
icon: <Discussions />,
url: "https://github.com/bluewave-labs/checkmate/discussions",
},
{
name: t("menu.docs"),
path: "docs",
icon: <Docs />,
url: "https://bluewavelabs.gitbook.io/checkmate",
},
{
name: t("menu.changelog"),
path: "changelog",
icon: <ChangeLog />,
url: "https://github.com/bluewave-labs/checkmate/releases",
},
];
@@ -1,103 +0,0 @@
import Tooltip from "@mui/material/Tooltip";
import ListItemButton from "@mui/material/ListItemButton";
import ListItemIcon from "@mui/material/ListItemIcon";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import { useTheme } from "@mui/material/styles";
export interface NavData {
name: string;
icon: JSX.Element;
}
export const NavItem = ({
item,
collapsed,
selected,
onClick,
}: {
item: NavData;
collapsed: boolean;
selected: boolean;
onClick: (event: React.MouseEvent) => void;
}) => {
const theme = useTheme();
const iconStroke = selected
? theme.palette.primary.contrastText
: theme.palette.primary.contrastTextTertiary;
const buttonBgColor = selected ? theme.palette.secondary.main : "transparent";
const buttonBgHoverColor = selected
? theme.palette.secondary.main
: theme.palette.tertiary.main;
const fontWeight = selected ? 600 : 400;
return (
<Tooltip
placement="right"
title={collapsed ? item.name : ""}
slotProps={{
popper: {
modifiers: [
{
name: "offset",
options: {
offset: [0, -16],
},
},
],
},
}}
disableInteractive
>
<ListItemButton
sx={{
backgroundColor: buttonBgColor,
"&:hover": {
backgroundColor: buttonBgHoverColor,
},
height: 37,
gap: theme.spacing(4),
borderRadius: theme.shape.borderRadius,
px: theme.spacing(4),
pl: theme.spacing(5),
}}
onClick={onClick}
>
<ListItemIcon
sx={{
minWidth: 0,
"& svg": {
height: 20,
width: 20,
opacity: 0.81,
},
"& svg path": {
stroke: iconStroke,
},
}}
>
{item.icon}
</ListItemIcon>
<Box
sx={{
overflow: "hidden",
transition: "opacity 900ms ease",
opacity: collapsed ? 0 : 1,
whiteSpace: "nowrap",
}}
>
<Typography
variant="body1"
color={theme.palette.primary.contrastText}
sx={{
fontWeight: fontWeight,
opacity: 0.9,
}}
>
{item.name}
</Typography>
</Box>
</ListItemButton>
</Tooltip>
);
};
@@ -1,106 +0,0 @@
import { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { setCollapsed } from "@/Features/UI/uiSlice";
import useMediaQuery from "@mui/material/useMediaQuery";
import { useTheme } from "@mui/material/styles";
import { useTranslation } from "react-i18next";
import { useNavigate, useLocation } from "react-router-dom";
import { CollapseButton } from "@/Components/v2/Layouts/Sidebar/CollapseButton";
import Stack from "@mui/material/Stack";
import List from "@mui/material/List";
import Divider from "@mui/material/Divider";
import { Logo } from "@/Components/v2/Layouts/Sidebar/Logo";
import { getMenu, getBottomMenu } from "@/Components/v2/Layouts/Sidebar/Menu";
import { NavItem } from "@/Components/v2/Layouts/Sidebar/NavItem";
import { BottomControls } from "@/Components/v2/Layouts/Sidebar/BottomControls";
export const COLLAPSED_WIDTH = 64;
export const EXPANDED_WIDTH = 250;
export const SideBar = () => {
const theme = useTheme();
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
const dispatch = useDispatch();
const collapsed = useSelector((state: any) => state.ui.sidebar.collapsed);
const { t } = useTranslation();
const navigate = useNavigate();
const location = useLocation();
const menu = getMenu(t);
const bottomMenu = getBottomMenu(t);
useEffect(() => {
dispatch(setCollapsed({ collapsed: isSmall }));
}, [isSmall]);
return (
<Stack
component="aside"
position="sticky"
top={0}
minHeight={"100vh"}
maxHeight={"100vh"}
paddingTop={theme.spacing(6)}
paddingBottom={theme.spacing(6)}
gap={theme.spacing(6)}
borderRight={`1px solid ${theme.palette.primary.lowContrast}`}
width={collapsed ? COLLAPSED_WIDTH : EXPANDED_WIDTH}
sx={{
transition: "width 650ms cubic-bezier(0.36, -0.01, 0, 0.77)",
}}
>
<CollapseButton collapsed={collapsed} />
<Logo collapsed={collapsed} />
<List
component="nav"
disablePadding
sx={{
px: theme.spacing(6),
height: "100%",
}}
>
{menu.map((item) => {
const selected = location.pathname.startsWith(`/${item.path}`);
return (
<NavItem
key={item.path}
item={item}
collapsed={collapsed}
selected={selected}
onClick={() => navigate(`/${item.path}`)}
/>
);
})}
</List>
<List
component="nav"
disablePadding
sx={{
px: theme.spacing(6),
}}
>
{bottomMenu.map((item) => {
const selected = location.pathname.startsWith(`/${item.path}`);
return (
<NavItem
key={item.path}
item={item}
collapsed={collapsed}
selected={selected}
onClick={() => {
if (item.url) {
window.open(item.url, "_blank", "noreferrer");
} else {
navigate(`/${item.path}`);
}
}}
/>
);
})}
</List>
<Divider sx={{ mt: "auto", borderColor: theme.palette.primary.lowContrast }} />
<BottomControls />
</Stack>
);
};
@@ -1,84 +0,0 @@
import { BaseChart } from "./HistogramStatus";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import AverageResponseIcon from "@/assets/icons/average-response-icon.svg?react";
import { Cell, RadialBarChart, RadialBar, ResponsiveContainer } from "recharts";
import { getResponseTimeColor } from "@/Utils/v2/MonitorUtils";
import { useTheme } from "@mui/material/styles";
export const ChartAvgResponse = ({ avg, max }: { avg: number; max: number }) => {
const theme = useTheme();
const chartData = [
{ name: "max", value: max - avg, color: "transparent" },
{ name: "avg", value: avg, color: "red" },
];
const palette = getResponseTimeColor(avg);
const msg: Record<string, string> = {
success: "Excellent",
warning: "Average",
danger: "Poor",
};
return (
<BaseChart
icon={<AverageResponseIcon />}
title={"Average response time"}
>
<Stack
height="100%"
position={"relative"}
justifyContent={"space-between"}
>
<ResponsiveContainer
width="100%"
height={155}
>
<RadialBarChart
cy="89%"
data={chartData}
startAngle={180}
endAngle={0}
innerRadius={"120%"}
outerRadius={"200%"}
>
<RadialBar
dataKey="value"
background={{ fill: theme.palette[palette].lowContrast }}
>
<Cell visibility={"hidden"} />
<Cell fill={theme.palette[palette].main} />
</RadialBar>
</RadialBarChart>
</ResponsiveContainer>
<Stack
direction={"row"}
justifyContent={"space-between"}
>
<Typography variant="body2">Low</Typography>
<Typography variant="body2">High</Typography>
</Stack>
<Stack
position="absolute"
top={"50%"}
right={"50%"}
sx={{
transform: "translate(50%, 0%)",
}}
>
<Typography
variant="h6"
textAlign={"center"}
>
{msg[palette]}
</Typography>
<Typography
variant="h6"
textAlign={"center"}
>{`${avg?.toFixed()}ms`}</Typography>
</Stack>
</Stack>
</BaseChart>
);
};
@@ -1,163 +0,0 @@
import { BaseChart } from "./HistogramStatus";
import { BaseBox } from "../DesignElements";
import ResponseTimeIcon from "@/assets/icons/response-time-icon.svg?react";
import { normalizeResponseTimes } from "@/Utils/v2/DataUtils";
import {
AreaChart,
Area,
XAxis,
Tooltip,
CartesianGrid,
ResponsiveContainer,
Text,
} from "recharts";
import Typography from "@mui/material/Typography";
import {
formatDateWithTz,
tickDateFormatLookup,
tooltipDateFormatLookup,
} from "@/Utils/v2/TimeUtils";
import { useTheme } from "@mui/material/styles";
import type { GroupedCheck } from "@/Types/Check";
import { useSelector } from "react-redux";
type XTickProps = {
x: number;
y: number;
payload: { value: any };
range: string;
};
const XTick: React.FC<XTickProps> = ({ x, y, payload, range }) => {
const format = tickDateFormatLookup(range);
const theme = useTheme();
const uiTimezone = useSelector((state: any) => state.ui.timezone);
return (
<Text
x={x}
y={y + 10}
textAnchor="middle"
fill={theme.palette.primary.contrastTextTertiary}
fontSize={11}
fontWeight={400}
>
{formatDateWithTz(payload?.value, format, uiTimezone)}
</Text>
);
};
type ResponseTimeToolTipProps = {
active?: boolean | undefined;
payload?: any[];
label?: string;
range: string;
theme: any;
uiTimezone: string;
};
const ResponseTimeToolTip: React.FC<ResponseTimeToolTipProps> = ({
active,
payload,
label,
range,
theme,
uiTimezone,
}) => {
if (!label) return null;
if (!payload) return null;
if (!active) return null;
const format = tooltipDateFormatLookup(range);
const responseTime = Math.floor(payload?.[0]?.payload?.avgResponseTime || 0);
return (
<BaseBox sx={{ py: theme.spacing(2), px: theme.spacing(4) }}>
<Typography>{formatDateWithTz(label, format, uiTimezone)}</Typography>
<Typography>Response time: {responseTime} ms</Typography>
</BaseBox>
);
};
export const ChartResponseTime = ({
checks,
range,
}: {
checks: GroupedCheck[];
range: string;
}) => {
const theme = useTheme();
const uiTimezone = useSelector((state: any) => state.ui.timezone);
const normalized = normalizeResponseTimes<GroupedCheck, "avgResponseTime">(
checks,
"avgResponseTime"
);
return (
<BaseChart
icon={<ResponseTimeIcon />}
title="Response times"
>
<ResponsiveContainer
width="100%"
height={300}
>
<AreaChart data={normalized?.slice().reverse()}>
<CartesianGrid
stroke={theme.palette.primary.lowContrast}
strokeWidth={1}
strokeOpacity={1}
fill="transparent"
vertical={false}
/>
<defs>
<linearGradient
id="colorUv"
x1="0"
y1="0"
x2="0"
y2="1"
>
<stop
offset="0%"
stopColor={theme.palette.accent.main}
stopOpacity={0.8}
/>
<stop
offset="100%"
stopColor={theme.palette.accent.light}
stopOpacity={0}
/>
</linearGradient>
</defs>
<XAxis
axisLine={false}
tickLine={false}
dataKey="_id"
tick={(props) => (
<XTick
{...props}
range={range}
/>
)}
/>
<Tooltip
content={(props) => (
<ResponseTimeToolTip
{...props}
range={range}
theme={theme}
uiTimezone={uiTimezone}
/>
)}
/>
<Area
type="monotone"
dataKey="normalResponseTime"
stroke={theme.palette.accent.main}
fill="url(#colorUv)"
/>
</AreaChart>
</ResponsiveContainer>
</BaseChart>
);
};
@@ -1,67 +0,0 @@
import Stack from "@mui/material/Stack";
import { MonitorStatus } from "@/Components/v2/Monitors/MonitorStatus";
import { ButtonGroup, Button } from "@/Components/v2/Inputs";
import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined";
import PauseOutlinedIcon from "@mui/icons-material/PauseOutlined";
import PlayArrowOutlinedIcon from "@mui/icons-material/PlayArrowOutlined";
import EmailIcon from "@mui/icons-material/Email";
import BugReportOutlinedIcon from "@mui/icons-material/BugReportOutlined";
import { useMediaQuery } from "@mui/material";
import { useTranslation } from "react-i18next";
import { useTheme } from "@mui/material/styles";
import type { IMonitor } from "@/Types/Monitor";
export const HeaderControls = ({
monitor,
patch,
isPatching,
refetch,
}: {
monitor: IMonitor;
patch: Function;
isPatching: boolean;
refetch: Function;
}) => {
const { t } = useTranslation();
const theme = useTheme();
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
return (
<Stack
direction={isSmall ? "column" : "row"}
spacing={isSmall ? theme.spacing(4) : 0}
justifyContent={"space-between"}
>
<MonitorStatus monitor={monitor} />
<Stack
direction={"row"}
spacing={theme.spacing(2)}
>
<ButtonGroup
orientation={isSmall ? "vertical" : "horizontal"}
fullWidth={isSmall}
variant="contained"
color="secondary"
>
<Button startIcon={<EmailIcon />}>{t("sendTestNotifications")}</Button>
<Button startIcon={<BugReportOutlinedIcon />}>{t("menu.incidents")}</Button>
<Button
loading={isPatching}
onClick={async () => {
await patch(`/monitors/${monitor._id}/active`);
refetch();
}}
startIcon={
monitor?.isActive ? <PauseOutlinedIcon /> : <PlayArrowOutlinedIcon />
}
>
{monitor?.isActive ? t("pause") : t("resume")}
</Button>
<Button startIcon={<SettingsOutlinedIcon />}>{t("configure")}</Button>
</ButtonGroup>
</Stack>
</Stack>
);
};
@@ -1,35 +0,0 @@
import Stack from "@mui/material/Stack";
import { Button } from "@/Components/v2/Inputs";
import { useTheme } from "@mui/material/styles";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router";
export const HeaderCreate = ({
label,
isLoading,
path,
}: {
label?: string;
isLoading: boolean;
path: string;
}) => {
const theme = useTheme();
const { t } = useTranslation();
const navigate = useNavigate();
return (
<Stack
direction="row"
justifyContent="end"
alignItems="center"
gap={theme.spacing(6)}
>
<Button
loading={isLoading}
variant="contained"
color="accent"
onClick={() => navigate(path)}
>
{label || t("createNew")}
</Button>
</Stack>
);
};
@@ -1,62 +0,0 @@
import Stack from "@mui/material/Stack";
import { ButtonGroup, Button } from "@/Components/v2/Inputs";
import { useTheme } from "@mui/material/styles";
import Typography from "@mui/material/Typography";
import { useMediaQuery } from "@mui/material";
export const HeaderRange = ({
range,
setRange,
loading,
}: {
range: string;
setRange: Function;
loading: boolean;
}) => {
const theme = useTheme();
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
return (
<Stack
gap={theme.spacing(9)}
direction={isSmall ? "column" : "row"}
alignItems={"center"}
justifyContent="flex-end"
>
<Typography variant="body2">{`Showing statistics for past ${range}`}</Typography>
<ButtonGroup
orientation={isSmall ? "vertical" : "horizontal"}
fullWidth={isSmall}
variant="contained"
color={"primary"}
>
<Button
color={range === "2h" ? "secondary" : "inherit"}
onClick={() => setRange("2h")}
loading={loading}
>
Recent
</Button>
<Button
color={range === "24h" ? "secondary" : "inherit"}
onClick={() => setRange("24h")}
loading={loading}
>
Day
</Button>
<Button
color={range === "7d" ? "secondary" : "inherit"}
onClick={() => setRange("7d")}
loading={loading}
>
7 days
</Button>
<Button
color={range === "30d" ? "secondary" : "inherit"}
onClick={() => setRange("30d")}
loading={loading}
>
30 days
</Button>
</ButtonGroup>
</Stack>
);
};
@@ -1,87 +0,0 @@
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import { useTheme } from "@mui/material/styles";
import type { Check } from "@/Types/Check";
import { HistogramResponseTimeTooltip } from "@/Components/v2/Monitors/HistogramResponseTimeTooltip";
import { normalizeResponseTimes } from "@/Utils/v2/DataUtils";
export const HistogramResponseTime = ({ checks }: { checks: Check[] }) => {
const normalChecks = normalizeResponseTimes(checks, "responseTime");
let data = Array<any>();
if (!normalChecks || normalChecks.length === 0) {
return null;
}
if (normalChecks.length !== 25) {
const placeholders = Array(25 - normalChecks.length).fill("placeholder");
data = [...normalChecks, ...placeholders];
} else {
data = normalChecks;
}
const theme = useTheme();
return (
<Stack
direction="row"
flexWrap="nowrap"
gap={theme.spacing(1.5)}
height="50px"
width="fit-content"
onClick={(event) => event.stopPropagation()}
sx={{
cursor: "default",
}}
>
{data.map((check, index) => {
if (check === "placeholder") {
return (
<Box
key={`${check}-${index}`}
position="relative"
width={theme.spacing(4.5)}
height="100%"
bgcolor={theme.palette.primary.lowContrast}
sx={{
borderRadius: theme.spacing(1.5),
}}
/>
);
} else {
return (
<HistogramResponseTimeTooltip
key={`${check}-${index}`}
check={check}
>
<Box
position="relative"
width="9px"
height="100%"
bgcolor={theme.palette.primary.lowContrast}
sx={{
borderRadius: theme.spacing(1.5),
}}
>
<Box
position="absolute"
bottom={0}
width="100%"
height={`${check.normalResponseTime}%`}
bgcolor={
check.status
? theme.palette.success.lowContrast
: theme.palette.error.lowContrast
}
sx={{
borderRadius: theme.spacing(1.5),
transition: "height 600ms cubic-bezier(0.4, 0, 0.2, 1)",
}}
/>
</Box>
</HistogramResponseTimeTooltip>
);
}
})}
</Stack>
);
};
@@ -1,29 +0,0 @@
import Stack from "@mui/material/Stack";
import Tooltip from "@mui/material/Tooltip";
import Typography from "@mui/material/Typography";
import { formatDateWithTz } from "@/Utils/v2/TimeUtils";
import { useSelector } from "react-redux";
import type { LatestCheck } from "@/Types/Check";
export const HistogramResponseTimeTooltip: React.FC<{
children: React.ReactElement;
check: LatestCheck;
}> = ({ children, check }) => {
const uiTimezone = useSelector((state: any) => state.ui.timezone);
return (
<Tooltip
title={
<Stack>
<Typography>
{formatDateWithTz(check?.checkedAt, "ddd, MMMM D, YYYY, HH:mm A", uiTimezone)}
</Typography>
{check?.responseTime && (
<Typography>Response Time: {check.responseTime} ms</Typography>
)}
</Stack>
}
>
{children}
</Tooltip>
);
};
@@ -1,218 +0,0 @@
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import { BaseBox } from "@/Components/v2/DesignElements";
import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts";
import UptimeIcon from "@/assets/icons/uptime-icon.svg?react";
import IncidentsIcon from "@/assets/icons/incidents.svg?react";
import type { GroupedCheck } from "@/Types/Check";
import type { MonitorStatus } from "@/Types/Monitor";
import { normalizeResponseTimes } from "@/Utils/v2/DataUtils";
import { useState } from "react";
import { formatDateWithTz } from "@/Utils/v2/TimeUtils";
import { useSelector } from "react-redux";
import { useTheme } from "@mui/material/styles";
import { getResponseTimeColor } from "@/Utils/v2/MonitorUtils";
const XLabel = ({
p1,
p2,
range,
}: {
p1: GroupedCheck;
p2: GroupedCheck;
range: string;
}) => {
const theme = useTheme();
const uiTimezone = useSelector((state: any) => state.ui.timezone);
const dateFormat = range === "day" ? "MMM D, h:mm A" : "MMM D";
return (
<>
<text
x={0}
y="100%"
dy={-3}
textAnchor="start"
fontSize={11}
fill={theme.palette.primary.contrastTextTertiary}
>
{formatDateWithTz(p1._id, dateFormat, uiTimezone)}
</text>
<text
x="100%"
y="100%"
dy={-3}
textAnchor="end"
fontSize={11}
fill={theme.palette.primary.contrastTextTertiary}
>
{formatDateWithTz(p2._id, dateFormat, uiTimezone)}
</text>
</>
);
};
type BaseChartProps = React.PropsWithChildren<{
icon: React.ReactNode;
title: string;
}>;
export const BaseChart: React.FC<BaseChartProps> = ({ children, icon, title }) => {
const theme = useTheme();
return (
<BaseBox
sx={{
padding: theme.spacing(8),
display: "flex",
flex: 1,
}}
>
<Stack
gap={theme.spacing(8)}
flex={1}
>
<Stack
direction="row"
alignItems={"center"}
gap={theme.spacing(4)}
>
<BaseBox
sx={{
display: "flex",
alignItems: "center",
justifyContent: "center",
width: 34,
height: 34,
backgroundColor: theme.palette.tertiary.main,
"& svg": {
width: 20,
height: 20,
"& path": {
stroke: theme.palette.primary.contrastTextTertiary,
},
},
}}
>
{icon}
</BaseBox>
<Typography variant="h2">{title}</Typography>
</Stack>
<Box flex={1}>{children}</Box>
</Stack>
</BaseBox>
);
};
export const HistogramStatus = ({
checks,
status,
range,
title,
}: {
checks: GroupedCheck[];
status: MonitorStatus;
range: string;
title: string;
}) => {
const uiTimezone = useSelector((state: any) => state.ui.timezone);
const icon = status === "up" ? <UptimeIcon /> : <IncidentsIcon />;
const theme = useTheme();
const [idx, setIdx] = useState<number | null>(null);
const dateFormat = range === "1d" || range === "2h" ? "MMM D, h A" : "MMM D";
const normalChecks = normalizeResponseTimes(checks, "avgResponseTime");
if (normalChecks.length === 0) {
return (
<BaseChart
icon={icon}
title={title}
>
<Stack
height={"100%"}
alignItems={"center"}
justifyContent={"center"}
>
<Typography variant="h2">
{status === "up" ? "No checks yet" : "Great, no downtime yet!"}
</Typography>
</Stack>
</BaseChart>
);
}
const totalChecks = normalChecks.reduce((count, check) => {
return count + check.count;
}, 0);
return (
<BaseChart
icon={icon}
title={title}
>
<Stack gap={theme.spacing(8)}>
<Stack
position="relative"
direction="row"
justifyContent="space-between"
>
<Stack>
<Typography>Total checks</Typography>
{idx ? (
<Stack>
<Typography variant="h2">{normalChecks[idx].count}</Typography>
<Typography
position={"absolute"}
top={"100%"}
>
{formatDateWithTz(normalChecks[idx]._id, dateFormat, uiTimezone)}
</Typography>
</Stack>
) : (
<Typography variant="h2">{totalChecks}</Typography>
)}
</Stack>
</Stack>
<ResponsiveContainer
width="100%"
height={155}
>
<BarChart data={normalChecks}>
<XAxis
stroke={theme.palette.primary.lowContrast}
height={15}
tick={false}
label={
<XLabel
p1={normalChecks[0]}
p2={normalChecks[normalChecks.length - 1]}
range={range}
/>
}
/>
<Bar
dataKey="normalResponseTime"
maxBarSize={7}
background={{ fill: "transparent" }}
>
{normalChecks?.map((groupedCheck, idx) => {
const fillColor = getResponseTimeColor(groupedCheck.normalResponseTime);
return (
<Cell
onMouseEnter={() => setIdx(idx)}
onMouseLeave={() => setIdx(null)}
key={groupedCheck._id}
fill={theme.palette[fillColor].main}
/>
);
})}
</Bar>
</BarChart>
</ResponsiveContainer>
</Stack>
</BaseChart>
);
};
@@ -1,60 +0,0 @@
import type { IMonitor } from "@/Types/Monitor";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { PulseDot } from "@/Components/v2/DesignElements/PulseDot";
import { Dot } from "@/Components/v2/DesignElements/Dot";
import { getStatusColor, formatUrl } from "@/Utils/v2/MonitorUtils";
import { useTheme } from "@mui/material/styles";
import prettyMilliseconds from "pretty-ms";
import { typographyLevels } from "@/Utils/Theme/v2/palette";
import { useMediaQuery } from "@mui/material";
export const MonitorStatus = ({ monitor }: { monitor: IMonitor }) => {
const theme = useTheme();
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
if (!monitor) {
return null;
}
return (
<Stack>
<Typography
fontSize={typographyLevels.xl}
fontWeight={500}
color={theme.palette.primary.contrastText}
overflow={"hidden"}
textOverflow={"ellipsis"}
whiteSpace={"nowrap"}
maxWidth={isSmall ? "100%" : "calc((100vw - var(--env-var-width-2)) / 2)"}
>
{monitor.name}
</Typography>
<Stack
direction="row"
alignItems={"center"}
gap={theme.spacing(4)}
>
<PulseDot color={getStatusColor(monitor.status, theme)} />
<Typography
color={theme.palette.primary.contrastTextSecondary}
fontSize={typographyLevels.l}
fontWeight={"bolder"}
fontFamily={"monospace"}
overflow={"hidden"}
textOverflow={"ellipsis"}
whiteSpace={"nowrap"}
maxWidth={isSmall ? "100%" : "calc((100vw - var(--env-var-width-2)) / 2)"}
>
{formatUrl(monitor?.url)}
</Typography>
{!isSmall && (
<>
<Dot />
<Typography>
Checking every {prettyMilliseconds(monitor?.interval, { verbose: true })}
</Typography>
</>
)}
</Stack>
</Stack>
);
};
@@ -1 +0,0 @@
export { HeaderCreate } from "./HeaderCreate";
-19
View File
@@ -1,19 +0,0 @@
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
isAuthenticated: false,
};
const v2AuthSlice = createSlice({
name: "v2Auth",
initialState,
reducers: {
setIsAuthenticated: (state, action) => {
const { authenticated } = action.payload;
state.isAuthenticated = authenticated;
},
},
});
export default v2AuthSlice.reducer;
export const { setIsAuthenticated } = v2AuthSlice.actions;
+3
View File
@@ -58,6 +58,9 @@ const uiSlice = createSlice({
state.showURL = action.payload;
},
setGreeting(state, action) {
if (!state.greeting) {
state.greeting = { index: 0, lastUpdate: null };
}
state.greeting.index = action.payload.index;
state.greeting.lastUpdate = action.payload.lastUpdate;
},
@@ -1,6 +1,6 @@
import { useState, useEffect } from "react";
import { networkService } from "../../main.jsx";
import { createToast } from "../../Utils/toastUtils.jsx";
import { networkService } from "../main.jsx";
import { createToast } from "../Utils/toastUtils.jsx";
import { useTranslation } from "react-i18next";
const useFetchChecksTeam = ({
Loaded 100 of 588 files, more files were not shown because too many files have changed in this diff. Show more