From 8bfb187ef0c95931a844a9930e4fac73995d5ef6 Mon Sep 17 00:00:00 2001 From: Owaise Imdad Date: Sun, 27 Jul 2025 03:47:30 +0530 Subject: [PATCH 01/33] FIX merge conflicts --- docker/dev/docker-compose.yaml | 33 ++++++++------------------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/docker/dev/docker-compose.yaml b/docker/dev/docker-compose.yaml index 292d0b1d5..75d152052 100755 --- a/docker/dev/docker-compose.yaml +++ b/docker/dev/docker-compose.yaml @@ -1,29 +1,9 @@ services: - client: - image: uptime_client:latest - restart: always - ports: - - "80:80" - environment: - UPTIME_APP_API_BASE_URL: "http://localhost:52345/api/v1" - UPTIME_APP_CLIENT_HOST: "http://localhost" - volumes: - - ./nginx/conf.d:/etc/nginx/conf.d/ - depends_on: - - server - server: - image: uptime_server:latest - restart: always - ports: - - "52345:52345" - env_file: - - server.env - depends_on: - - redis - - mongodb redis: - image: uptime_redis:latest + image: redis:latest restart: always + ports: + - "6379:6379" # ✅ Expose Redis to host volumes: - ./redis/data:/data healthcheck: @@ -32,14 +12,17 @@ services: timeout: 10s retries: 5 start_period: 5s + mongodb: - image: uptime_mongo:latest + image: mongo:latest restart: always + ports: + - "27017:27017" # ✅ Expose MongoDB to host command: ["mongod", "--quiet", "--replSet", "rs0", "--bind_ip_all"] volumes: - ./mongo/data:/data/db healthcheck: - test: echo "try { rs.status() } catch (err) { rs.initiate({_id:'rs0',members:[{_id:0,host:'mongodb:27017'}]}) }" | mongosh --port 27017 --quiet + test: echo "try { rs.status() } catch (err) { rs.initiate({_id:'rs0',members:[{_id:0,host:'localhost:27017'}]}) }" | mongosh --port 27017 --quiet interval: 5s timeout: 30s start_period: 0s From 5c147562b3c6954f2d2dee084247e089d973b344 Mon Sep 17 00:00:00 2001 From: Owaise Imdad Date: Tue, 29 Jul 2025 00:17:34 +0530 Subject: [PATCH 02/33] Added net to be returned from api in backend for infra. --- server/db/models/HardwareCheck.js | 19 +++++ .../db/mongo/modules/monitorModuleQueries.js | 82 +++++++++++++++++++ .../service/infrastructure/statusService.js | 3 +- 3 files changed, 103 insertions(+), 1 deletion(-) diff --git a/server/db/models/HardwareCheck.js b/server/db/models/HardwareCheck.js index bda9f693c..f29df2dbc 100755 --- a/server/db/models/HardwareCheck.js +++ b/server/db/models/HardwareCheck.js @@ -40,6 +40,20 @@ const captureSchema = mongoose.Schema({ mode: { type: String, default: "" }, }); +const networkInterfaceSchema = mongoose.Schema({ + name: { type: String, required: true }, + bytes_sent: { type: Number, default: 0 }, + bytes_recv: { type: Number, default: 0 }, + packets_sent: { type: Number, default: 0 }, + packets_recv: { type: Number, default: 0 }, + err_in: { type: Number, default: 0 }, + err_out: { type: Number, default: 0 }, + drop_in: { type: Number, default: 0 }, + drop_out: { type: Number, default: 0 }, + fifo_in: { type: Number, default: 0 }, + fifo_out: { type: Number, default: 0 }, +}); + const HardwareCheckSchema = mongoose.Schema( { ...BaseCheckSchema.obj, @@ -69,6 +83,11 @@ const HardwareCheckSchema = mongoose.Schema( type: captureSchema, default: () => ({}), }, + + net: { + type: [networkInterfaceSchema], + default: () => [], + }, }, { timestamps: true } ); diff --git a/server/db/mongo/modules/monitorModuleQueries.js b/server/db/mongo/modules/monitorModuleQueries.js index d96e128fa..56c9a794b 100755 --- a/server/db/mongo/modules/monitorModuleQueries.js +++ b/server/db/mongo/modules/monitorModuleQueries.js @@ -220,6 +220,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { diskCount: { $size: "$disk", }, + netCount: { $size: "$net" }, }, }, { @@ -227,6 +228,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { from: "hardwarechecks", let: { diskCount: "$diskCount", + netCount: "$netCount", }, pipeline: [ { @@ -258,6 +260,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { disks: { $push: "$disk", }, + net: { + $push: "$net", + }, }, }, { @@ -371,6 +376,83 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, }, }, + net: { + $map: { + input: { $range: [0, "$$netCount"] }, + as: "netIndex", + in: { + name: { + $concat: ["net", { $toString: "$$netIndex" }], + }, + bytesSent: { + $avg: { + $map: { + input: "$net", + as: "netArray", + in: { + $arrayElemAt: ["$$netArray.bytes_sent", "$$netIndex"], + }, + }, + }, + }, + bytesRecv: { + $avg: { + $map: { + input: "$net", + as: "netArray", + in: { + $arrayElemAt: ["$$netArray.bytes_recv", "$$netIndex"], + }, + }, + }, + }, + packetsSent: { + $avg: { + $map: { + input: "$net", + as: "netArray", + in: { + $arrayElemAt: ["$$netArray.packets_sent", "$$netIndex"], + }, + }, + }, + }, + packetsRecv: { + $avg: { + $map: { + input: "$net", + as: "netArray", + in: { + $arrayElemAt: ["$$netArray.packets_recv", "$$netIndex"], + }, + }, + }, + }, + errIn: { + $avg: { + $map: { + input: "$net", + as: "netArray", + in: { + $arrayElemAt: ["$$netArray.errin", "$$netIndex"], + }, + }, + }, + }, + errOut: { + $avg: { + $map: { + input: "$net", + as: "netArray", + in: { + $arrayElemAt: ["$$netArray.errout", "$$netIndex"], + }, + }, + }, + }, + }, + }, + }, }, }, ], diff --git a/server/service/infrastructure/statusService.js b/server/service/infrastructure/statusService.js index 1c91a407c..0967d3d46 100755 --- a/server/service/infrastructure/statusService.js +++ b/server/service/infrastructure/statusService.js @@ -232,7 +232,7 @@ class StatusService { } if (type === "hardware") { - const { cpu, memory, disk, host } = payload?.data ?? {}; + const { cpu, memory, disk, host, net } = payload?.data ?? {}; const { errors } = payload?.errors ?? []; check.cpu = cpu ?? {}; check.memory = memory ?? {}; @@ -240,6 +240,7 @@ class StatusService { check.host = host ?? {}; check.errors = errors ?? []; check.capture = payload?.capture ?? {}; + check.net = net ?? {}; } return check; }; From 19fe41d738ce87098d0235c361da5d4daa39978b Mon Sep 17 00:00:00 2001 From: Owaise Imdad Date: Tue, 29 Jul 2025 00:22:14 +0530 Subject: [PATCH 03/33] reverting the docker dev changes I made. --- docker/dev/docker-compose.yaml | 35 +++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/docker/dev/docker-compose.yaml b/docker/dev/docker-compose.yaml index 75d152052..f0b193b2d 100755 --- a/docker/dev/docker-compose.yaml +++ b/docker/dev/docker-compose.yaml @@ -1,9 +1,29 @@ services: - redis: - image: redis:latest + client: + image: uptime_client:latest restart: always ports: - - "6379:6379" # ✅ Expose Redis to host + - "80:80" + environment: + UPTIME_APP_API_BASE_URL: "http://localhost:52345/api/v1" + UPTIME_APP_CLIENT_HOST: "http://localhost" + volumes: + - ./nginx/conf.d:/etc/nginx/conf.d/ + depends_on: + - server + server: + image: uptime_server:latest + restart: always + ports: + - "52345:52345" + env_file: + - server.env + depends_on: + - redis + - mongodb + redis: + image: uptime_redis:latest + restart: always volumes: - ./redis/data:/data healthcheck: @@ -12,19 +32,16 @@ services: timeout: 10s retries: 5 start_period: 5s - mongodb: - image: mongo:latest + image: uptime_mongo:latest restart: always - ports: - - "27017:27017" # ✅ Expose MongoDB to host command: ["mongod", "--quiet", "--replSet", "rs0", "--bind_ip_all"] volumes: - ./mongo/data:/data/db healthcheck: - test: echo "try { rs.status() } catch (err) { rs.initiate({_id:'rs0',members:[{_id:0,host:'localhost:27017'}]}) }" | mongosh --port 27017 --quiet + test: echo "try { rs.status() } catch (err) { rs.initiate({_id:'rs0',members:[{_id:0,host:'mongodb:27017'}]}) }" | mongosh --port 27017 --quiet interval: 5s timeout: 30s start_period: 0s start_interval: 1s - retries: 30 + retries: 30 \ No newline at end of file From ab0bf5ca5f9a8cbe10437d1e1b4a43ba31a63322 Mon Sep 17 00:00:00 2001 From: Owaise Imdad Date: Tue, 29 Jul 2025 00:42:55 +0530 Subject: [PATCH 04/33] Fixed the names and err out and err in . --- .../src/db/mongo/modules/monitorModuleQueries.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/server/src/db/mongo/modules/monitorModuleQueries.js b/server/src/db/mongo/modules/monitorModuleQueries.js index 56c9a794b..df15593e2 100755 --- a/server/src/db/mongo/modules/monitorModuleQueries.js +++ b/server/src/db/mongo/modules/monitorModuleQueries.js @@ -382,7 +382,16 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { as: "netIndex", in: { name: { - $concat: ["net", { $toString: "$$netIndex" }], + $arrayElemAt: [ + { + $map: { + input: "$net", + as: "netArray", + in: { $arrayElemAt: ["$$netArray.name", "$$netIndex"] }, + }, + }, + 0, + ], }, bytesSent: { $avg: { @@ -434,7 +443,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { input: "$net", as: "netArray", in: { - $arrayElemAt: ["$$netArray.errin", "$$netIndex"], + $arrayElemAt: ["$$netArray.err_in", "$$netIndex"], }, }, }, @@ -445,7 +454,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { input: "$net", as: "netArray", in: { - $arrayElemAt: ["$$netArray.errout", "$$netIndex"], + $arrayElemAt: ["$$netArray.err_out", "$$netIndex"], }, }, }, From 94a7044b7be4b1d9ae7a6561275ca06b296100d8 Mon Sep 17 00:00:00 2001 From: Mohansai Mallineni Date: Thu, 31 Jul 2025 05:07:42 +0530 Subject: [PATCH 05/33] Replaced existing icons with the provided svg's in the Incident page block area --- .../Components/StatusBoxes/StatusBox.jsx | 16 ++++++++-------- client/src/assets/icons/alert-icon.svg | 3 +++ client/src/assets/icons/check-icon.svg | 3 +++ client/src/assets/icons/close-icon.svg | 3 +++ client/src/assets/icons/warning-icon.svg | 3 +++ 5 files changed, 20 insertions(+), 8 deletions(-) create mode 100644 client/src/assets/icons/alert-icon.svg create mode 100644 client/src/assets/icons/check-icon.svg create mode 100644 client/src/assets/icons/close-icon.svg create mode 100644 client/src/assets/icons/warning-icon.svg diff --git a/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx b/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx index b19f017e7..90e664b5a 100644 --- a/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx +++ b/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx @@ -2,10 +2,10 @@ import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; import { Box, Stack, Typography } from "@mui/material"; import Background from "../../../../assets/Images/background-grid.svg?react"; -import MonitorHeartOutlinedIcon from "@mui/icons-material/MonitorHeartOutlined"; -import TaskAltOutlinedIcon from "@mui/icons-material/TaskAltOutlined"; -import CancelOutlinedIcon from "@mui/icons-material/CancelOutlined"; -import WarningAmberRoundedIcon from "@mui/icons-material/WarningAmberRounded"; +import AlertIcon from "../../../../assets/icons/alert-icon.svg?react"; +import CheckIcon from "../../../../assets/icons/check-icon.svg?react"; +import CloseIcon from "../../../../assets/icons/close-icon.svg?react"; +import WarningIcon from "../../../../assets/icons/warning-icon.svg?react"; const StatusBox = ({ title, value, status }) => { const theme = useTheme(); @@ -22,28 +22,28 @@ const StatusBox = ({ title, value, status }) => { color = theme.palette.success.lowContrast; icon = ( - + ); } else if (status === "down") { color = theme.palette.error.lowContrast; icon = ( - + ); } else if (status === "paused") { color = theme.palette.warning.lowContrast; icon = ( - + ); } else { color = theme.palette.accent.main; icon = ( - + ); } diff --git a/client/src/assets/icons/alert-icon.svg b/client/src/assets/icons/alert-icon.svg new file mode 100644 index 000000000..1dc7fc2b7 --- /dev/null +++ b/client/src/assets/icons/alert-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/src/assets/icons/check-icon.svg b/client/src/assets/icons/check-icon.svg new file mode 100644 index 000000000..9a2cfac5a --- /dev/null +++ b/client/src/assets/icons/check-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/src/assets/icons/close-icon.svg b/client/src/assets/icons/close-icon.svg new file mode 100644 index 000000000..e1b6b690a --- /dev/null +++ b/client/src/assets/icons/close-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/src/assets/icons/warning-icon.svg b/client/src/assets/icons/warning-icon.svg new file mode 100644 index 000000000..468611758 --- /dev/null +++ b/client/src/assets/icons/warning-icon.svg @@ -0,0 +1,3 @@ + + + From 126767ed5c97f255d28298065de5f952c2e2a313 Mon Sep 17 00:00:00 2001 From: Mohansai Mallineni Date: Thu, 31 Jul 2025 22:39:17 +0530 Subject: [PATCH 06/33] Replaced existing icons with the provided svg's in the Incident page block area --- client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx b/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx index 90e664b5a..adf4faa8b 100644 --- a/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx +++ b/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx @@ -12,7 +12,6 @@ const StatusBox = ({ title, value, status }) => { let sharedStyles = { position: "absolute", right: 8, - opacity: 0.5, "& svg path": { stroke: theme.palette.primary.contrastTextTertiary }, }; From 3dc332010e7d72f8dc964962b615e666926575f9 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 31 Jul 2025 12:47:36 -0700 Subject: [PATCH 07/33] start refactor --- .../Components/Layouts/HomeLayout/index.css | 4 +- .../Sidebar/{index.css => index.bak} | 3 +- client/src/Components/Sidebar/index.jsx | 774 +--------------- client/src/Components/Sidebar/index_old.jsx | 873 ++++++++++++++++++ client/src/Components/Sidebar/logo.jsx | 57 ++ 5 files changed, 972 insertions(+), 739 deletions(-) rename client/src/Components/Sidebar/{index.css => index.bak} (98%) create mode 100644 client/src/Components/Sidebar/index_old.jsx create mode 100644 client/src/Components/Sidebar/logo.jsx diff --git a/client/src/Components/Layouts/HomeLayout/index.css b/client/src/Components/Layouts/HomeLayout/index.css index 2db147f41..56606a935 100644 --- a/client/src/Components/Layouts/HomeLayout/index.css +++ b/client/src/Components/Layouts/HomeLayout/index.css @@ -12,13 +12,13 @@ } } */ -.home-layout aside { +/* .home-layout aside { position: sticky; top: 0; left: 0; height: 100vh; max-width: var(--env-var-side-bar-width); -} +} */ .home-layout > div { min-height: calc(100vh - var(--env-var-spacing-2) * 2); diff --git a/client/src/Components/Sidebar/index.css b/client/src/Components/Sidebar/index.bak similarity index 98% rename from client/src/Components/Sidebar/index.css rename to client/src/Components/Sidebar/index.bak index ef921acca..a14a0f3d6 100644 --- a/client/src/Components/Sidebar/index.css +++ b/client/src/Components/Sidebar/index.bak @@ -1,5 +1,4 @@ -/* TODO */ -aside .MuiList-root svg { +TODO aside .MuiList-root svg { width: 20px; height: 20px; opacity: 0.9; diff --git a/client/src/Components/Sidebar/index.jsx b/client/src/Components/Sidebar/index.jsx index 219ceff1e..cad7d3b7d 100644 --- a/client/src/Components/Sidebar/index.jsx +++ b/client/src/Components/Sidebar/index.jsx @@ -1,19 +1,14 @@ -import React, { useEffect, useState, useRef } from "react"; -import { - Box, - Collapse, - Divider, - IconButton, - List, - ListItemButton, - ListItemIcon, - ListItemText, - Menu, - MenuItem, - Stack, - Tooltip, - Typography, -} from "@mui/material"; +import IconButton from "@mui/material/IconButton"; +import Stack from "@mui/material/Stack"; +import ArrowRight from "../../assets/icons/right-arrow.svg?react"; +import ArrowLeft from "../../assets/icons/left-arrow.svg?react"; +import List from "@mui/material/List"; +import ListItemButton from "@mui/material/ListItemButton"; +import ListItemIcon from "@mui/material/ListItemIcon"; +import ListItemText from "@mui/material/ListItemText"; +import Tooltip from "@mui/material/Tooltip"; +import Logo from "./logo"; + import ThemeSwitch from "../ThemeSwitch"; import Avatar from "../Avatar"; import StarPrompt from "../StarPrompt"; @@ -30,8 +25,6 @@ import PageSpeed from "../../assets/icons/page-speed.svg?react"; import Settings from "../../assets/icons/settings.svg?react"; import ArrowDown from "../../assets/icons/down-arrow.svg?react"; import ArrowUp from "../../assets/icons/up-arrow.svg?react"; -import ArrowRight from "../../assets/icons/right-arrow.svg?react"; -import ArrowLeft from "../../assets/icons/left-arrow.svg?react"; import DotsVertical from "../../assets/icons/dots-vertical.svg?react"; import ChangeLog from "../../assets/icons/changeLog.svg?react"; import Docs from "../../assets/icons/docs.svg?react"; @@ -40,17 +33,13 @@ 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 "./index.css"; - // Utils -import { useLocation, useNavigate } from "react-router"; -import { useTheme } from "@emotion/react"; +import { useTheme } from "@mui/material/styles"; import { useDispatch, useSelector } from "react-redux"; -import { useTranslation } from "react-i18next"; -import { clearAuthState } from "../../Features/Auth/authSlice"; import { toggleSidebar } from "../../Features/UI/uiSlice"; -import { TurnedIn } from "@mui/icons-material"; -import { rules } from "eslint-plugin-react-refresh"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useNavigate } from "react-router"; const getMenu = (t) => [ { name: t("menu.uptime"), path: "uptime", icon: }, @@ -75,207 +64,25 @@ const getMenu = (t) => [ }, ]; -const getOtherMenuItems = (t) => [ - { name: t("menu.support"), path: "support", icon: }, - { - name: t("menu.discussions"), - path: "discussions", - icon: , - }, - { name: t("menu.docs"), path: "docs", icon: }, - { name: t("menu.changelog"), path: "changelog", icon: }, -]; - -const getAccountMenuItems = (t) => [ - { name: t("menu.profile"), path: "account/profile", icon: }, - { name: t("menu.password"), path: "account/password", icon: }, - { name: t("menu.team"), path: "account/team", icon: }, -]; - -/* TODO this could be a key in nested Path would be the link */ -const URL_MAP = { - support: "https://discord.com/invite/NAb6H3UTjK", - discussions: "https://github.com/bluewave-labs/checkmate/discussions", - docs: "https://bluewavelabs.gitbook.io/checkmate", - changelog: "https://github.com/bluewave-labs/checkmate/releases", -}; - -const PATH_MAP = { - monitors: "Dashboard", - pagespeed: "Dashboard", - infrastructure: "Dashboard", - account: "Account", - settings: "Settings", -}; - -/** - * @component - * Sidebar component serves as a sidebar containing a menu. - * - * @returns {JSX.Element} The JSX element representing the Sidebar component. - */ - -function Sidebar() { +const Sidebar = () => { const theme = useTheme(); - const navigate = useNavigate(); - const location = useLocation(); const dispatch = useDispatch(); const { t } = useTranslation(); - const authState = useSelector((state) => state.auth); - - const otherMenuItems = getOtherMenuItems(t); - const accountMenuItems = getAccountMenuItems(t); + const navigate = useNavigate(); + // Redux state const collapsed = useSelector((state) => state.ui.sidebar.collapsed); + + // Local state const [open, setOpen] = useState({ Dashboard: false, Account: false, Other: false }); - const [anchorEl, setAnchorEl] = useState(null); - const [popup, setPopup] = useState(); - const { user } = useSelector((state) => state.auth); - const sidebarRef = useRef(null); - const [sidebarReady, setSidebarReady] = useState(false); - const TRANSITION_DURATION = 200; - let menu = getMenu(t); - menu = menu.filter((item) => { - if (item.path === "logs") { - return user.role?.includes("admin") || user.role?.includes("superadmin"); - } - return true; - }); - - useEffect(() => { - if (!collapsed) { - setSidebarReady(false); - const timeout = setTimeout(() => { - setSidebarReady(true); - }, TRANSITION_DURATION); - return () => clearTimeout(timeout); - } else { - setSidebarReady(false); - } - }, [collapsed]); - - const renderAccountMenuItems = () => { - let filteredAccountMenuItems = [...accountMenuItems]; - - // If the user is in demo mode, remove the "Password" option - if (user.role?.includes("demo")) { - filteredAccountMenuItems = filteredAccountMenuItems.filter( - (item) => item.name !== "Password" - ); - } - - // If the user is NOT a superadmin, remove the "Team" option - if (user.role && !user.role.includes("superadmin")) { - filteredAccountMenuItems = filteredAccountMenuItems.filter( - (item) => item.name !== "Team" - ); - } - - return filteredAccountMenuItems.map((item) => ( - { - closePopup(); - navigate(item.path); - }} - sx={{ - gap: theme.spacing(2), - borderRadius: theme.shape.borderRadius, - pl: theme.spacing(4), - }} - > - {item.icon} - {item.name} - - )); - }; - - const openPopup = (event, id) => { - setAnchorEl(event.currentTarget); - setPopup(id); - }; - const closePopup = () => { - setAnchorEl(null); - }; - - /** - * Handles logging out the user - * - */ - const logout = async () => { - // Clear auth state - dispatch(clearAuthState()); - navigate("/login"); - }; - - useEffect(() => { - const matchedKey = Object.keys(PATH_MAP).find((key) => - location.pathname.includes(key) - ); - - if (matchedKey) { - setOpen((prev) => ({ ...prev, [PATH_MAP[matchedKey]]: true })); - } - }, [location]); - - const iconColor = theme.palette.primary.contrastTextTertiary; - const sidebarClassName = `${collapsed ? "collapsed" : "expanded"} ${sidebarReady ? "sidebar-ready" : ""}`; - - /* TODO refactor this, there are a some ternaries and comments in the return */ + const menu = getMenu(t); + console.log(collapsed); return ( {collapsed ? : } - {/* TODO Alignment done using padding. Use single source of truth to that*/} - - {/* TODO Abstract logo into component */} - {/* TODO Turn logo into a link */} - navigate("/")} - sx={{ cursor: "pointer" }} - > - - C - - - {t("common.appName")} - - - - - - {menu.map((item) => { - return item.path ? ( - /* If item has a path */ - - navigate(`/${item.path}`)} - sx={{ - height: "37px", - gap: theme.spacing(4), - borderRadius: theme.shape.borderRadius, - px: theme.spacing(4), - pl: theme.spacing(5), - }} - > - {item.icon} - {item.name} - - - ) : collapsed ? ( - /* TODO Do we ever get here? If item does not have a path and collapsed state is true */ - - - openPopup(event, item.name)} - sx={{ - position: "relative", - gap: theme.spacing(4), - borderRadius: theme.shape.borderRadius, - px: theme.spacing(4), - }} - > - {item.icon} - {item.name} - - - - {item.nested.map((child) => { - if ( - child.name === "Team" && - authState.user?.role && - !authState.user.role.includes("superadmin") - ) { - return null; - } - - return ( - { - const url = URL_MAP[child.path]; - if (url) { - window.open(url, "_blank", "noreferrer"); - } else { - navigate(`/${child.path}`); - } - closePopup(); - }} - sx={{ - gap: theme.spacing(4), - opacity: 0.9, - /* TODO this has no effect? */ - "& svg": { - "& path": { - stroke: theme.palette.primary.contrastTextTertiary, - strokeWidth: 1.1, - }, - }, - }} - > - {child.icon} - {child.name} - - ); - })} - - - ) : ( - /* TODO Do we ever get here? If item does not have a path and collapsed state is false */ - - - setOpen((prev) => ({ - ...Object.fromEntries(Object.keys(prev).map((key) => [key, false])), - [item.name]: !prev[item.name], - })) - } - sx={{ - gap: theme.spacing(4), - borderRadius: theme.shape.borderRadius, - px: theme.spacing(4), - }} - > - {item.icon} - {item.name} - {open[`${item.name}`] ? : } - - - - {item.nested.map((child) => { - if ( - child.name === "Team" && - authState.user?.role && - !authState.user.role.includes("superadmin") - ) { - return null; - } - - return ( - { - const url = URL_MAP[child.path]; - if (url) { - window.open(url, "_blank", "noreferrer"); - } else { - navigate(`/${child.path}`); - } - }} - sx={{ - gap: theme.spacing(4), - borderRadius: theme.shape.borderRadius, - pl: theme.spacing(4), - "&::before": { - content: `""`, - position: "absolute", - top: 0, - left: "-7px", - height: "100%", - borderLeft: 1, - borderLeftColor: theme.palette.primary.lowContrast, - }, - "&:last-child::before": { - height: "50%", - }, - "&::after": { - content: `""`, - position: "absolute", - top: "45%", - left: "-8px", - height: "3px", - width: "3px", - borderRadius: "50%", - backgroundColor: theme.palette.primary.lowContrast, - }, - "&.selected-path::after": { - /* TODO what is this selector doing? */ - backgroundColor: theme.palette.primary.contrastTextTertiary, - transform: "scale(1.2)", - }, - }} - > - {child.icon} - {child.name} - - ); - })} - - - - ); - })} - - - - {!collapsed && } - + - {otherMenuItems.map((item) => { + {menu.map((item) => { return item.path ? ( + /* If item has a path */ { - const url = URL_MAP[item.path]; - if (url) { - window.open(url, "_blank", "noreferrer"); - } else { - navigate(`/${item.path}`); - } - }} + onClick={() => navigate(`/${item.path}`)} sx={{ height: "37px", gap: theme.spacing(4), @@ -666,208 +163,15 @@ function Sidebar() { pl: theme.spacing(5), }} > - {item.icon} - {item.name} {" "} + {item.icon} + {!collapsed && {item.name}} ) : null; })} - - - {collapsed ? ( - <> - - openPopup(event, "logout")} - sx={{ p: 0, "&:focus": { outline: "none" } }} - > - - - - - ) : ( - <> - - - - {authState.user?.firstName} {authState.user?.lastName} - - - {authState.user?.role?.includes("superadmin") - ? t("roles.superAdmin") - : authState.user?.role?.includes("admin") - ? t("roles.admin") - : authState.user?.role?.includes("user") - ? t("roles.teamMember") - : authState.user?.role?.includes("demo") - ? t("roles.demoUser") - : authState.user?.role} - - - - - - openPopup(event, "logout")} - > - - - - - - )} - - {collapsed && ( - - - - {authState.user?.firstName} {authState.user?.lastName} - - - {authState.user?.role} - - - - )} - {/* TODO Do we need two dividers? */} - {collapsed && } - {/* */} - {renderAccountMenuItems()} - - - {t("menu.logOut", "Log out")} - - - ); -} +}; export default Sidebar; diff --git a/client/src/Components/Sidebar/index_old.jsx b/client/src/Components/Sidebar/index_old.jsx new file mode 100644 index 000000000..219ceff1e --- /dev/null +++ b/client/src/Components/Sidebar/index_old.jsx @@ -0,0 +1,873 @@ +import React, { useEffect, useState, useRef } from "react"; +import { + Box, + Collapse, + Divider, + IconButton, + List, + ListItemButton, + ListItemIcon, + ListItemText, + Menu, + MenuItem, + Stack, + Tooltip, + Typography, +} from "@mui/material"; +import ThemeSwitch from "../ThemeSwitch"; +import Avatar from "../Avatar"; +import StarPrompt from "../StarPrompt"; +import LockSvg from "../../assets/icons/lock.svg?react"; +import UserSvg from "../../assets/icons/user.svg?react"; +import TeamSvg from "../../assets/icons/user-two.svg?react"; +import LogoutSvg from "../../assets/icons/logout.svg?react"; +import Support from "../../assets/icons/support.svg?react"; +import 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 ArrowDown from "../../assets/icons/down-arrow.svg?react"; +import ArrowUp from "../../assets/icons/up-arrow.svg?react"; +import ArrowRight from "../../assets/icons/right-arrow.svg?react"; +import ArrowLeft from "../../assets/icons/left-arrow.svg?react"; +import DotsVertical from "../../assets/icons/dots-vertical.svg?react"; +import ChangeLog from "../../assets/icons/changeLog.svg?react"; +import Docs from "../../assets/icons/docs.svg?react"; +import 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 "./index.css"; + +// Utils +import { useLocation, useNavigate } from "react-router"; +import { useTheme } from "@emotion/react"; +import { useDispatch, useSelector } from "react-redux"; +import { useTranslation } from "react-i18next"; +import { clearAuthState } from "../../Features/Auth/authSlice"; +import { toggleSidebar } from "../../Features/UI/uiSlice"; +import { TurnedIn } from "@mui/icons-material"; +import { rules } from "eslint-plugin-react-refresh"; + +const getMenu = (t) => [ + { name: t("menu.uptime"), path: "uptime", icon: }, + { name: t("menu.pagespeed"), path: "pagespeed", icon: }, + + { name: t("menu.infrastructure"), path: "infrastructure", icon: }, + { + name: t("menu.notifications"), + path: "notifications", + icon: , + }, + { name: t("menu.incidents"), path: "incidents", icon: }, + + { name: t("menu.statusPages"), path: "status", icon: }, + { name: t("menu.maintenance"), path: "maintenance", icon: }, + { name: t("menu.logs"), path: "logs", icon: }, + + { + name: t("menu.settings"), + icon: , + path: "settings", + }, +]; + +const getOtherMenuItems = (t) => [ + { name: t("menu.support"), path: "support", icon: }, + { + name: t("menu.discussions"), + path: "discussions", + icon: , + }, + { name: t("menu.docs"), path: "docs", icon: }, + { name: t("menu.changelog"), path: "changelog", icon: }, +]; + +const getAccountMenuItems = (t) => [ + { name: t("menu.profile"), path: "account/profile", icon: }, + { name: t("menu.password"), path: "account/password", icon: }, + { name: t("menu.team"), path: "account/team", icon: }, +]; + +/* TODO this could be a key in nested Path would be the link */ +const URL_MAP = { + support: "https://discord.com/invite/NAb6H3UTjK", + discussions: "https://github.com/bluewave-labs/checkmate/discussions", + docs: "https://bluewavelabs.gitbook.io/checkmate", + changelog: "https://github.com/bluewave-labs/checkmate/releases", +}; + +const PATH_MAP = { + monitors: "Dashboard", + pagespeed: "Dashboard", + infrastructure: "Dashboard", + account: "Account", + settings: "Settings", +}; + +/** + * @component + * Sidebar component serves as a sidebar containing a menu. + * + * @returns {JSX.Element} The JSX element representing the Sidebar component. + */ + +function Sidebar() { + const theme = useTheme(); + const navigate = useNavigate(); + const location = useLocation(); + const dispatch = useDispatch(); + const { t } = useTranslation(); + const authState = useSelector((state) => state.auth); + + const otherMenuItems = getOtherMenuItems(t); + const accountMenuItems = getAccountMenuItems(t); + const collapsed = useSelector((state) => state.ui.sidebar.collapsed); + const [open, setOpen] = useState({ Dashboard: false, Account: false, Other: false }); + const [anchorEl, setAnchorEl] = useState(null); + const [popup, setPopup] = useState(); + const { user } = useSelector((state) => state.auth); + + const sidebarRef = useRef(null); + const [sidebarReady, setSidebarReady] = useState(false); + const TRANSITION_DURATION = 200; + let menu = getMenu(t); + menu = menu.filter((item) => { + if (item.path === "logs") { + return user.role?.includes("admin") || user.role?.includes("superadmin"); + } + return true; + }); + + useEffect(() => { + if (!collapsed) { + setSidebarReady(false); + const timeout = setTimeout(() => { + setSidebarReady(true); + }, TRANSITION_DURATION); + return () => clearTimeout(timeout); + } else { + setSidebarReady(false); + } + }, [collapsed]); + + const renderAccountMenuItems = () => { + let filteredAccountMenuItems = [...accountMenuItems]; + + // If the user is in demo mode, remove the "Password" option + if (user.role?.includes("demo")) { + filteredAccountMenuItems = filteredAccountMenuItems.filter( + (item) => item.name !== "Password" + ); + } + + // If the user is NOT a superadmin, remove the "Team" option + if (user.role && !user.role.includes("superadmin")) { + filteredAccountMenuItems = filteredAccountMenuItems.filter( + (item) => item.name !== "Team" + ); + } + + return filteredAccountMenuItems.map((item) => ( + { + closePopup(); + navigate(item.path); + }} + sx={{ + gap: theme.spacing(2), + borderRadius: theme.shape.borderRadius, + pl: theme.spacing(4), + }} + > + {item.icon} + {item.name} + + )); + }; + + const openPopup = (event, id) => { + setAnchorEl(event.currentTarget); + setPopup(id); + }; + const closePopup = () => { + setAnchorEl(null); + }; + + /** + * Handles logging out the user + * + */ + const logout = async () => { + // Clear auth state + dispatch(clearAuthState()); + navigate("/login"); + }; + + useEffect(() => { + const matchedKey = Object.keys(PATH_MAP).find((key) => + location.pathname.includes(key) + ); + + if (matchedKey) { + setOpen((prev) => ({ ...prev, [PATH_MAP[matchedKey]]: true })); + } + }, [location]); + + const iconColor = theme.palette.primary.contrastTextTertiary; + const sidebarClassName = `${collapsed ? "collapsed" : "expanded"} ${sidebarReady ? "sidebar-ready" : ""}`; + + /* TODO refactor this, there are a some ternaries and comments in the return */ + return ( + + { + setOpen((prev) => + Object.fromEntries(Object.keys(prev).map((key) => [key, false])) + ); + dispatch(toggleSidebar()); + }} + > + {collapsed ? : } + + {/* TODO Alignment done using padding. Use single source of truth to that*/} + + {/* TODO Abstract logo into component */} + {/* TODO Turn logo into a link */} + navigate("/")} + sx={{ cursor: "pointer" }} + > + + C + + + {t("common.appName")} + + + + + + {menu.map((item) => { + return item.path ? ( + /* If item has a path */ + + navigate(`/${item.path}`)} + sx={{ + height: "37px", + gap: theme.spacing(4), + borderRadius: theme.shape.borderRadius, + px: theme.spacing(4), + pl: theme.spacing(5), + }} + > + {item.icon} + {item.name} + + + ) : collapsed ? ( + /* TODO Do we ever get here? If item does not have a path and collapsed state is true */ + + + openPopup(event, item.name)} + sx={{ + position: "relative", + gap: theme.spacing(4), + borderRadius: theme.shape.borderRadius, + px: theme.spacing(4), + }} + > + {item.icon} + {item.name} + + + + {item.nested.map((child) => { + if ( + child.name === "Team" && + authState.user?.role && + !authState.user.role.includes("superadmin") + ) { + return null; + } + + return ( + { + const url = URL_MAP[child.path]; + if (url) { + window.open(url, "_blank", "noreferrer"); + } else { + navigate(`/${child.path}`); + } + closePopup(); + }} + sx={{ + gap: theme.spacing(4), + opacity: 0.9, + /* TODO this has no effect? */ + "& svg": { + "& path": { + stroke: theme.palette.primary.contrastTextTertiary, + strokeWidth: 1.1, + }, + }, + }} + > + {child.icon} + {child.name} + + ); + })} + + + ) : ( + /* TODO Do we ever get here? If item does not have a path and collapsed state is false */ + + + setOpen((prev) => ({ + ...Object.fromEntries(Object.keys(prev).map((key) => [key, false])), + [item.name]: !prev[item.name], + })) + } + sx={{ + gap: theme.spacing(4), + borderRadius: theme.shape.borderRadius, + px: theme.spacing(4), + }} + > + {item.icon} + {item.name} + {open[`${item.name}`] ? : } + + + + {item.nested.map((child) => { + if ( + child.name === "Team" && + authState.user?.role && + !authState.user.role.includes("superadmin") + ) { + return null; + } + + return ( + { + const url = URL_MAP[child.path]; + if (url) { + window.open(url, "_blank", "noreferrer"); + } else { + navigate(`/${child.path}`); + } + }} + sx={{ + gap: theme.spacing(4), + borderRadius: theme.shape.borderRadius, + pl: theme.spacing(4), + "&::before": { + content: `""`, + position: "absolute", + top: 0, + left: "-7px", + height: "100%", + borderLeft: 1, + borderLeftColor: theme.palette.primary.lowContrast, + }, + "&:last-child::before": { + height: "50%", + }, + "&::after": { + content: `""`, + position: "absolute", + top: "45%", + left: "-8px", + height: "3px", + width: "3px", + borderRadius: "50%", + backgroundColor: theme.palette.primary.lowContrast, + }, + "&.selected-path::after": { + /* TODO what is this selector doing? */ + backgroundColor: theme.palette.primary.contrastTextTertiary, + transform: "scale(1.2)", + }, + }} + > + {child.icon} + {child.name} + + ); + })} + + + + ); + })} + + + + {!collapsed && } + + + {otherMenuItems.map((item) => { + return item.path ? ( + + { + const url = URL_MAP[item.path]; + if (url) { + window.open(url, "_blank", "noreferrer"); + } else { + navigate(`/${item.path}`); + } + }} + sx={{ + height: "37px", + gap: theme.spacing(4), + borderRadius: theme.shape.borderRadius, + px: theme.spacing(4), + pl: theme.spacing(5), + }} + > + {item.icon} + {item.name} {" "} + + + ) : null; + })} + + + + {collapsed ? ( + <> + + openPopup(event, "logout")} + sx={{ p: 0, "&:focus": { outline: "none" } }} + > + + + + + ) : ( + <> + + + + {authState.user?.firstName} {authState.user?.lastName} + + + {authState.user?.role?.includes("superadmin") + ? t("roles.superAdmin") + : authState.user?.role?.includes("admin") + ? t("roles.admin") + : authState.user?.role?.includes("user") + ? t("roles.teamMember") + : authState.user?.role?.includes("demo") + ? t("roles.demoUser") + : authState.user?.role} + + + + + + openPopup(event, "logout")} + > + + + + + + )} + + {collapsed && ( + + + + {authState.user?.firstName} {authState.user?.lastName} + + + {authState.user?.role} + + + + )} + {/* TODO Do we need two dividers? */} + {collapsed && } + {/* */} + {renderAccountMenuItems()} + + + {t("menu.logOut", "Log out")} + + + + + ); +} + +export default Sidebar; diff --git a/client/src/Components/Sidebar/logo.jsx b/client/src/Components/Sidebar/logo.jsx new file mode 100644 index 000000000..0a8a76f0c --- /dev/null +++ b/client/src/Components/Sidebar/logo.jsx @@ -0,0 +1,57 @@ +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import { useTheme } from "@mui/material/styles"; +import { useNavigate } from "react-router"; +import { useTranslation } from "react-i18next"; + +const Logo = () => { + const { t } = useTranslation(); + const theme = useTheme(); + const navigate = useNavigate(); + + return ( + + {/* TODO Abstract logo into component */} + {/* TODO Turn logo into a link */} + navigate("/")} + sx={{ cursor: "pointer" }} + > + + C + + + {t("common.appName")} + + + + ); +}; + +export default Logo; From b61a7ffc3c59cfef3ba36ae3e7a95e5c3ed670a4 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 31 Jul 2025 15:37:40 -0700 Subject: [PATCH 08/33] components --- .../src/Components/Sidebar/collapseButton.jsx | 48 ++++ .../Sidebar/components/authFooter.jsx | 260 ++++++++++++++++++ .../Components/Sidebar/components/navItem.jsx | 89 ++++++ client/src/Components/Sidebar/index.jsx | 176 ++++++------ client/src/Components/Sidebar/logo.jsx | 28 +- client/src/Utils/Theme/globalTheme.js | 11 +- 6 files changed, 510 insertions(+), 102 deletions(-) create mode 100644 client/src/Components/Sidebar/collapseButton.jsx create mode 100644 client/src/Components/Sidebar/components/authFooter.jsx create mode 100644 client/src/Components/Sidebar/components/navItem.jsx diff --git a/client/src/Components/Sidebar/collapseButton.jsx b/client/src/Components/Sidebar/collapseButton.jsx new file mode 100644 index 000000000..551bcf25d --- /dev/null +++ b/client/src/Components/Sidebar/collapseButton.jsx @@ -0,0 +1,48 @@ +import IconButton from "@mui/material/IconButton"; +import ArrowRight from "../../assets/icons/right-arrow.svg?react"; +import ArrowLeft from "../../assets/icons/left-arrow.svg?react"; +import { useTheme } from "@mui/material/styles"; +import { useDispatch } from "react-redux"; +import { toggleSidebar } from "../../Features/UI/uiSlice"; + +const CollapseButton = ({ collapsed, setOpen }) => { + const theme = useTheme(); + const dispatch = useDispatch(); + return ( + { + setOpen((prev) => + Object.fromEntries(Object.keys(prev).map((key) => [key, false])) + ); + dispatch(toggleSidebar()); + }} + > + {collapsed ? : } + + ); +}; + +export default CollapseButton; diff --git a/client/src/Components/Sidebar/components/authFooter.jsx b/client/src/Components/Sidebar/components/authFooter.jsx new file mode 100644 index 000000000..7ff2f36ef --- /dev/null +++ b/client/src/Components/Sidebar/components/authFooter.jsx @@ -0,0 +1,260 @@ +import Stack from "@mui/material/Stack"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import Tooltip from "@mui/material/Tooltip"; +import IconButton from "@mui/material/IconButton"; +import Avatar from "../../Avatar"; +import ThemeSwitch from "../../ThemeSwitch"; +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 { useTheme } from "@emotion/react"; +import { useSelector } from "react-redux"; +import { useTranslation } from "react-i18next"; +import { useState } from "react"; +import { useNavigate } from "react-router"; +import { clearAuthState } from "../../../Features/Auth/authSlice"; +import { useDispatch } from "react-redux"; + +const AuthFooter = ({ collapsed, accountMenuItems }) => { + const { t } = useTranslation(); + const theme = useTheme(); + const authState = useSelector((state) => state.auth); + const navigate = useNavigate(); + const dispatch = useDispatch(); + + const [anchorEl, setAnchorEl] = useState(null); + const [popup, setPopup] = useState(); + + const openPopup = (event, id) => { + setAnchorEl(event.currentTarget); + setPopup(id); + }; + + const closePopup = () => { + setAnchorEl(null); + }; + + const logout = async () => { + // Clear auth state + dispatch(clearAuthState()); + navigate("/login"); + }; + const renderAccountMenuItems = (user, items) => { + let filteredAccountMenuItems = [...items]; + + // If the user is in demo mode, remove the "Password" option + if (user.role?.includes("demo")) { + filteredAccountMenuItems = filteredAccountMenuItems.filter( + (item) => item.name !== "Password" + ); + } + + // If the user is NOT a superadmin, remove the "Team" option + if (user.role && !user.role.includes("superadmin")) { + filteredAccountMenuItems = filteredAccountMenuItems.filter( + (item) => item.name !== "Team" + ); + } + + return filteredAccountMenuItems.map((item) => ( + { + closePopup(); + navigate(item.path); + }} + sx={{ + gap: theme.spacing(2), + borderRadius: theme.shape.borderRadius, + pl: theme.spacing(4), + }} + > + {item.icon} + {item.name} + + )); + }; + return ( + + <> + + + + + {authState.user?.firstName} {authState.user?.lastName} + + + {authState.user?.role?.includes("superadmin") + ? t("roles.superAdmin") + : authState.user?.role?.includes("admin") + ? t("roles.admin") + : authState.user?.role?.includes("user") + ? t("roles.teamMember") + : authState.user?.role?.includes("demo") + ? t("roles.demoUser") + : authState.user?.role} + + + + + + openPopup(event, "logout")} + > + + + + + + + + {collapsed && ( + + + + {authState.user?.firstName} {authState.user?.lastName} + + + {authState.user?.role} + + + + )} + {/* TODO Do we need two dividers? */} + {collapsed && } + {/* */} + {renderAccountMenuItems(authState.user, accountMenuItems)} + + + {t("menu.logOut", "Log out")} + + + + ); +}; + +export default AuthFooter; diff --git a/client/src/Components/Sidebar/components/navItem.jsx b/client/src/Components/Sidebar/components/navItem.jsx new file mode 100644 index 000000000..f43b1bf1e --- /dev/null +++ b/client/src/Components/Sidebar/components/navItem.jsx @@ -0,0 +1,89 @@ +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 "@emotion/react"; +import { useNavigate } from "react-router"; + +const NavItem = ({ item, collapsed, selected, onClick }) => { + const theme = useTheme(); + const navigate = useNavigate(); + + return ( + + + + {item.icon} + + + + {item.name} + + + + + ); +}; + +export default NavItem; diff --git a/client/src/Components/Sidebar/index.jsx b/client/src/Components/Sidebar/index.jsx index cad7d3b7d..ac4a68c0f 100644 --- a/client/src/Components/Sidebar/index.jsx +++ b/client/src/Components/Sidebar/index.jsx @@ -1,21 +1,16 @@ -import IconButton from "@mui/material/IconButton"; import Stack from "@mui/material/Stack"; -import ArrowRight from "../../assets/icons/right-arrow.svg?react"; -import ArrowLeft from "../../assets/icons/left-arrow.svg?react"; -import List from "@mui/material/List"; -import ListItemButton from "@mui/material/ListItemButton"; -import ListItemIcon from "@mui/material/ListItemIcon"; -import ListItemText from "@mui/material/ListItemText"; -import Tooltip from "@mui/material/Tooltip"; -import Logo from "./logo"; -import ThemeSwitch from "../ThemeSwitch"; -import Avatar from "../Avatar"; +import List from "@mui/material/List"; +import Logo from "./logo"; +import CollapseButton from "./collapseButton"; +import Divider from "@mui/material/Divider"; +import NavItem from "./components/navItem"; +import AuthFooter from "./components/authFooter"; + import StarPrompt from "../StarPrompt"; import LockSvg from "../../assets/icons/lock.svg?react"; import UserSvg from "../../assets/icons/user.svg?react"; import TeamSvg from "../../assets/icons/user-two.svg?react"; -import LogoutSvg from "../../assets/icons/logout.svg?react"; import Support from "../../assets/icons/support.svg?react"; import Maintenance from "../../assets/icons/maintenance.svg?react"; import Monitors from "../../assets/icons/monitors.svg?react"; @@ -25,7 +20,6 @@ import PageSpeed from "../../assets/icons/page-speed.svg?react"; import Settings from "../../assets/icons/settings.svg?react"; import ArrowDown from "../../assets/icons/down-arrow.svg?react"; import ArrowUp from "../../assets/icons/up-arrow.svg?react"; -import DotsVertical from "../../assets/icons/dots-vertical.svg?react"; import ChangeLog from "../../assets/icons/changeLog.svg?react"; import Docs from "../../assets/icons/docs.svg?react"; import StatusPages from "../../assets/icons/status-pages.svg?react"; @@ -35,12 +29,18 @@ import Logs from "../../assets/icons/logs.svg?react"; // Utils import { useTheme } from "@mui/material/styles"; -import { useDispatch, useSelector } from "react-redux"; -import { toggleSidebar } from "../../Features/UI/uiSlice"; +import { useSelector } from "react-redux"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router"; +const URL_MAP = { + support: "https://discord.com/invite/NAb6H3UTjK", + discussions: "https://github.com/bluewave-labs/checkmate/discussions", + docs: "https://bluewavelabs.gitbook.io/checkmate", + changelog: "https://github.com/bluewave-labs/checkmate/releases", +}; + const getMenu = (t) => [ { name: t("menu.uptime"), path: "uptime", icon: }, { name: t("menu.pagespeed"), path: "pagespeed", icon: }, @@ -64,9 +64,25 @@ const getMenu = (t) => [ }, ]; +const getOtherMenuItems = (t) => [ + { name: t("menu.support"), path: "support", icon: }, + { + name: t("menu.discussions"), + path: "discussions", + icon: , + }, + { name: t("menu.docs"), path: "docs", icon: }, + { name: t("menu.changelog"), path: "changelog", icon: }, +]; + +const getAccountMenuItems = (t) => [ + { name: t("menu.profile"), path: "account/profile", icon: }, + { name: t("menu.password"), path: "account/password", icon: }, + { name: t("menu.team"), path: "account/team", icon: }, +]; + const Sidebar = () => { const theme = useTheme(); - const dispatch = useDispatch(); const { t } = useTranslation(); const navigate = useNavigate(); // Redux state @@ -76,100 +92,82 @@ const Sidebar = () => { const [open, setOpen] = useState({ Dashboard: false, Account: false, Other: false }); const menu = getMenu(t); - console.log(collapsed); + const otherMenuItems = getOtherMenuItems(t); + const accountMenuItems = getAccountMenuItems(t); + return ( - { - setOpen((prev) => - Object.fromEntries(Object.keys(prev).map((key) => [key, false])) - ); - dispatch(toggleSidebar()); - }} - > - {collapsed ? : } - - + + {menu.map((item) => { - return item.path ? ( - /* If item has a path */ - - navigate(`/${item.path}`)} - sx={{ - height: "37px", - gap: theme.spacing(4), - borderRadius: theme.shape.borderRadius, - px: theme.spacing(4), - pl: theme.spacing(5), - }} - > - {item.icon} - {!collapsed && {item.name}} - - - ) : null; + item={item} + collapsed={collapsed} + selected={selected} + onClick={() => navigate(`/${item.path}`)} + /> + ); })} + {!collapsed && } + + {otherMenuItems.map((item) => { + const selected = location.pathname.startsWith(`/${item.path}`); + + return ( + { + const url = URL_MAP[item.path]; + if (url) { + window.open(url, "_blank", "noreferrer"); + } else { + navigate(`/${item.path}`); + } + }} + /> + ); + })} + + + ); }; diff --git a/client/src/Components/Sidebar/logo.jsx b/client/src/Components/Sidebar/logo.jsx index 0a8a76f0c..0d7adf54f 100644 --- a/client/src/Components/Sidebar/logo.jsx +++ b/client/src/Components/Sidebar/logo.jsx @@ -1,10 +1,11 @@ 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"; -const Logo = () => { +const Logo = ({ collapsed }) => { const { t } = useTranslation(); const theme = useTheme(); const navigate = useNavigate(); @@ -35,20 +36,31 @@ const Logo = () => { sx={{ position: "relative", backgroundColor: theme.palette.accent.main, - color: theme.palette.accent.contrastText, borderRadius: theme.shape.borderRadius, userSelect: "none", }} > C - - {t("common.appName")} - + {" "} + + {t("common.appName")} + + ); diff --git a/client/src/Utils/Theme/globalTheme.js b/client/src/Utils/Theme/globalTheme.js index ec292142d..64ac46fd1 100644 --- a/client/src/Utils/Theme/globalTheme.js +++ b/client/src/Utils/Theme/globalTheme.js @@ -233,16 +233,16 @@ const baseTheme = (palette) => ({ }, MuiList: { styleOverrides: { - root: { + root: ({ theme }) => ({ padding: 0, - }, + }), }, }, MuiListItemButton: { styleOverrides: { - root: { - transition: "none", - }, + root: ({ theme }) => ({ + transition: "background-color .3s", + }), }, }, MuiListItemText: { @@ -279,6 +279,7 @@ const baseTheme = (palette) => ({ }), }, }, + MuiTableHead: { styleOverrides: { root: ({ theme }) => ({ From 48990b860f678f0334dce302921c2ba4552dd190 Mon Sep 17 00:00:00 2001 From: Mohansai Mallineni Date: Fri, 1 Aug 2025 21:09:51 +0530 Subject: [PATCH 09/33] Handled styling of new icons --- .../src/Pages/Incidents/Components/StatusBoxes/StatusBox.css | 5 +++++ .../src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.css diff --git a/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.css b/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.css new file mode 100644 index 000000000..bff2c0477 --- /dev/null +++ b/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.css @@ -0,0 +1,5 @@ +svg { + width: 20px; + height: 20px; + opacity: 0.9; +} \ No newline at end of file diff --git a/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx b/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx index adf4faa8b..54c89ee3d 100644 --- a/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx +++ b/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx @@ -6,7 +6,7 @@ import AlertIcon from "../../../../assets/icons/alert-icon.svg?react"; import CheckIcon from "../../../../assets/icons/check-icon.svg?react"; import CloseIcon from "../../../../assets/icons/close-icon.svg?react"; import WarningIcon from "../../../../assets/icons/warning-icon.svg?react"; - +import "./StatusBox.css"; const StatusBox = ({ title, value, status }) => { const theme = useTheme(); let sharedStyles = { From 8274d4ffbe4a8ec9f94f8cf71a54cb74bca5089b Mon Sep 17 00:00:00 2001 From: Mohansai Mallineni Date: Fri, 1 Aug 2025 21:11:46 +0530 Subject: [PATCH 10/33] Handled styling of new icons --- .../src/Pages/Incidents/Components/StatusBoxes/StatusBox.css | 5 +++++ .../src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.css diff --git a/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.css b/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.css new file mode 100644 index 000000000..6a260491f --- /dev/null +++ b/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.css @@ -0,0 +1,5 @@ +svg { + width: 20px; + height: 20px; + opacity: 0.9; +} diff --git a/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx b/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx index adf4faa8b..54c89ee3d 100644 --- a/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx +++ b/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx @@ -6,7 +6,7 @@ import AlertIcon from "../../../../assets/icons/alert-icon.svg?react"; import CheckIcon from "../../../../assets/icons/check-icon.svg?react"; import CloseIcon from "../../../../assets/icons/close-icon.svg?react"; import WarningIcon from "../../../../assets/icons/warning-icon.svg?react"; - +import "./StatusBox.css"; const StatusBox = ({ title, value, status }) => { const theme = useTheme(); let sharedStyles = { From ebfb25ebc067fd4e440a3881be288b7cdbde1e76 Mon Sep 17 00:00:00 2001 From: Mohansai Mallineni Date: Fri, 1 Aug 2025 22:09:15 +0530 Subject: [PATCH 11/33] Handled styling of new icons --- .../Pages/Incidents/Components/StatusBoxes/StatusBox.css | 5 ----- .../Pages/Incidents/Components/StatusBoxes/StatusBox.jsx | 8 ++++++-- 2 files changed, 6 insertions(+), 7 deletions(-) delete mode 100644 client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.css diff --git a/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.css b/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.css deleted file mode 100644 index 6a260491f..000000000 --- a/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.css +++ /dev/null @@ -1,5 +0,0 @@ -svg { - width: 20px; - height: 20px; - opacity: 0.9; -} diff --git a/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx b/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx index f925cf4c2..7b20e3d51 100644 --- a/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx +++ b/client/src/Pages/Incidents/Components/StatusBoxes/StatusBox.jsx @@ -6,14 +6,18 @@ import AlertIcon from "../../../../assets/icons/alert-icon.svg?react"; import CheckIcon from "../../../../assets/icons/check-icon.svg?react"; import CloseIcon from "../../../../assets/icons/close-icon.svg?react"; import WarningIcon from "../../../../assets/icons/warning-icon.svg?react"; -import "./StatusBox.css"; const StatusBox = ({ title, value, status }) => { const theme = useTheme(); let sharedStyles = { position: "absolute", right: 8, - "& svg path": { stroke: theme.palette.primary.contrastTextTertiary }, + "& svg": { + width: 20, + height: 20, + opacity: 0.9, + "& path": { stroke: theme.palette.primary.contrastTextTertiary, strokeWidth: 1.7 }, + }, }; let color; From 2d01205f70839874d07a6bb78cd0d84ecf6ad2d3 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 1 Aug 2025 12:39:35 -0700 Subject: [PATCH 12/33] auth footer styling --- .../Sidebar/components/authFooter.jsx | 140 +++++++++--------- .../Sidebar/{index.bak => index.css} | 0 2 files changed, 70 insertions(+), 70 deletions(-) rename client/src/Components/Sidebar/{index.bak => index.css} (100%) diff --git a/client/src/Components/Sidebar/components/authFooter.jsx b/client/src/Components/Sidebar/components/authFooter.jsx index 7ff2f36ef..e792cec12 100644 --- a/client/src/Components/Sidebar/components/authFooter.jsx +++ b/client/src/Components/Sidebar/components/authFooter.jsx @@ -88,82 +88,82 @@ const AuthFooter = ({ collapsed, accountMenuItems }) => { gap={theme.spacing(2)} borderRadius={theme.shape.borderRadius} > - <> - - + + + - - - {authState.user?.firstName} {authState.user?.lastName} - - - {authState.user?.role?.includes("superadmin") - ? t("roles.superAdmin") - : authState.user?.role?.includes("admin") - ? t("roles.admin") - : authState.user?.role?.includes("user") - ? t("roles.teamMember") - : authState.user?.role?.includes("demo") - ? t("roles.demoUser") - : authState.user?.role} - - - - - - + + {authState.user?.role?.includes("superadmin") + ? t("roles.superAdmin") + : authState.user?.role?.includes("admin") + ? t("roles.admin") + : authState.user?.role?.includes("user") + ? t("roles.teamMember") + : authState.user?.role?.includes("demo") + ? t("roles.demoUser") + : authState.user?.role} + + + + + + openPopup(event, "logout")} - > - - - - - - + "& svg": { + width: "22px", + height: "22px", + }, + "& svg path": { + /* Vertical three dots */ + stroke: theme.palette.primary.contrastTextTertiary, + }, + }} + onClick={(event) => openPopup(event, "logout")} + > + + + + Date: Fri, 1 Aug 2025 14:10:20 -0700 Subject: [PATCH 13/33] simplify layouts --- .../src/Components/Sidebar/collapseButton.jsx | 5 +- .../Sidebar/components/authFooter.jsx | 138 ++++++++++-------- client/src/Components/Sidebar/index.jsx | 12 +- client/src/Components/Sidebar/logo.jsx | 79 +++++----- 4 files changed, 113 insertions(+), 121 deletions(-) diff --git a/client/src/Components/Sidebar/collapseButton.jsx b/client/src/Components/Sidebar/collapseButton.jsx index 551bcf25d..b8f088669 100644 --- a/client/src/Components/Sidebar/collapseButton.jsx +++ b/client/src/Components/Sidebar/collapseButton.jsx @@ -5,7 +5,7 @@ import { useTheme } from "@mui/material/styles"; import { useDispatch } from "react-redux"; import { toggleSidebar } from "../../Features/UI/uiSlice"; -const CollapseButton = ({ collapsed, setOpen }) => { +const CollapseButton = ({ collapsed }) => { const theme = useTheme(); const dispatch = useDispatch(); return ( @@ -34,9 +34,6 @@ const CollapseButton = ({ collapsed, setOpen }) => { }, }} onClick={() => { - setOpen((prev) => - Object.fromEntries(Object.keys(prev).map((key) => [key, false])) - ); dispatch(toggleSidebar()); }} > diff --git a/client/src/Components/Sidebar/components/authFooter.jsx b/client/src/Components/Sidebar/components/authFooter.jsx index e792cec12..dbc1ccfb0 100644 --- a/client/src/Components/Sidebar/components/authFooter.jsx +++ b/client/src/Components/Sidebar/components/authFooter.jsx @@ -87,82 +87,94 @@ const AuthFooter = ({ collapsed, accountMenuItems }) => { px={theme.spacing(8)} gap={theme.spacing(2)} borderRadius={theme.shape.borderRadius} + boxSizing={"border-box"} > - - {authState.user?.firstName} {authState.user?.lastName} - - - {authState.user?.role?.includes("superadmin") - ? t("roles.superAdmin") - : authState.user?.role?.includes("admin") - ? t("roles.admin") - : authState.user?.role?.includes("user") - ? t("roles.teamMember") - : authState.user?.role?.includes("demo") - ? t("roles.demoUser") - : authState.user?.role} - - - - - - openPopup(event, "logout")} > - - - + {authState.user?.firstName} {authState.user?.lastName} + + + {authState.user?.role?.includes("superadmin") + ? t("roles.superAdmin") + : authState.user?.role?.includes("admin") + ? t("roles.admin") + : authState.user?.role?.includes("user") + ? t("roles.teamMember") + : authState.user?.role?.includes("demo") + ? t("roles.demoUser") + : authState.user?.role} + + + + + + openPopup(event, "logout")} + > + + + + { // Redux state const collapsed = useSelector((state) => state.ui.sidebar.collapsed); - // Local state - const [open, setOpen] = useState({ Dashboard: false, Account: false, Other: false }); - const menu = getMenu(t); const otherMenuItems = getOtherMenuItems(t); const accountMenuItems = getAccountMenuItems(t); @@ -108,10 +102,7 @@ const Sidebar = () => { transition: "width 650ms cubic-bezier(0.36, -0.01, 0, 0.77)", }} > - + { sx={{ px: theme.spacing(6), height: "100%", - /* overflow: "hidden", */ }} > {menu.map((item) => { diff --git a/client/src/Components/Sidebar/logo.jsx b/client/src/Components/Sidebar/logo.jsx index 0d7adf54f..e6aa660fb 100644 --- a/client/src/Components/Sidebar/logo.jsx +++ b/client/src/Components/Sidebar/logo.jsx @@ -15,53 +15,46 @@ const Logo = ({ collapsed }) => { 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" }} > - {/* TODO Abstract logo into component */} - {/* TODO Turn logo into a link */} - navigate("/")} - sx={{ cursor: "pointer" }} + - + + {" "} + - C - - - {" "} - - {t("common.appName")} - - - + {t("common.appName")} + + ); }; From b4f42bb7d495b53c862c85e86e29c6f4c4cbfca1 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 1 Aug 2025 14:58:30 -0700 Subject: [PATCH 14/33] fix arrow icons --- client/src/Components/ArrowLeft/index.jsx | 36 +++++++++++ client/src/Components/ArrowRight/index.jsx | 28 +++++++++ client/src/Components/Breadcrumbs/index.jsx | 3 +- .../src/Components/Sidebar/collapseButton.jsx | 34 ++++++---- .../Sidebar/components/collapseButton.jsx | 59 ++++++++++++++++++ .../Components/Sidebar/components/logo.jsx | 62 +++++++++++++++++++ .../Table/TablePagination/Actions/index.jsx | 10 ++- client/src/assets/icons/left-arrow-double.svg | 5 +- client/src/assets/icons/left-arrow-long.svg | 5 +- client/src/assets/icons/left-arrow.svg | 5 +- .../src/assets/icons/right-arrow-double.svg | 5 +- client/src/assets/icons/right-arrow.svg | 5 +- 12 files changed, 227 insertions(+), 30 deletions(-) create mode 100644 client/src/Components/ArrowLeft/index.jsx create mode 100644 client/src/Components/ArrowRight/index.jsx create mode 100644 client/src/Components/Sidebar/components/collapseButton.jsx create mode 100644 client/src/Components/Sidebar/components/logo.jsx diff --git a/client/src/Components/ArrowLeft/index.jsx b/client/src/Components/ArrowLeft/index.jsx new file mode 100644 index 000000000..cc7e0b078 --- /dev/null +++ b/client/src/Components/ArrowLeft/index.jsx @@ -0,0 +1,36 @@ +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 PropTypes from "prop-types"; + +const ArrowLeft = ({ type, color = "#667085", ...props }) => { + console.log(color); + if (type === "double") { + return ( + + ); + } else if (type === "long") { + return ( + + ); + } else { + return ( + + ); + } +}; + +ArrowLeft.propTypes = { + color: PropTypes.string, + type: PropTypes.oneOf(["double", "long", "default"]), +}; +export default ArrowLeft; diff --git a/client/src/Components/ArrowRight/index.jsx b/client/src/Components/ArrowRight/index.jsx new file mode 100644 index 000000000..335bc19f2 --- /dev/null +++ b/client/src/Components/ArrowRight/index.jsx @@ -0,0 +1,28 @@ +import RightArrow from "../../assets/icons/right-arrow.svg?react"; +import RightArrowDouble from "../../assets/icons/right-arrow-double.svg?react"; +import PropTypes from "prop-types"; + +const ArrowRight = ({ type, color = "#667085", ...props }) => { + if (type === "double") { + return ( + + ); + } else { + return ( + + ); + } +}; + +ArrowRight.propTypes = { + type: PropTypes.oneOf(["double", "default"]), + color: PropTypes.string, +}; + +export default ArrowRight; diff --git a/client/src/Components/Breadcrumbs/index.jsx b/client/src/Components/Breadcrumbs/index.jsx index bf04afa2e..f1af89790 100644 --- a/client/src/Components/Breadcrumbs/index.jsx +++ b/client/src/Components/Breadcrumbs/index.jsx @@ -2,8 +2,7 @@ import PropTypes from "prop-types"; import { Box, Breadcrumbs as MUIBreadcrumbs } from "@mui/material"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; -import ArrowRight from "../../assets/icons/right-arrow.svg?react"; - +import ArrowRight from "../ArrowRight"; import "./index.css"; /** diff --git a/client/src/Components/Sidebar/collapseButton.jsx b/client/src/Components/Sidebar/collapseButton.jsx index b8f088669..1cd9fc618 100644 --- a/client/src/Components/Sidebar/collapseButton.jsx +++ b/client/src/Components/Sidebar/collapseButton.jsx @@ -1,13 +1,27 @@ import IconButton from "@mui/material/IconButton"; -import ArrowRight from "../../assets/icons/right-arrow.svg?react"; -import ArrowLeft from "../../assets/icons/left-arrow.svg?react"; +import ArrowRight from "../ArrowRight"; +import ArrowLeft from "../ArrowLeft"; import { useTheme } from "@mui/material/styles"; import { useDispatch } from "react-redux"; import { toggleSidebar } from "../../Features/UI/uiSlice"; +import PropTypes from "prop-types"; const CollapseButton = ({ collapsed }) => { const theme = useTheme(); const dispatch = useDispatch(); + const arrowIcon = collapsed ? ( + + ) : ( + + ); return ( { right: 0, transform: `translate(50%, 0)`, backgroundColor: theme.palette.tertiary.main, - border: 1, - borderColor: theme.palette.primary.lowContrast, + border: `1px solid ${theme.palette.primary.lowContrast}`, p: theme.spacing(2.5), - "& svg": { - width: theme.spacing(8), - height: theme.spacing(8), - "& path": { - stroke: theme.palette.primary.contrastTextSecondary, - }, - }, + "&:focus": { outline: "none" }, "&:hover": { backgroundColor: theme.palette.primary.lowContrast, @@ -37,9 +44,12 @@ const CollapseButton = ({ collapsed }) => { dispatch(toggleSidebar()); }} > - {collapsed ? : } + {arrowIcon} ); }; +CollapseButton.propTypes = { + collapsed: PropTypes.bool.isRequired, +}; export default CollapseButton; diff --git a/client/src/Components/Sidebar/components/collapseButton.jsx b/client/src/Components/Sidebar/components/collapseButton.jsx new file mode 100644 index 000000000..bcae710f0 --- /dev/null +++ b/client/src/Components/Sidebar/components/collapseButton.jsx @@ -0,0 +1,59 @@ +import IconButton from "@mui/material/IconButton"; +import ArrowRight from "../../ArrowRight"; +import ArrowLeft from "../../ArrowLeft"; +import { useTheme } from "@mui/material/styles"; +import { useDispatch } from "react-redux"; +import { toggleSidebar } from "../../../Features/UI/uiSlice"; + +import PropTypes from "prop-types"; + +const CollapseButton = ({ collapsed }) => { + const theme = useTheme(); + const dispatch = useDispatch(); + + const arrowIcon = collapsed ? ( + + ) : ( + + ); + + return ( + { + dispatch(toggleSidebar()); + }} + > + {arrowIcon} + + ); +}; + +CollapseButton.propTypes = { + collapsed: PropTypes.bool, +}; + +export default CollapseButton; diff --git a/client/src/Components/Sidebar/components/logo.jsx b/client/src/Components/Sidebar/components/logo.jsx new file mode 100644 index 000000000..e6aa660fb --- /dev/null +++ b/client/src/Components/Sidebar/components/logo.jsx @@ -0,0 +1,62 @@ +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"; + +const Logo = ({ collapsed }) => { + const { t } = useTranslation(); + const theme = useTheme(); + const navigate = useNavigate(); + + return ( + navigate("/")} + sx={{ cursor: "pointer" }} + > + + C + + + {" "} + + {t("common.appName")} + + + + ); +}; + +export default Logo; diff --git a/client/src/Components/Table/TablePagination/Actions/index.jsx b/client/src/Components/Table/TablePagination/Actions/index.jsx index 1b654fe9b..dec72446b 100644 --- a/client/src/Components/Table/TablePagination/Actions/index.jsx +++ b/client/src/Components/Table/TablePagination/Actions/index.jsx @@ -1,9 +1,7 @@ import PropTypes from "prop-types"; import { Box, Button } from "@mui/material"; -import LeftArrowDouble from "../../../../assets/icons/left-arrow-double.svg?react"; -import RightArrowDouble from "../../../../assets/icons/right-arrow-double.svg?react"; -import LeftArrow from "../../../../assets/icons/left-arrow.svg?react"; -import RightArrow from "../../../../assets/icons/right-arrow.svg?react"; +import LeftArrow from "../../../ArrowLeft"; +import RightArrow from "../../../ArrowRight"; import { useTheme } from "@emotion/react"; TablePaginationActions.propTypes = { @@ -50,7 +48,7 @@ function TablePaginationActions({ count, page, rowsPerPage, onPageChange }) { disabled={page === 0} aria-label="first page" > - + ); diff --git a/client/src/assets/icons/left-arrow-double.svg b/client/src/assets/icons/left-arrow-double.svg index 6001a3ead..4cb2a5186 100644 --- a/client/src/assets/icons/left-arrow-double.svg +++ b/client/src/assets/icons/left-arrow-double.svg @@ -1,3 +1,4 @@ - - + + \ No newline at end of file diff --git a/client/src/assets/icons/left-arrow-long.svg b/client/src/assets/icons/left-arrow-long.svg index db9b827ed..56a774730 100644 --- a/client/src/assets/icons/left-arrow-long.svg +++ b/client/src/assets/icons/left-arrow-long.svg @@ -1,3 +1,4 @@ - - + + \ No newline at end of file diff --git a/client/src/assets/icons/left-arrow.svg b/client/src/assets/icons/left-arrow.svg index 8b38c408a..649d1681c 100644 --- a/client/src/assets/icons/left-arrow.svg +++ b/client/src/assets/icons/left-arrow.svg @@ -1,3 +1,4 @@ - - + + \ No newline at end of file diff --git a/client/src/assets/icons/right-arrow-double.svg b/client/src/assets/icons/right-arrow-double.svg index 4235134d7..6ad4882ad 100644 --- a/client/src/assets/icons/right-arrow-double.svg +++ b/client/src/assets/icons/right-arrow-double.svg @@ -1,3 +1,4 @@ - - + + \ No newline at end of file diff --git a/client/src/assets/icons/right-arrow.svg b/client/src/assets/icons/right-arrow.svg index 6b02062f2..77d022798 100644 --- a/client/src/assets/icons/right-arrow.svg +++ b/client/src/assets/icons/right-arrow.svg @@ -1,3 +1,4 @@ - - + + \ No newline at end of file From 800f988979fcc10abcf3fa89fad394a104c9ed17 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 1 Aug 2025 15:01:30 -0700 Subject: [PATCH 15/33] move components to component folder --- .../src/Components/Sidebar/collapseButton.jsx | 55 ---------------- .../Sidebar/components/collapseButton.jsx | 10 +-- client/src/Components/Sidebar/index.jsx | 4 +- client/src/Components/Sidebar/logo.jsx | 62 ------------------- 4 files changed, 5 insertions(+), 126 deletions(-) delete mode 100644 client/src/Components/Sidebar/collapseButton.jsx delete mode 100644 client/src/Components/Sidebar/logo.jsx diff --git a/client/src/Components/Sidebar/collapseButton.jsx b/client/src/Components/Sidebar/collapseButton.jsx deleted file mode 100644 index 1cd9fc618..000000000 --- a/client/src/Components/Sidebar/collapseButton.jsx +++ /dev/null @@ -1,55 +0,0 @@ -import IconButton from "@mui/material/IconButton"; -import ArrowRight from "../ArrowRight"; -import ArrowLeft from "../ArrowLeft"; -import { useTheme } from "@mui/material/styles"; -import { useDispatch } from "react-redux"; -import { toggleSidebar } from "../../Features/UI/uiSlice"; -import PropTypes from "prop-types"; - -const CollapseButton = ({ collapsed }) => { - const theme = useTheme(); - const dispatch = useDispatch(); - const arrowIcon = collapsed ? ( - - ) : ( - - ); - return ( - { - dispatch(toggleSidebar()); - }} - > - {arrowIcon} - - ); -}; - -CollapseButton.propTypes = { - collapsed: PropTypes.bool.isRequired, -}; -export default CollapseButton; diff --git a/client/src/Components/Sidebar/components/collapseButton.jsx b/client/src/Components/Sidebar/components/collapseButton.jsx index bcae710f0..e7c382e5a 100644 --- a/client/src/Components/Sidebar/components/collapseButton.jsx +++ b/client/src/Components/Sidebar/components/collapseButton.jsx @@ -4,27 +4,24 @@ import ArrowLeft from "../../ArrowLeft"; import { useTheme } from "@mui/material/styles"; import { useDispatch } from "react-redux"; import { toggleSidebar } from "../../../Features/UI/uiSlice"; - import PropTypes from "prop-types"; const CollapseButton = ({ collapsed }) => { const theme = useTheme(); const dispatch = useDispatch(); - const arrowIcon = collapsed ? ( ) : ( ); - return ( { }; CollapseButton.propTypes = { - collapsed: PropTypes.bool, + collapsed: PropTypes.bool.isRequired, }; - export default CollapseButton; diff --git a/client/src/Components/Sidebar/index.jsx b/client/src/Components/Sidebar/index.jsx index e8ce7ff78..bbecb075d 100644 --- a/client/src/Components/Sidebar/index.jsx +++ b/client/src/Components/Sidebar/index.jsx @@ -1,8 +1,8 @@ import Stack from "@mui/material/Stack"; import List from "@mui/material/List"; -import Logo from "./logo"; -import CollapseButton from "./collapseButton"; +import Logo from "./components/logo"; +import CollapseButton from "./components/collapseButton"; import Divider from "@mui/material/Divider"; import NavItem from "./components/navItem"; import AuthFooter from "./components/authFooter"; diff --git a/client/src/Components/Sidebar/logo.jsx b/client/src/Components/Sidebar/logo.jsx deleted file mode 100644 index e6aa660fb..000000000 --- a/client/src/Components/Sidebar/logo.jsx +++ /dev/null @@ -1,62 +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"; - -const Logo = ({ collapsed }) => { - const { t } = useTranslation(); - const theme = useTheme(); - const navigate = useNavigate(); - - return ( - navigate("/")} - sx={{ cursor: "pointer" }} - > - - C - - - {" "} - - {t("common.appName")} - - - - ); -}; - -export default Logo; From fe9bf6375f0fcbea0984a2066c93cebd292e6ee2 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 1 Aug 2025 15:14:03 -0700 Subject: [PATCH 16/33] fix sticky sidebar --- .../src/Components/Sidebar/components/authFooter.jsx | 2 +- client/src/Components/Sidebar/index.jsx | 10 ++++++++-- client/src/index.css | 2 ++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/client/src/Components/Sidebar/components/authFooter.jsx b/client/src/Components/Sidebar/components/authFooter.jsx index dbc1ccfb0..224bdbb34 100644 --- a/client/src/Components/Sidebar/components/authFooter.jsx +++ b/client/src/Components/Sidebar/components/authFooter.jsx @@ -81,7 +81,7 @@ const AuthFooter = ({ collapsed, accountMenuItems }) => { return ( { return ( Date: Fri, 1 Aug 2025 15:22:44 -0700 Subject: [PATCH 17/33] remove extra popup state --- .../Components/Sidebar/components/authFooter.jsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/client/src/Components/Sidebar/components/authFooter.jsx b/client/src/Components/Sidebar/components/authFooter.jsx index 224bdbb34..616754c67 100644 --- a/client/src/Components/Sidebar/components/authFooter.jsx +++ b/client/src/Components/Sidebar/components/authFooter.jsx @@ -18,6 +18,7 @@ import { useState } from "react"; import { useNavigate } from "react-router"; import { clearAuthState } from "../../../Features/Auth/authSlice"; import { useDispatch } from "react-redux"; +import PropTypes from "prop-types"; const AuthFooter = ({ collapsed, accountMenuItems }) => { const { t } = useTranslation(); @@ -27,11 +28,9 @@ const AuthFooter = ({ collapsed, accountMenuItems }) => { const dispatch = useDispatch(); const [anchorEl, setAnchorEl] = useState(null); - const [popup, setPopup] = useState(); - const openPopup = (event, id) => { + const openPopup = (event) => { setAnchorEl(event.currentTarget); - setPopup(id); }; const closePopup = () => { @@ -169,7 +168,7 @@ const AuthFooter = ({ collapsed, accountMenuItems }) => { stroke: theme.palette.primary.contrastTextTertiary, }, }} - onClick={(event) => openPopup(event, "logout")} + onClick={(event) => openPopup(event)} > @@ -179,7 +178,7 @@ const AuthFooter = ({ collapsed, accountMenuItems }) => { { ); }; +AuthFooter.propTypes = { + collapsed: PropTypes.bool, + accountMenuItems: PropTypes.array, +}; + export default AuthFooter; From eb4856901b716de2539d61d7e5e713318c12e19f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 1 Aug 2025 15:28:22 -0700 Subject: [PATCH 18/33] extract function --- .../Sidebar/components/authFooter.jsx | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/client/src/Components/Sidebar/components/authFooter.jsx b/client/src/Components/Sidebar/components/authFooter.jsx index 616754c67..6d2dfceeb 100644 --- a/client/src/Components/Sidebar/components/authFooter.jsx +++ b/client/src/Components/Sidebar/components/authFooter.jsx @@ -20,6 +20,22 @@ import { clearAuthState } from "../../../Features/Auth/authSlice"; import { useDispatch } from "react-redux"; import PropTypes from "prop-types"; +const getFilteredAccountMenuItems = (user, items) => { + if (!user) return []; + + let filtered = [...items]; + + if (user.role?.includes("demo")) { + filtered = filtered.filter((item) => item.name !== "Password"); + } + + if (!user.role?.includes("superadmin")) { + filtered = filtered.filter((item) => item.name !== "Team"); + } + + return filtered; +}; + const AuthFooter = ({ collapsed, accountMenuItems }) => { const { t } = useTranslation(); const theme = useTheme(); @@ -43,23 +59,9 @@ const AuthFooter = ({ collapsed, accountMenuItems }) => { navigate("/login"); }; const renderAccountMenuItems = (user, items) => { - let filteredAccountMenuItems = [...items]; + const filteredItems = getFilteredAccountMenuItems(user, items); - // If the user is in demo mode, remove the "Password" option - if (user.role?.includes("demo")) { - filteredAccountMenuItems = filteredAccountMenuItems.filter( - (item) => item.name !== "Password" - ); - } - - // If the user is NOT a superadmin, remove the "Team" option - if (user.role && !user.role.includes("superadmin")) { - filteredAccountMenuItems = filteredAccountMenuItems.filter( - (item) => item.name !== "Team" - ); - } - - return filteredAccountMenuItems.map((item) => ( + return filteredItems.map((item) => ( { From 7c22663250f2c52b7e1fbb60ac608a6a963bb11a Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 1 Aug 2025 15:31:03 -0700 Subject: [PATCH 19/33] remove unused css and old sidebar --- client/src/Components/Sidebar/index.css | 105 --- client/src/Components/Sidebar/index_old.jsx | 873 -------------------- 2 files changed, 978 deletions(-) delete mode 100644 client/src/Components/Sidebar/index.css delete mode 100644 client/src/Components/Sidebar/index_old.jsx diff --git a/client/src/Components/Sidebar/index.css b/client/src/Components/Sidebar/index.css deleted file mode 100644 index a14a0f3d6..000000000 --- a/client/src/Components/Sidebar/index.css +++ /dev/null @@ -1,105 +0,0 @@ -TODO aside .MuiList-root svg { - width: 20px; - height: 20px; - opacity: 0.9; -} -aside span.MuiTypography-root { - font-size: var(--env-var-font-size-medium); - line-height: 1; -} -aside .MuiStack-root + span.MuiTypography-root { - font-size: var(--env-var-font-size-medium-plus); -} -aside .MuiListSubheader-root { - font-size: var(--env-var-font-size-small); - font-weight: 500; - line-height: 1.5; - text-transform: uppercase; - margin-bottom: 2px; - opacity: 0.6; -} -aside p.MuiTypography-root { - font-size: var(--env-var-font-size-small); - opacity: 0.8; -} -aside .MuiListItemButton-root:not(.selected-path) > * { - opacity: 0.9; -} -aside .selected-path > * { - opacity: 1; -} -aside .selected-path span.MuiTypography-root { - font-weight: 600; -} -aside .MuiCollapse-wrapperInner .MuiList-root > .MuiListItemButton-root { - position: relative; -} -aside .MuiCollapse-wrapperInner .MuiList-root svg, -aside .MuiList-root .MuiListItemText-root + svg { - width: 18px; - height: 18px; -} - -.sidebar-popup li.MuiButtonBase-root:has(.MuiBox-root) { - padding-bottom: 0; -} -.sidebar-popup svg { - width: 16px; - height: 16px; - opacity: 0.9; -} - -/* TRANSITIONS */ -aside { - flex: 1; - transition: max-width 650ms cubic-bezier(0.36, -0.01, 0, 0.77); -} -.home-layout aside.collapsed { - max-width: 64px; -} - -aside.expanded .MuiTypography-root, -aside.expanded p.MuiTypography-root, -aside.expanded .MuiListItemText-root + svg, -aside.expanded .MuiAvatar-root + .MuiBox-root + .MuiIconButton-root { - visibility: visible; - animation: fadeIn 1s ease; -} - -aside.collapsed .MuiTypography-root, -aside.collapsed p.MuiTypography-root, -aside.collapsed .MuiListItemText-root + svg, -aside.collapsed .MuiAvatar-root + .MuiBox-root + .MuiIconButton-root { - opacity: 0; - visibility: hidden; -} - -aside .MuiListSubheader-root { - transition: padding 200ms ease; -} - -.sidebar-delay-fade { - opacity: 0; - visibility: hidden; - transition: opacity 0.3s ease; -} - -aside.expanded.sidebar-ready .sidebar-delay-fade { - opacity: 1; - visibility: visible; -} - -@keyframes fadeIn { - 0% { - opacity: 0; - visibility: hidden; - } - 30% { - opacity: 0; - visibility: hidden; - } - 100% { - opacity: 0.9; - visibility: visible; - } -} diff --git a/client/src/Components/Sidebar/index_old.jsx b/client/src/Components/Sidebar/index_old.jsx deleted file mode 100644 index 219ceff1e..000000000 --- a/client/src/Components/Sidebar/index_old.jsx +++ /dev/null @@ -1,873 +0,0 @@ -import React, { useEffect, useState, useRef } from "react"; -import { - Box, - Collapse, - Divider, - IconButton, - List, - ListItemButton, - ListItemIcon, - ListItemText, - Menu, - MenuItem, - Stack, - Tooltip, - Typography, -} from "@mui/material"; -import ThemeSwitch from "../ThemeSwitch"; -import Avatar from "../Avatar"; -import StarPrompt from "../StarPrompt"; -import LockSvg from "../../assets/icons/lock.svg?react"; -import UserSvg from "../../assets/icons/user.svg?react"; -import TeamSvg from "../../assets/icons/user-two.svg?react"; -import LogoutSvg from "../../assets/icons/logout.svg?react"; -import Support from "../../assets/icons/support.svg?react"; -import 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 ArrowDown from "../../assets/icons/down-arrow.svg?react"; -import ArrowUp from "../../assets/icons/up-arrow.svg?react"; -import ArrowRight from "../../assets/icons/right-arrow.svg?react"; -import ArrowLeft from "../../assets/icons/left-arrow.svg?react"; -import DotsVertical from "../../assets/icons/dots-vertical.svg?react"; -import ChangeLog from "../../assets/icons/changeLog.svg?react"; -import Docs from "../../assets/icons/docs.svg?react"; -import 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 "./index.css"; - -// Utils -import { useLocation, useNavigate } from "react-router"; -import { useTheme } from "@emotion/react"; -import { useDispatch, useSelector } from "react-redux"; -import { useTranslation } from "react-i18next"; -import { clearAuthState } from "../../Features/Auth/authSlice"; -import { toggleSidebar } from "../../Features/UI/uiSlice"; -import { TurnedIn } from "@mui/icons-material"; -import { rules } from "eslint-plugin-react-refresh"; - -const getMenu = (t) => [ - { name: t("menu.uptime"), path: "uptime", icon: }, - { name: t("menu.pagespeed"), path: "pagespeed", icon: }, - - { name: t("menu.infrastructure"), path: "infrastructure", icon: }, - { - name: t("menu.notifications"), - path: "notifications", - icon: , - }, - { name: t("menu.incidents"), path: "incidents", icon: }, - - { name: t("menu.statusPages"), path: "status", icon: }, - { name: t("menu.maintenance"), path: "maintenance", icon: }, - { name: t("menu.logs"), path: "logs", icon: }, - - { - name: t("menu.settings"), - icon: , - path: "settings", - }, -]; - -const getOtherMenuItems = (t) => [ - { name: t("menu.support"), path: "support", icon: }, - { - name: t("menu.discussions"), - path: "discussions", - icon: , - }, - { name: t("menu.docs"), path: "docs", icon: }, - { name: t("menu.changelog"), path: "changelog", icon: }, -]; - -const getAccountMenuItems = (t) => [ - { name: t("menu.profile"), path: "account/profile", icon: }, - { name: t("menu.password"), path: "account/password", icon: }, - { name: t("menu.team"), path: "account/team", icon: }, -]; - -/* TODO this could be a key in nested Path would be the link */ -const URL_MAP = { - support: "https://discord.com/invite/NAb6H3UTjK", - discussions: "https://github.com/bluewave-labs/checkmate/discussions", - docs: "https://bluewavelabs.gitbook.io/checkmate", - changelog: "https://github.com/bluewave-labs/checkmate/releases", -}; - -const PATH_MAP = { - monitors: "Dashboard", - pagespeed: "Dashboard", - infrastructure: "Dashboard", - account: "Account", - settings: "Settings", -}; - -/** - * @component - * Sidebar component serves as a sidebar containing a menu. - * - * @returns {JSX.Element} The JSX element representing the Sidebar component. - */ - -function Sidebar() { - const theme = useTheme(); - const navigate = useNavigate(); - const location = useLocation(); - const dispatch = useDispatch(); - const { t } = useTranslation(); - const authState = useSelector((state) => state.auth); - - const otherMenuItems = getOtherMenuItems(t); - const accountMenuItems = getAccountMenuItems(t); - const collapsed = useSelector((state) => state.ui.sidebar.collapsed); - const [open, setOpen] = useState({ Dashboard: false, Account: false, Other: false }); - const [anchorEl, setAnchorEl] = useState(null); - const [popup, setPopup] = useState(); - const { user } = useSelector((state) => state.auth); - - const sidebarRef = useRef(null); - const [sidebarReady, setSidebarReady] = useState(false); - const TRANSITION_DURATION = 200; - let menu = getMenu(t); - menu = menu.filter((item) => { - if (item.path === "logs") { - return user.role?.includes("admin") || user.role?.includes("superadmin"); - } - return true; - }); - - useEffect(() => { - if (!collapsed) { - setSidebarReady(false); - const timeout = setTimeout(() => { - setSidebarReady(true); - }, TRANSITION_DURATION); - return () => clearTimeout(timeout); - } else { - setSidebarReady(false); - } - }, [collapsed]); - - const renderAccountMenuItems = () => { - let filteredAccountMenuItems = [...accountMenuItems]; - - // If the user is in demo mode, remove the "Password" option - if (user.role?.includes("demo")) { - filteredAccountMenuItems = filteredAccountMenuItems.filter( - (item) => item.name !== "Password" - ); - } - - // If the user is NOT a superadmin, remove the "Team" option - if (user.role && !user.role.includes("superadmin")) { - filteredAccountMenuItems = filteredAccountMenuItems.filter( - (item) => item.name !== "Team" - ); - } - - return filteredAccountMenuItems.map((item) => ( - { - closePopup(); - navigate(item.path); - }} - sx={{ - gap: theme.spacing(2), - borderRadius: theme.shape.borderRadius, - pl: theme.spacing(4), - }} - > - {item.icon} - {item.name} - - )); - }; - - const openPopup = (event, id) => { - setAnchorEl(event.currentTarget); - setPopup(id); - }; - const closePopup = () => { - setAnchorEl(null); - }; - - /** - * Handles logging out the user - * - */ - const logout = async () => { - // Clear auth state - dispatch(clearAuthState()); - navigate("/login"); - }; - - useEffect(() => { - const matchedKey = Object.keys(PATH_MAP).find((key) => - location.pathname.includes(key) - ); - - if (matchedKey) { - setOpen((prev) => ({ ...prev, [PATH_MAP[matchedKey]]: true })); - } - }, [location]); - - const iconColor = theme.palette.primary.contrastTextTertiary; - const sidebarClassName = `${collapsed ? "collapsed" : "expanded"} ${sidebarReady ? "sidebar-ready" : ""}`; - - /* TODO refactor this, there are a some ternaries and comments in the return */ - return ( - - { - setOpen((prev) => - Object.fromEntries(Object.keys(prev).map((key) => [key, false])) - ); - dispatch(toggleSidebar()); - }} - > - {collapsed ? : } - - {/* TODO Alignment done using padding. Use single source of truth to that*/} - - {/* TODO Abstract logo into component */} - {/* TODO Turn logo into a link */} - navigate("/")} - sx={{ cursor: "pointer" }} - > - - C - - - {t("common.appName")} - - - - - - {menu.map((item) => { - return item.path ? ( - /* If item has a path */ - - navigate(`/${item.path}`)} - sx={{ - height: "37px", - gap: theme.spacing(4), - borderRadius: theme.shape.borderRadius, - px: theme.spacing(4), - pl: theme.spacing(5), - }} - > - {item.icon} - {item.name} - - - ) : collapsed ? ( - /* TODO Do we ever get here? If item does not have a path and collapsed state is true */ - - - openPopup(event, item.name)} - sx={{ - position: "relative", - gap: theme.spacing(4), - borderRadius: theme.shape.borderRadius, - px: theme.spacing(4), - }} - > - {item.icon} - {item.name} - - - - {item.nested.map((child) => { - if ( - child.name === "Team" && - authState.user?.role && - !authState.user.role.includes("superadmin") - ) { - return null; - } - - return ( - { - const url = URL_MAP[child.path]; - if (url) { - window.open(url, "_blank", "noreferrer"); - } else { - navigate(`/${child.path}`); - } - closePopup(); - }} - sx={{ - gap: theme.spacing(4), - opacity: 0.9, - /* TODO this has no effect? */ - "& svg": { - "& path": { - stroke: theme.palette.primary.contrastTextTertiary, - strokeWidth: 1.1, - }, - }, - }} - > - {child.icon} - {child.name} - - ); - })} - - - ) : ( - /* TODO Do we ever get here? If item does not have a path and collapsed state is false */ - - - setOpen((prev) => ({ - ...Object.fromEntries(Object.keys(prev).map((key) => [key, false])), - [item.name]: !prev[item.name], - })) - } - sx={{ - gap: theme.spacing(4), - borderRadius: theme.shape.borderRadius, - px: theme.spacing(4), - }} - > - {item.icon} - {item.name} - {open[`${item.name}`] ? : } - - - - {item.nested.map((child) => { - if ( - child.name === "Team" && - authState.user?.role && - !authState.user.role.includes("superadmin") - ) { - return null; - } - - return ( - { - const url = URL_MAP[child.path]; - if (url) { - window.open(url, "_blank", "noreferrer"); - } else { - navigate(`/${child.path}`); - } - }} - sx={{ - gap: theme.spacing(4), - borderRadius: theme.shape.borderRadius, - pl: theme.spacing(4), - "&::before": { - content: `""`, - position: "absolute", - top: 0, - left: "-7px", - height: "100%", - borderLeft: 1, - borderLeftColor: theme.palette.primary.lowContrast, - }, - "&:last-child::before": { - height: "50%", - }, - "&::after": { - content: `""`, - position: "absolute", - top: "45%", - left: "-8px", - height: "3px", - width: "3px", - borderRadius: "50%", - backgroundColor: theme.palette.primary.lowContrast, - }, - "&.selected-path::after": { - /* TODO what is this selector doing? */ - backgroundColor: theme.palette.primary.contrastTextTertiary, - transform: "scale(1.2)", - }, - }} - > - {child.icon} - {child.name} - - ); - })} - - - - ); - })} - - - - {!collapsed && } - - - {otherMenuItems.map((item) => { - return item.path ? ( - - { - const url = URL_MAP[item.path]; - if (url) { - window.open(url, "_blank", "noreferrer"); - } else { - navigate(`/${item.path}`); - } - }} - sx={{ - height: "37px", - gap: theme.spacing(4), - borderRadius: theme.shape.borderRadius, - px: theme.spacing(4), - pl: theme.spacing(5), - }} - > - {item.icon} - {item.name} {" "} - - - ) : null; - })} - - - - {collapsed ? ( - <> - - openPopup(event, "logout")} - sx={{ p: 0, "&:focus": { outline: "none" } }} - > - - - - - ) : ( - <> - - - - {authState.user?.firstName} {authState.user?.lastName} - - - {authState.user?.role?.includes("superadmin") - ? t("roles.superAdmin") - : authState.user?.role?.includes("admin") - ? t("roles.admin") - : authState.user?.role?.includes("user") - ? t("roles.teamMember") - : authState.user?.role?.includes("demo") - ? t("roles.demoUser") - : authState.user?.role} - - - - - - openPopup(event, "logout")} - > - - - - - - )} - - {collapsed && ( - - - - {authState.user?.firstName} {authState.user?.lastName} - - - {authState.user?.role} - - - - )} - {/* TODO Do we need two dividers? */} - {collapsed && } - {/* */} - {renderAccountMenuItems()} - - - {t("menu.logOut", "Log out")} - - - - - ); -} - -export default Sidebar; From 12747bd95b499d7332c5c45647951dd928a41778 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 1 Aug 2025 15:36:33 -0700 Subject: [PATCH 20/33] remove console --- client/src/Components/ArrowLeft/index.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/Components/ArrowLeft/index.jsx b/client/src/Components/ArrowLeft/index.jsx index cc7e0b078..7909441f3 100644 --- a/client/src/Components/ArrowLeft/index.jsx +++ b/client/src/Components/ArrowLeft/index.jsx @@ -4,7 +4,6 @@ import LeftArrowLong from "../../assets/icons/left-arrow-long.svg?react"; import PropTypes from "prop-types"; const ArrowLeft = ({ type, color = "#667085", ...props }) => { - console.log(color); if (type === "double") { return ( Date: Fri, 1 Aug 2025 15:43:12 -0700 Subject: [PATCH 21/33] extract role names --- .../Sidebar/components/authFooter.jsx | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/client/src/Components/Sidebar/components/authFooter.jsx b/client/src/Components/Sidebar/components/authFooter.jsx index 6d2dfceeb..c18584b96 100644 --- a/client/src/Components/Sidebar/components/authFooter.jsx +++ b/client/src/Components/Sidebar/components/authFooter.jsx @@ -36,6 +36,17 @@ const getFilteredAccountMenuItems = (user, items) => { return filtered; }; +const getRoleDisplayText = (user, t) => { + if (!user?.role) return ""; + + if (user.role.includes("superadmin")) return t("roles.superAdmin"); + if (user.role.includes("admin")) return t("roles.admin"); + if (user.role.includes("user")) return t("roles.teamMember"); + if (user.role.includes("demo")) return t("roles.demoUser"); + + return user.role; +}; + const AuthFooter = ({ collapsed, accountMenuItems }) => { const { t } = useTranslation(); const theme = useTheme(); @@ -54,7 +65,6 @@ const AuthFooter = ({ collapsed, accountMenuItems }) => { }; const logout = async () => { - // Clear auth state dispatch(clearAuthState()); navigate("/login"); }; @@ -133,15 +143,7 @@ const AuthFooter = ({ collapsed, accountMenuItems }) => { whiteSpace="nowrap" sx={{ textTransform: "capitalize", opacity: 0.8 }} > - {authState.user?.role?.includes("superadmin") - ? t("roles.superAdmin") - : authState.user?.role?.includes("admin") - ? t("roles.admin") - : authState.user?.role?.includes("user") - ? t("roles.teamMember") - : authState.user?.role?.includes("demo") - ? t("roles.demoUser") - : authState.user?.role} + {getRoleDisplayText(authState.user, t)} Date: Fri, 1 Aug 2025 15:45:43 -0700 Subject: [PATCH 22/33] add proptypes, fix logo text animation --- client/src/Components/Sidebar/components/logo.jsx | 9 +++++++-- client/src/Components/Sidebar/components/navItem.jsx | 10 +++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/client/src/Components/Sidebar/components/logo.jsx b/client/src/Components/Sidebar/components/logo.jsx index e6aa660fb..7a59f406d 100644 --- a/client/src/Components/Sidebar/components/logo.jsx +++ b/client/src/Components/Sidebar/components/logo.jsx @@ -4,6 +4,7 @@ import Typography from "@mui/material/Typography"; import { useTheme } from "@mui/material/styles"; import { useNavigate } from "react-router"; import { useTranslation } from "react-i18next"; +import PropTypes from "prop-types"; const Logo = ({ collapsed }) => { const { t } = useTranslation(); @@ -38,10 +39,10 @@ const Logo = ({ collapsed }) => { {" "} @@ -59,4 +60,8 @@ const Logo = ({ collapsed }) => { ); }; +Logo.propTypes = { + collapsed: PropTypes.bool, +}; + export default Logo; diff --git a/client/src/Components/Sidebar/components/navItem.jsx b/client/src/Components/Sidebar/components/navItem.jsx index f43b1bf1e..409a84fde 100644 --- a/client/src/Components/Sidebar/components/navItem.jsx +++ b/client/src/Components/Sidebar/components/navItem.jsx @@ -3,13 +3,11 @@ import ListItemButton from "@mui/material/ListItemButton"; import ListItemIcon from "@mui/material/ListItemIcon"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; - import { useTheme } from "@emotion/react"; -import { useNavigate } from "react-router"; +import PropTypes from "prop-types"; const NavItem = ({ item, collapsed, selected, onClick }) => { const theme = useTheme(); - const navigate = useNavigate(); return ( { ); }; +NavItem.propTypes = { + item: PropTypes.object, + collapsed: PropTypes.bool, + selected: PropTypes.bool, + onClick: PropTypes.func, +}; export default NavItem; From 7900e1104f0a5e7b77f4ad91b81f29854f784c10 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 1 Aug 2025 15:52:55 -0700 Subject: [PATCH 23/33] clean up nav item --- .../Components/Sidebar/components/navItem.jsx | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/client/src/Components/Sidebar/components/navItem.jsx b/client/src/Components/Sidebar/components/navItem.jsx index 409a84fde..2c569edde 100644 --- a/client/src/Components/Sidebar/components/navItem.jsx +++ b/client/src/Components/Sidebar/components/navItem.jsx @@ -8,7 +8,15 @@ import PropTypes from "prop-types"; const NavItem = ({ item, collapsed, selected, onClick }) => { 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 ( { > { sx={{ minWidth: 0, "& svg": { - height: "20px", - width: "20px", + height: 20, + width: 20, opacity: 0.81, }, "& svg path": { - stroke: selected - ? theme.palette.primary.contrastText - : theme.palette.primary.contrastTextTertiary, + stroke: iconStroke, }, }} > @@ -72,7 +76,7 @@ const NavItem = ({ item, collapsed, selected, onClick }) => { variant="body1" color={theme.palette.primary.contrastText} sx={{ - fontWeight: selected ? 600 : 400, + fontWeight: fontWeight, opacity: 0.9, }} > From 521c6988a52765ce789aac2de5eff3fb62179f4a Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 1 Aug 2025 15:58:01 -0700 Subject: [PATCH 24/33] add onclick to avatar --- client/src/Components/Avatar/index.jsx | 4 +++- client/src/Components/Sidebar/components/authFooter.jsx | 8 +++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/client/src/Components/Avatar/index.jsx b/client/src/Components/Avatar/index.jsx index aa2050827..d089080ff 100644 --- a/client/src/Components/Avatar/index.jsx +++ b/client/src/Components/Avatar/index.jsx @@ -15,7 +15,7 @@ import { useTheme } from "@emotion/react"; * */ -const Avatar = ({ src, small, sx }) => { +const Avatar = ({ src, small, sx, onClick = () => {} }) => { const { user } = useSelector((state) => state.auth); const theme = useTheme(); @@ -31,6 +31,7 @@ const Avatar = ({ src, small, sx }) => { return ( { borderRadius={theme.shape.borderRadius} boxSizing={"border-box"} > - + collapsed && openPopup(e)} + sx={{ + cursor: collapsed ? "pointer" : "default", + }} + /> Date: Sat, 2 Aug 2025 12:34:31 +0530 Subject: [PATCH 25/33] Removed teh required true from network db. --- server/src/db/models/HardwareCheck.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/db/models/HardwareCheck.js b/server/src/db/models/HardwareCheck.js index f29df2dbc..3e65e2df4 100755 --- a/server/src/db/models/HardwareCheck.js +++ b/server/src/db/models/HardwareCheck.js @@ -41,7 +41,7 @@ const captureSchema = mongoose.Schema({ }); const networkInterfaceSchema = mongoose.Schema({ - name: { type: String, required: true }, + name: { type: String}, bytes_sent: { type: Number, default: 0 }, bytes_recv: { type: Number, default: 0 }, packets_sent: { type: Number, default: 0 }, @@ -87,6 +87,7 @@ const HardwareCheckSchema = mongoose.Schema( net: { type: [networkInterfaceSchema], default: () => [], + required: false, }, }, { timestamps: true } From 950c5fdc92a567046c3fd75c8d56fd57846c4aa6 Mon Sep 17 00:00:00 2001 From: owiaseimdad Date: Sat, 2 Aug 2025 12:42:35 +0530 Subject: [PATCH 26/33] Formating done. --- server/src/db/models/HardwareCheck.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/db/models/HardwareCheck.js b/server/src/db/models/HardwareCheck.js index 3e65e2df4..5b7d56213 100755 --- a/server/src/db/models/HardwareCheck.js +++ b/server/src/db/models/HardwareCheck.js @@ -41,7 +41,7 @@ const captureSchema = mongoose.Schema({ }); const networkInterfaceSchema = mongoose.Schema({ - name: { type: String}, + name: { type: String }, bytes_sent: { type: Number, default: 0 }, bytes_recv: { type: Number, default: 0 }, packets_sent: { type: Number, default: 0 }, From 91b4c976e5500badd4e08c7ef38edf9e9dad1ccb Mon Sep 17 00:00:00 2001 From: Jesulayomy Date: Sat, 2 Aug 2025 03:53:11 -0400 Subject: [PATCH 27/33] [Infra]: Refactor Create Component, Added Pause and Remove tool bar --- .../src/Pages/Infrastructure/Create/index.jsx | 334 +++++++++++++----- 1 file changed, 243 insertions(+), 91 deletions(-) diff --git a/client/src/Pages/Infrastructure/Create/index.jsx b/client/src/Pages/Infrastructure/Create/index.jsx index cde9315aa..50609b588 100644 --- a/client/src/Pages/Infrastructure/Create/index.jsx +++ b/client/src/Pages/Infrastructure/Create/index.jsx @@ -1,78 +1,51 @@ -// React, Redux, Router -import { useTheme } from "@emotion/react"; -import { useParams } from "react-router-dom"; -import { useState, useEffect } from "react"; -import { useSelector } from "react-redux"; -// Utility and Network -import { infrastructureMonitorValidation } from "../../../Validation/validation"; -import { useFetchHardwareMonitorById } from "../../../Hooks/monitorHooks"; -import { capitalizeFirstLetter } from "../../../Utils/stringUtils"; -import { useTranslation } from "react-i18next"; -import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications"; -import NotificationsConfig from "../../../Components/NotificationConfig"; -import { - useUpdateMonitor, - useCreateMonitor, - useFetchGlobalSettings, -} from "../../../Hooks/monitorHooks"; - -// MUI -import { Box, Stack, Typography, Button, ButtonGroup } from "@mui/material"; - //Components import Breadcrumbs from "../../../Components/Breadcrumbs"; -import Link from "../../../Components/Link"; import ConfigBox from "../../../Components/ConfigBox"; +import Dialog from "../../../Components/Dialog"; +import FieldWrapper from "../../../Components/Inputs/FieldWrapper"; +import Link from "../../../Components/Link"; +import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline"; +import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded"; +import PulseDot from "../../../Components/Animated/PulseDot"; +import Select from "../../../Components/Inputs/Select"; import TextInput from "../../../Components/Inputs/TextInput"; +import { Box, Stack, Tooltip, Typography, Button, ButtonGroup } from "@mui/material"; +import { CustomThreshold } from "./Components/CustomThreshold"; import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments"; import { createToast } from "../../../Utils/toastUtils"; -import Select from "../../../Components/Inputs/Select"; -import { CustomThreshold } from "./Components/CustomThreshold"; -import FieldWrapper from "../../../Components/Inputs/FieldWrapper"; - -const SELECT_VALUES = [ - { _id: 0.25, name: "15 seconds" }, - { _id: 0.5, name: "30 seconds" }, - { _id: 1, name: "1 minute" }, - { _id: 2, name: "2 minutes" }, - { _id: 5, name: "5 minutes" }, - { _id: 10, name: "10 minutes" }, -]; - -const METRICS = ["cpu", "memory", "disk", "temperature"]; -const METRIC_PREFIX = "usage_"; -const MS_PER_MINUTE = 60000; - -const hasAlertError = (errors) => { - return Object.keys(errors).filter((k) => k.startsWith(METRIC_PREFIX)).length > 0; -}; - -const getAlertError = (errors) => { - return Object.keys(errors).find((key) => key.startsWith(METRIC_PREFIX)) - ? errors[Object.keys(errors).find((key) => key.startsWith(METRIC_PREFIX))] - : null; -}; +// Utils +import NotificationsConfig from "../../../Components/NotificationConfig"; +import { capitalizeFirstLetter } from "../../../Utils/stringUtils"; +import { infrastructureMonitorValidation } from "../../../Validation/validation"; +import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications"; +import { useMonitorUtils } from "../../../Hooks/useMonitorUtils"; +import { useParams } from "react-router-dom"; +import { useSelector } from "react-redux"; +import { useState, useEffect } from "react"; +import { useTheme } from "@emotion/react"; +import { useTranslation } from "react-i18next"; +import { + useCreateMonitor, + useDeleteMonitor, + useFetchGlobalSettings, + useFetchHardwareMonitorById, + usePauseMonitor, + useUpdateMonitor, +} from "../../../Hooks/monitorHooks"; const CreateInfrastructureMonitor = () => { - const theme = useTheme(); const { user } = useSelector((state) => state.auth); const { monitorId } = useParams(); - const { t } = useTranslation(); - - // Determine if we are creating or editing const isCreate = typeof monitorId === "undefined"; - // Fetch monitor details if editing - const [monitor, isLoading, networkError] = useFetchHardwareMonitorById({ monitorId }); - const [notifications, notificationsAreLoading, notificationsError] = - useGetNotificationsByTeamId(); - const [updateMonitor, isUpdating] = useUpdateMonitor(); - const [createMonitor, isCreating] = useCreateMonitor(); - const [globalSettings, globalSettingsLoading] = useFetchGlobalSettings(); + const theme = useTheme(); + const { t } = useTranslation(); // State const [errors, setErrors] = useState({}); const [https, setHttps] = useState(false); + const [isOpen, setIsOpen] = useState(false); + const [updateTrigger, setUpdateTrigger] = useState(false); const [infrastructureMonitor, setInfrastructureMonitor] = useState({ url: "", name: "", @@ -90,8 +63,51 @@ const CreateInfrastructureMonitor = () => { secret: "", }); - // Populate form fields if editing + // Fetch monitor details if editing + const { statusColor, pagespeedStatusMsg, determineState } = useMonitorUtils(); + const [monitor, isLoading] = useFetchHardwareMonitorById({ + monitorId, + updateTrigger, + }); + const [createMonitor, isCreating] = useCreateMonitor(); + const [deleteMonitor, isDeleting] = useDeleteMonitor(); + const [globalSettings, globalSettingsLoading] = useFetchGlobalSettings(); + const [notifications, notificationsAreLoading] = useGetNotificationsByTeamId(); + const [pauseMonitor, isPausing] = usePauseMonitor(); + const [updateMonitor, isUpdating] = useUpdateMonitor(); + const FREQUENCIES = [ + { _id: 0.25, name: t("time.fifteenSeconds") }, + { _id: 0.5, name: t("time.thirtySeconds") }, + { _id: 1, name: t("time.oneMinute") }, + { _id: 2, name: t("time.twoMinutes") }, + { _id: 5, name: t("time.fiveMinutes") }, + { _id: 10, name: t("time.tenMinutes") }, + ]; + const CRUMBS = [ + { name: "Infrastructure monitors", path: "/infrastructure" }, + ...(isCreate + ? [{ name: "Create", path: "/infrastructure/create" }] + : [ + { name: "Details", path: `/infrastructure/${monitorId}` }, + { name: "Configure", path: `/infrastructure/configure/${monitorId}` }, + ]), + ]; + const METRICS = ["cpu", "memory", "disk", "temperature"]; + const METRIC_PREFIX = "usage_"; + const MS_PER_MINUTE = 60000; + + const hasAlertError = (errors) => { + return Object.keys(errors).filter((k) => k.startsWith(METRIC_PREFIX)).length > 0; + }; + + const getAlertError = (errors) => { + return Object.keys(errors).find((key) => key.startsWith(METRIC_PREFIX)) + ? errors[Object.keys(errors).find((key) => key.startsWith(METRIC_PREFIX))] + : null; + }; + + // Populate form fields if editing useEffect(() => { if (isCreate) { if (globalSettingsLoading) return; @@ -231,6 +247,10 @@ const CreateInfrastructureMonitor = () => { : await updateMonitor({ monitor: form, redirect: "/infrastructure" }); }; + const triggerUpdate = () => { + setUpdateTrigger(!updateTrigger); + }; + const onChange = (event) => { const { value, name } = event.target; setInfrastructureMonitor({ @@ -257,19 +277,26 @@ const CreateInfrastructureMonitor = () => { }); }; + const handlePause = async () => { + await pauseMonitor({ monitorId, triggerUpdate }); + }; + + const handleRemove = async (event) => { + event.preventDefault(); + await deleteMonitor({ monitor, redirect: "/infrastructure" }); + }; + + const isBusy = + isLoading || + isUpdating || + isCreating || + isDeleting || + isPausing || + notificationsAreLoading; + return ( - + { gap={theme.spacing(12)} mt={theme.spacing(6)} > - - - {t(isCreate ? "infrastructureCreateYour" : "infrastructureEditYour")}{" "} - - - {t("monitor")} - - + + + + {!isCreate ? infrastructureMonitor.name : t("createYour") + " "} + + {isCreate ? ( + + {t("monitor")} + + ) : ( + <> + )} + + {!isCreate && ( + + + + + + + + {infrastructureMonitor.url?.replace(/^https?:\/\//, "") || "..."} + + + {t("editing")} + + + )} + + {!isCreate && ( + + + + + )} + { label="Check frequency" value={infrastructureMonitor.interval || 15} onChange={onChange} - items={SELECT_VALUES} + items={FREQUENCIES} /> @@ -474,12 +615,23 @@ const CreateInfrastructureMonitor = () => { type="submit" variant="contained" color="accent" - loading={isLoading || isUpdating || isCreating || notificationsAreLoading} + loading={isBusy} > {t(isCreate ? "infrastructureCreateMonitor" : "infrastructureEditMonitor")} + {!isCreate && ( + setIsOpen(false)} + confirmationButtonLabel={t("delete")} + onConfirm={handleRemove} + /> + )} ); }; From 2fc0eb9460b65db9594b56ec2d669a984afc4c3c Mon Sep 17 00:00:00 2001 From: Jesulayomy Date: Sat, 2 Aug 2025 03:53:54 -0400 Subject: [PATCH 28/33] [Infra]: Added translations for some time blocks --- client/src/locales/en.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 605a50fa0..230637474 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -980,6 +980,7 @@ "testLocale": "testLocale", "testNotificationsDisabled": "There are no notifications setup for this monitor. You need to add one by clicking 'Configure' button", "time": { + "fifteenSeconds": "15 seconds", "fiveMinutes": "5 minutes", "fourMinutes": "4 minutes", "oneDay": "1 day", @@ -987,6 +988,7 @@ "oneMinute": "1 minute", "oneWeek": "1 week", "tenMinutes": "10 minutes", + "thirtySeconds": "30 seconds", "threeMinutes": "3 minutes", "twentyMinutes": "20 minutes", "twoMinutes": "2 minutes" From aa35e9211bbb135af0178b1046f3a55c48582552 Mon Sep 17 00:00:00 2001 From: Jesulayomy Date: Sat, 2 Aug 2025 04:13:29 -0400 Subject: [PATCH 29/33] [Infra]: Removed duplicate call to .find method on the errors object --- client/src/Pages/Infrastructure/Create/index.jsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/src/Pages/Infrastructure/Create/index.jsx b/client/src/Pages/Infrastructure/Create/index.jsx index 50609b588..ae95cce33 100644 --- a/client/src/Pages/Infrastructure/Create/index.jsx +++ b/client/src/Pages/Infrastructure/Create/index.jsx @@ -102,9 +102,8 @@ const CreateInfrastructureMonitor = () => { }; const getAlertError = (errors) => { - return Object.keys(errors).find((key) => key.startsWith(METRIC_PREFIX)) - ? errors[Object.keys(errors).find((key) => key.startsWith(METRIC_PREFIX))] - : null; + const errorKey = Object.keys(errors).find((key) => key.startsWith(METRIC_PREFIX)); + return errorKey ? errors[errorKey] : null; }; // Populate form fields if editing From 45352b62e1cd9ed7c0c70353b9b08ddde9bcd51b Mon Sep 17 00:00:00 2001 From: mohadeseh safari Date: Sun, 3 Aug 2025 18:51:12 -0400 Subject: [PATCH 30/33] feat(status-page): add customCSS field to StatusPage model --- server/src/db/models/StatusPage.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/src/db/models/StatusPage.js b/server/src/db/models/StatusPage.js index 92cbab1cd..621749fb8 100755 --- a/server/src/db/models/StatusPage.js +++ b/server/src/db/models/StatusPage.js @@ -74,6 +74,10 @@ const StatusPageSchema = mongoose.Schema( type: Boolean, default: false, }, + customCSS: { + type: String, + default: "", + }, }, { timestamps: true } ); From 86a0b0b60ca8e06e757f2ef1aabc76cc63759f3b Mon Sep 17 00:00:00 2001 From: vineet-channe Date: Mon, 4 Aug 2025 18:43:37 +0530 Subject: [PATCH 31/33] fix: update diagnostic page design to match infrastructure page - Remove 'System diagnostics' title and divider line - Add infrastructure-style status boxes with dynamic status - Update gauge components to use BaseContainer styling - Match infrastructure page layout and spacing Resolves #2725 --- .../Diagnostics/components/gauges/index.jsx | 164 +++++++++++------- client/src/Pages/Logs/Diagnostics/index.jsx | 83 +++++---- 2 files changed, 155 insertions(+), 92 deletions(-) diff --git a/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx b/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx index e9f235112..538c157e3 100644 --- a/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx +++ b/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx @@ -1,34 +1,83 @@ import Stack from "@mui/material/Stack"; -import Gauge from "../../../../../Components/Charts/CustomGauge"; +import CustomGauge from "../../../../../Components/Charts/CustomGauge"; import Typography from "@mui/material/Typography"; // Utils import { useTheme } from "@emotion/react"; import PropTypes from "prop-types"; -import { getPercentage } from "../../utils/utils"; +import { getPercentage, formatBytes } from "../../utils/utils"; import { useTranslation } from "react-i18next"; +import { Box } from "@mui/material"; -const GaugeBox = ({ title, subtitle, children }) => { - const theme = useTheme(); - return ( - - {children} - - {title} - {subtitle} - +const BaseContainer = ({children}) => { + const theme = useTheme() + return( + + {children} + ); }; -GaugeBox.propTypes = { - title: PropTypes.string.isRequired, - subtitle: PropTypes.string.isRequired, - children: PropTypes.node.isRequired, +const InfrastructureStyleGauge = ({ value, heading, metricOne, valueOne, metricTwo, valueTwo }) => { + const theme = useTheme(); + const valueStyle = { + borderRadius: theme.spacing(2), + backgroundColor: theme.palette.tertiary.main, + width: "40%", + mb: theme.spacing(2), + mt: theme.spacing(2), + pr: theme.spacing(2), + textAlign: "right", + }; + + return( + + + + + + {heading} + + + + + {metricOne} + {valueOne} + + + {metricTwo} + {valueTwo} + + + + + ); }; const Gauges = ({ diagnostics, isLoading }) => { @@ -53,50 +102,41 @@ const Gauges = ({ diagnostics, isLoading }) => { return ( - - - - - - - - - - - - + + + + 80 ? "High" : diagnostics?.cpuUsage?.usagePercentage > 50 ? "Medium" : "Low"} + /> ); }; diff --git a/client/src/Pages/Logs/Diagnostics/index.jsx b/client/src/Pages/Logs/Diagnostics/index.jsx index 7c5d1f5a9..61e64811b 100644 --- a/client/src/Pages/Logs/Diagnostics/index.jsx +++ b/client/src/Pages/Logs/Diagnostics/index.jsx @@ -1,14 +1,14 @@ import Stack from "@mui/material/Stack"; import Box from "@mui/material/Box"; -import Typography from "@mui/material/Typography"; import Gauges from "./components/gauges"; -import Stats from "./components/stats"; -import Divider from "@mui/material/Divider"; import Button from "@mui/material/Button"; - +import StatBox from "../../../Components/StatBox"; +import StatusBoxes from "../../../Components/StatusBoxes"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; import { useFetchDiagnostics } from "../../../Hooks/logHooks"; +import { getHumanReadableDuration } from "../../../Utils/timeUtils"; +import { formatBytes, getPercentage } from "./utils/utils"; const Diagnostics = () => { // Local state @@ -19,34 +19,57 @@ const Diagnostics = () => { const [diagnostics, fetchDiagnostics, isLoading, error] = useFetchDiagnostics(); // Setup return ( - + + + + + + + + + + - {t("diagnosticsPage.diagnosticDescription")} + - - - - - - - - ); }; From de34710c90ce797447d5e2b8ac82d36feb8b63b7 Mon Sep 17 00:00:00 2001 From: vineet-channe Date: Tue, 5 Aug 2025 17:35:40 +0530 Subject: [PATCH 32/33] fix: address review feedback - Replace hardcoded button text with translation - Fix status box translation to avoid showing removed title - Add conditional rendering for optional gauge metrics --- .../Diagnostics/components/gauges/index.jsx | 136 ++++++++++-------- client/src/Pages/Logs/Diagnostics/index.jsx | 22 +-- 2 files changed, 84 insertions(+), 74 deletions(-) diff --git a/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx b/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx index 538c157e3..5bb227b7c 100644 --- a/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx +++ b/client/src/Pages/Logs/Diagnostics/components/gauges/index.jsx @@ -14,10 +14,9 @@ const BaseContainer = ({children}) => { return( @@ -28,15 +27,28 @@ const BaseContainer = ({children}) => { const InfrastructureStyleGauge = ({ value, heading, metricOne, valueOne, metricTwo, valueTwo }) => { const theme = useTheme(); - const valueStyle = { - borderRadius: theme.spacing(2), - backgroundColor: theme.palette.tertiary.main, - width: "40%", - mb: theme.spacing(2), - mt: theme.spacing(2), - pr: theme.spacing(2), - textAlign: "right", - }; + + const MetricRow = ({ label, value }) => ( + + {label} + + {value} + + + ); return( @@ -47,7 +59,6 @@ const InfrastructureStyleGauge = ({ value, heading, metricOne, valueOne, metricT flexDirection: "column", alignItems: "center", width: "100%", - backgroundColor: theme.palette.gradient?.color1 || "transparent" }} > @@ -55,25 +66,11 @@ const InfrastructureStyleGauge = ({ value, heading, metricOne, valueOne, metricT {heading} - - - {metricOne} - {valueOne} - - - {metricTwo} - {valueTwo} - + + + {metricTwo && valueTwo && ( + + )} @@ -105,38 +102,38 @@ const Gauges = ({ diagnostics, isLoading }) => { spacing={theme.spacing(8)} flexWrap="wrap" > - - - - 80 ? "High" : diagnostics?.cpuUsage?.usagePercentage > 50 ? "Medium" : "Low"} - /> + + + + ); }; @@ -146,4 +143,17 @@ Gauges.propTypes = { isLoading: PropTypes.bool, }; +InfrastructureStyleGauge.propTypes = { + value: PropTypes.number, + heading: PropTypes.string, + metricOne: PropTypes.string, + valueOne: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), + metricTwo: PropTypes.string, + valueTwo: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), +}; + +BaseContainer.propTypes = { + children: PropTypes.node.isRequired, +}; + export default Gauges; diff --git a/client/src/Pages/Logs/Diagnostics/index.jsx b/client/src/Pages/Logs/Diagnostics/index.jsx index 61e64811b..72ee47f48 100644 --- a/client/src/Pages/Logs/Diagnostics/index.jsx +++ b/client/src/Pages/Logs/Diagnostics/index.jsx @@ -20,39 +20,39 @@ const Diagnostics = () => { // Setup return ( - + @@ -67,7 +67,7 @@ const Diagnostics = () => { onClick={fetchDiagnostics} loading={isLoading} > - Fetch Diagnostics + {t("queuePage.refreshButton")} From d4e83818272d8a5080a10b45bc8f1c76b4ac9817 Mon Sep 17 00:00:00 2001 From: Owaise Date: Tue, 5 Aug 2025 19:43:35 +0530 Subject: [PATCH 33/33] Renamed them as average values in the table. --- server/src/db/mongo/modules/monitorModuleQueries.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/src/db/mongo/modules/monitorModuleQueries.js b/server/src/db/mongo/modules/monitorModuleQueries.js index df15593e2..f7283c8e9 100755 --- a/server/src/db/mongo/modules/monitorModuleQueries.js +++ b/server/src/db/mongo/modules/monitorModuleQueries.js @@ -393,7 +393,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { 0, ], }, - bytesSent: { + avgBytesSent: { $avg: { $map: { input: "$net", @@ -404,7 +404,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, }, }, - bytesRecv: { + avgBytesRecv: { $avg: { $map: { input: "$net", @@ -415,7 +415,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, }, }, - packetsSent: { + avgPacketsSent: { $avg: { $map: { input: "$net", @@ -426,7 +426,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, }, }, - packetsRecv: { + avgPacketsRecv: { $avg: { $map: { input: "$net", @@ -437,7 +437,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, }, }, - errIn: { + avgErrIn: { $avg: { $map: { input: "$net", @@ -448,7 +448,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, }, }, - errOut: { + avgErrOut: { $avg: { $map: { input: "$net",