From bf150181738bf0e428a977a29293cf284e622c50 Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Thu, 15 Jan 2026 22:04:56 -0500 Subject: [PATCH 1/6] fix: various UI/UX improvements for infrastructure pages ## Changes - Fix infrastructure monitor configure page loading error by adding required dateRange parameter - Fix URL validation error when editing existing infrastructure monitors - Fix monitor update using correct 'id' property instead of '_id' - Fix gauge box labels: "Device" (capitalized) and "Mounted on" instead of "mountpoint" - Show full device path instead of truncated ".../" prefix - Implement CSS Grid layout for gauge boxes ensuring consistent widths across all rows - Gauge boxes expand to fill space only when 4+ boxes exist ## Files Modified - client/src/Pages/Infrastructure/Create/index.jsx - client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx - client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx - client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx - client/src/Pages/Infrastructure/Details/Components/BaseContainer/index.jsx - client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx - client/src/locales/en.json --- .../Create/hooks/useInfrastructureSubmit.jsx | 2 +- .../src/Pages/Infrastructure/Create/index.jsx | 9 +++++--- .../Components/BaseContainer/index.jsx | 5 +++-- .../Details/Components/GaugeBoxes/Gauge.jsx | 10 +++++++-- .../Details/Components/GaugeBoxes/index.jsx | 22 +++++++++++++------ .../Details/Hooks/useHardwareUtils.jsx | 22 ++++--------------- client/src/locales/en.json | 2 ++ 7 files changed, 39 insertions(+), 33 deletions(-) diff --git a/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx b/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx index c5c7aacf2..04279cac4 100644 --- a/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx +++ b/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx @@ -61,7 +61,7 @@ const useInfrastructureSubmit = () => { }; const finalForm = { - ...(isCreate ? {} : { _id: monitorId }), + ...(isCreate ? {} : { id: monitorId }), ...rest, description: form.name, type: "hardware", diff --git a/client/src/Pages/Infrastructure/Create/index.jsx b/client/src/Pages/Infrastructure/Create/index.jsx index b27c11cd4..e5cdbcd63 100644 --- a/client/src/Pages/Infrastructure/Create/index.jsx +++ b/client/src/Pages/Infrastructure/Create/index.jsx @@ -44,6 +44,7 @@ const CreateInfrastructureMonitor = () => { // Fetch monitor details if editing const [monitor, isLoading] = useFetchHardwareMonitorById({ monitorId, + dateRange: "day", updateTrigger, }); const [deleteMonitor, isDeleting] = useDeleteMonitor(); @@ -127,7 +128,9 @@ const CreateInfrastructureMonitor = () => { const onSubmit = async (event) => { event.preventDefault(); const form = buildForm(infrastructureMonitor, https); - const error = validateForm(form); + // When editing, exclude URL from validation since it's disabled and can't be changed + const formToValidate = isCreate ? form : { ...form, url: monitor.url }; + const error = validateForm(formToValidate); if (error) { return; } @@ -204,14 +207,14 @@ const CreateInfrastructureMonitor = () => { <> )} - {!isCreate && ( + {!isCreate && monitor && ( )} - {!isCreate && ( + {!isCreate && monitor && ( { +const BaseContainer = ({ children, sx = {}, shouldExpand = false }) => { const theme = useTheme(); const { getDimensions } = useHardwareUtils(); return ( @@ -22,7 +22,7 @@ const BaseContainer = ({ children, sx = {} }) => { sx={{ padding: `${theme.spacing(getDimensions().baseBoxPaddingVertical)} ${theme.spacing(getDimensions().baseBoxPaddingHorizontal)}`, minWidth: 200, - width: 225, + width: shouldExpand ? "100%" : 225, backgroundColor: theme.palette.primary.main, border: 1, borderStyle: "solid", @@ -38,6 +38,7 @@ const BaseContainer = ({ children, sx = {} }) => { BaseContainer.propTypes = { children: PropTypes.node.isRequired, sx: PropTypes.object, + shouldExpand: PropTypes.bool, }; export default BaseContainer; diff --git a/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx b/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx index 58e69861c..c762eeb32 100644 --- a/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx @@ -17,21 +17,26 @@ const Gauge = ({ valueThree, metricFour, valueFour, + shouldExpand = false, }) => { const theme = useTheme(); const valueStyle = { borderRadius: theme.spacing(2), backgroundColor: theme.palette.tertiary.main, - width: "40%", + minWidth: "40%", + maxWidth: "60%", mb: theme.spacing(2), mt: theme.spacing(2), pr: theme.spacing(2), textAlign: "right", + overflow: "hidden", + textOverflow: "ellipsis", + whiteSpace: "nowrap", }; return ( - + { valueTwo: formatBytes(disk.total_bytes, true), metricThree: t("device"), valueThree: formatDeviceName(disk.device), - metricFour: t("mountpoint"), + metricFour: t("mountedOn"), valueFour: formatMountpoint(disk.mountpoint), })), ]; + // Only expand gauges to fill row when there are 4 or more + const shouldExpand = gauges.length >= 4; + return ( - {gauges.map((gauge) => { return ( @@ -90,10 +97,11 @@ const Gauges = ({ isLoading = false, monitor }) => { valueThree={gauge.valueThree} metricFour={gauge.metricFour} valueFour={gauge.valueFour} + shouldExpand={shouldExpand} /> ); })} - + ); }; diff --git a/client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx b/client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx index 45e32e822..8938847a4 100644 --- a/client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx +++ b/client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx @@ -123,14 +123,7 @@ const useHardwareUtils = () => { const formatDeviceName = (device) => { const deviceStr = String(device || ""); - // Extract the last part of the path (after last '/') - const parts = deviceStr.split("/"); - const lastPart = parts[parts.length - 1]; - - // If there's more than one part, show with "..." prefix - const displayText = parts.length > 1 ? `.../${lastPart}` : deviceStr; - - // Always show tooltip with full device path + // Show full device path return ( { maxWidth: "100%", }} > - {displayText} + {deviceStr} ); @@ -181,14 +174,7 @@ const useHardwareUtils = () => { ); } - // Extract the last part of the path (after last '/') - const parts = mountpointStr.split("/"); - const lastPart = parts[parts.length - 1]; - - // If there's more than one part, show with "..." prefix - const displayText = parts.length > 1 ? `.../${lastPart}` : mountpointStr; - - // Always show tooltip with full mountpoint path + // Show full mountpoint path return ( { maxWidth: "100%", }} > - {displayText} + {mountpointStr} ); diff --git a/client/src/locales/en.json b/client/src/locales/en.json index a658d6d66..54fb6eddb 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -193,6 +193,8 @@ "total": "Total", "cores": "Cores", "frequency": "Frequency", + "device": "Device", + "mountedOn": "Mounted on", "status": "Status", "cpuPhysical": "CPU (Physical)", "cpuLogical": "CPU (Logical)", From 2055943876a8ea870c85d91c654bd0859312b08d Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Fri, 16 Jan 2026 13:10:45 -0500 Subject: [PATCH 2/6] fix: make dateRange optional for hardware details endpoint The Joi validation marks dateRange as optional, but the controller was using requireString() which throws a 400 error when dateRange is missing. Changed to optionalString() with "recent" as the default value, matching the behavior of other similar endpoints and the Joi validation schema. --- server/src/controllers/monitorController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/controllers/monitorController.ts b/server/src/controllers/monitorController.ts index d46845026..56a18b6b3 100644 --- a/server/src/controllers/monitorController.ts +++ b/server/src/controllers/monitorController.ts @@ -97,7 +97,7 @@ class MonitorController { await getHardwareDetailsByIdQueryValidation.validateAsync(req.query); const monitorId = requireString(req?.params?.monitorId, "Monitor ID"); - const dateRange = requireString(req?.query?.dateRange, "dateRange"); + const dateRange = optionalString(req?.query?.dateRange, "dateRange") || "recent"; const teamId = requireTeamId(req?.user?.teamId); const monitor = await this.monitorService.getHardwareDetailsById({ From 7436c2b5df6623b784a170e4c7c2cd8590458b9f Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Fri, 16 Jan 2026 13:12:00 -0500 Subject: [PATCH 3/6] fix: remove unnecessary dateRange param now that backend defaults it --- client/src/Pages/Infrastructure/Create/index.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/Pages/Infrastructure/Create/index.jsx b/client/src/Pages/Infrastructure/Create/index.jsx index e5cdbcd63..06a6a5716 100644 --- a/client/src/Pages/Infrastructure/Create/index.jsx +++ b/client/src/Pages/Infrastructure/Create/index.jsx @@ -44,7 +44,6 @@ const CreateInfrastructureMonitor = () => { // Fetch monitor details if editing const [monitor, isLoading] = useFetchHardwareMonitorById({ monitorId, - dateRange: "day", updateTrigger, }); const [deleteMonitor, isDeleting] = useDeleteMonitor(); From 3a77c6d7fad51e06a860328c7730ce62c0db989b Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Fri, 16 Jan 2026 22:26:43 -0500 Subject: [PATCH 4/6] Fix sidebar gap when scrolling on pages with tall content The sidebar was showing a visible gap at the bottom when scrolling on pages with tall tables (e.g., /checks). This occurred because the parent container's overflow-x: hidden was breaking sticky positioning. Changes: - Changed sidebar from position: sticky to position: fixed - Added a spacer Box element to maintain layout spacing - Moved border and background styles to the sidebar component - Cleaned up unused CSS selectors --- .../Components/v1/Layouts/HomeLayout/index.css | 10 +--------- .../Components/v1/Layouts/HomeLayout/index.jsx | 16 ++++++++++++++-- client/src/Components/v1/Sidebar/index.jsx | 7 +++++-- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/client/src/Components/v1/Layouts/HomeLayout/index.css b/client/src/Components/v1/Layouts/HomeLayout/index.css index 953a2c682..61808aa57 100644 --- a/client/src/Components/v1/Layouts/HomeLayout/index.css +++ b/client/src/Components/v1/Layouts/HomeLayout/index.css @@ -14,15 +14,7 @@ } } */ -/* .home-layout aside { - position: sticky; - top: 0; - left: 0; - height: 100vh; - max-width: var(--env-var-side-bar-width); -} */ - -.home-layout > div { +.home-layout > .home-content-wrapper { min-height: calc(100vh - var(--env-var-spacing-2) * 2); flex: 1; } diff --git a/client/src/Components/v1/Layouts/HomeLayout/index.jsx b/client/src/Components/v1/Layouts/HomeLayout/index.jsx index 3e63aff37..825157333 100644 --- a/client/src/Components/v1/Layouts/HomeLayout/index.jsx +++ b/client/src/Components/v1/Layouts/HomeLayout/index.jsx @@ -1,17 +1,29 @@ import Sidebar from "../../Sidebar/index.jsx"; import { Outlet } from "react-router"; -import { Stack } from "@mui/material"; +import { Box, Stack } from "@mui/material"; +import { useSelector } from "react-redux"; import "./index.css"; const HomeLayout = () => { + const collapsed = useSelector((state) => state.ui.sidebar?.collapsed ?? false); + return ( + {/* Spacer for fixed sidebar */} + diff --git a/client/src/Components/v1/Sidebar/index.jsx b/client/src/Components/v1/Sidebar/index.jsx index b66f081fb..f39121987 100644 --- a/client/src/Components/v1/Sidebar/index.jsx +++ b/client/src/Components/v1/Sidebar/index.jsx @@ -81,14 +81,17 @@ const Sidebar = () => { : "var(--env-var-side-bar-width)" } component="aside" - position="sticky" + position="fixed" top={0} - borderRight={`1px solid ${theme.palette.primary.lowContrast}`} + left={0} paddingTop={theme.spacing(6)} paddingBottom={theme.spacing(6)} gap={theme.spacing(6)} sx={{ transition: "width 650ms cubic-bezier(0.36, -0.01, 0, 0.77)", + backgroundColor: "#000000", + borderRight: "1px solid #344054", + zIndex: 1000, }} > From a4cf53ee1549f510787d71d0233e99f517a19022 Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Fri, 16 Jan 2026 22:52:11 -0500 Subject: [PATCH 5/6] Set html/body background to black and disable overscroll bounce - Added background-color: #000000 to prevent white flash on overscroll - Added overscroll-behavior: none to disable elastic bounce effect --- client/src/index.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/src/index.css b/client/src/index.css index d58a7e872..b29c17960 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -4,6 +4,12 @@ box-sizing: border-box; } +html, +body { + background-color: #000000; + overscroll-behavior: none; +} + html { scroll-behavior: smooth; } From 487f6b222d7ad0e919435348f21eb24067f6def6 Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Sun, 18 Jan 2026 14:11:05 -0500 Subject: [PATCH 6/6] refactor: extract sidebar width logic into useSidebar hook Create shared useSidebar hook to centralize sidebar width and transition logic, addressing code review feedback about duplicated logic between Sidebar and HomeLayout components. Changes: - Add useSidebar hook with collapsed state, width, and transition values - Update Sidebar to use the hook instead of inline selectors and values - Update HomeLayout to use the hook for spacer element styling This ensures sidebar width logic is defined in one place and stays in sync. --- .../v1/Layouts/HomeLayout/index.jsx | 10 +++---- client/src/Components/v1/Sidebar/index.jsx | 13 +++------ client/src/Hooks/useSidebar.js | 29 +++++++++++++++++++ 3 files changed, 37 insertions(+), 15 deletions(-) create mode 100644 client/src/Hooks/useSidebar.js diff --git a/client/src/Components/v1/Layouts/HomeLayout/index.jsx b/client/src/Components/v1/Layouts/HomeLayout/index.jsx index 825157333..b078fe048 100644 --- a/client/src/Components/v1/Layouts/HomeLayout/index.jsx +++ b/client/src/Components/v1/Layouts/HomeLayout/index.jsx @@ -1,12 +1,12 @@ import Sidebar from "../../Sidebar/index.jsx"; import { Outlet } from "react-router"; import { Box, Stack } from "@mui/material"; -import { useSelector } from "react-redux"; +import { useSidebar } from "@/Hooks/useSidebar.js"; import "./index.css"; const HomeLayout = () => { - const collapsed = useSelector((state) => state.ui.sidebar?.collapsed ?? false); + const { width, transition } = useSidebar(); return ( { {/* Spacer for fixed sidebar */} diff --git a/client/src/Components/v1/Sidebar/index.jsx b/client/src/Components/v1/Sidebar/index.jsx index f39121987..06caede5a 100644 --- a/client/src/Components/v1/Sidebar/index.jsx +++ b/client/src/Components/v1/Sidebar/index.jsx @@ -12,9 +12,9 @@ import Icon from "../Icon"; // Utils import { useTheme } from "@mui/material/styles"; -import { useSelector } from "react-redux"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router"; +import { useSidebar } from "@/Hooks/useSidebar.js"; const URL_MAP = { support: "https://discord.com/invite/NAb6H3UTjK", @@ -65,8 +65,7 @@ const Sidebar = () => { const theme = useTheme(); const { t } = useTranslation(); const navigate = useNavigate(); - // Redux state - const collapsed = useSelector((state) => state.ui.sidebar?.collapsed ?? false); + const { collapsed, width, transition } = useSidebar(); const menu = getMenu(t); const otherMenuItems = getOtherMenuItems(t); @@ -75,11 +74,7 @@ const Sidebar = () => { return ( { paddingBottom={theme.spacing(6)} gap={theme.spacing(6)} sx={{ - transition: "width 650ms cubic-bezier(0.36, -0.01, 0, 0.77)", + transition, backgroundColor: "#000000", borderRight: "1px solid #344054", zIndex: 1000, diff --git a/client/src/Hooks/useSidebar.js b/client/src/Hooks/useSidebar.js new file mode 100644 index 000000000..97724b417 --- /dev/null +++ b/client/src/Hooks/useSidebar.js @@ -0,0 +1,29 @@ +import { useSelector } from "react-redux"; + +// CSS variable names for sidebar widths +const SIDEBAR_WIDTH_VAR = "var(--env-var-side-bar-width)"; +const SIDEBAR_COLLAPSED_WIDTH_VAR = "var(--env-var-side-bar-collapsed-width)"; + +// Transition timing for sidebar width changes +const SIDEBAR_TRANSITION = "width 650ms cubic-bezier(0.36, -0.01, 0, 0.77)"; + +/** + * Hook to get sidebar state and computed width + * Centralizes sidebar width logic to avoid duplication between Sidebar and HomeLayout + * + * @returns {Object} Sidebar state and styles + * @returns {boolean} collapsed - Whether the sidebar is collapsed + * @returns {string} width - CSS width value based on collapsed state + * @returns {string} transition - CSS transition for width changes + */ +export const useSidebar = () => { + const collapsed = useSelector((state) => state.ui.sidebar?.collapsed ?? false); + + return { + collapsed, + width: collapsed ? SIDEBAR_COLLAPSED_WIDTH_VAR : SIDEBAR_WIDTH_VAR, + transition: SIDEBAR_TRANSITION, + }; +}; + +export default useSidebar;