From 96b2266a9df401ec3a3e9cb62e20af91ae7f3cb4 Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Fri, 28 Aug 2026 10:33:28 -0400 Subject: [PATCH 01/22] fix(infrastructure): wrap gauge row so every disk stays reachable The gauge row on the infrastructure details page laid out memory, CPU and one gauge per disk in a single flex row with no wrapping and no overflow handling. Each gauge is capped at 260px, so on a host with enough disks the row ran past the content column. RootLayout sets overflow: "hidden" on that column, so the excess was clipped outright rather than becoming scrollable - the later disks rendered but could not be reached by any means. Three changes, all scoped to this row: flexWrap moves the overflow onto additional rows. useFlexGap is required alongside it. MUI's Stack implements `spacing` as `& > :not(style) ~ :not(style) { marginLeft }`, and that selector is DOM-order based rather than line-aware, so the first card of each wrapped row would carry a spurious 16px left margin and misalign against the row above. useFlexGap switches to a real flex gap. flexBasis on the children is what actually makes the line break. BaseChart sets `flex: 1`, which expands to `flex: 1 1 0%`; wrapping is decided by the sum of items' hypothetical main sizes, so with a zero basis that sum is 0 and the row never overflows. The cards would instead shrink to min-content (roughly the 70px gauge plus the widest label) and stay on one cramped line. Giving each child a 260px basis makes the row wrap at the intended card width. The basis is applied through the row's own `& > *` selector rather than by changing BaseChart, which has eight other consumers that default to maxWidth: "100%" and would have had their layout altered. The repeated 260 literals are now a named constant. Only affects viewports at or above the md breakpoint; below it the stack is already a column, where flexWrap is inert. Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd --- .../Details/Components/Gauges.tsx | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/client/src/Pages/Infrastructure/Details/Components/Gauges.tsx b/client/src/Pages/Infrastructure/Details/Components/Gauges.tsx index e2b053aef..2e9e88a0f 100644 --- a/client/src/Pages/Infrastructure/Details/Components/Gauges.tsx +++ b/client/src/Pages/Infrastructure/Details/Components/Gauges.tsx @@ -8,6 +8,9 @@ import { useTheme } from "@mui/material"; import useMediaQuery from "@mui/material/useMediaQuery"; import type { CheckSnapshot } from "@/Types/Check"; +// Width each gauge card aims for, and the basis the wrapping row breaks on. +const GAUGE_MAX_WIDTH = 260; + export const InfraDetailsGauges = ({ snapshot, }: { @@ -26,10 +29,19 @@ export const InfraDetailsGauges = ({ direction={isSmall ? "column" : "row"} spacing={theme.spacing(8)} alignItems={"stretch"} + flexWrap={"wrap"} + useFlexGap + sx={{ + // The gauge cards are flex: 1 1 0, so a wrapping row would never + // overflow and they would squash to min-content instead of breaking + // the line. Give each one its own width as the basis so the row wraps + // at the intended card size. + "& > *": { flexBasis: GAUGE_MAX_WIDTH }, + }} > Date: Fri, 28 Aug 2026 11:22:29 -0400 Subject: [PATCH 02/22] fix(infrastructure): clamp gauge arc and fix disk key fallback Two small pre-existing bugs in the infrastructure gauges. Arc overdraw. Gauge computed strokeLength from the raw `progress` prop while the label and the fill colour both used `progressWithinRange`, the value clamped to 0-100. A reading above 100% therefore swept the arc past a full circle - the offset (circumference - strokeLength) went negative - while the label still read "100.0%". A negative reading drew the arc backwards for the same reason. strokeLength now uses the clamped value, so out-of-range input stops at exactly one turn and matches what the label and colour already showed. Values in range are unaffected. The effect that animates the arc also listed `progress` in its dependency array while only reading `strokeLength`, so it re-ran the 100ms animation on changes that could not alter the arc (105% to 110%, say). Now that strokeLength is derived from the clamped value, the stale dependency is dropped. Disk key fallback. `disk?.device || 0 + idx` parses as `disk?.device || (0 + idx)` because + binds tighter than ||, so the fallback was just `idx` and the leading 0 did nothing. It happened to produce unique keys, but by accident rather than intent, and a disk whose device is the empty string collided with index 0. Replaced with a `disk-${idx}` template so the intent is explicit. Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd --- client/src/Components/design-elements/Gauge.tsx | 9 ++++++--- .../Pages/Infrastructure/Details/Components/Gauges.tsx | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/client/src/Components/design-elements/Gauge.tsx b/client/src/Components/design-elements/Gauge.tsx index 4130f67a7..d53ce4da5 100644 --- a/client/src/Components/design-elements/Gauge.tsx +++ b/client/src/Components/design-elements/Gauge.tsx @@ -39,9 +39,12 @@ export const Gauge = forwardRef(function Gauge( () => ({ circumference: 2 * Math.PI * radius, totalSize: radius * 2 + strokeWidth * 2, - strokeLength: (progress / 100) * (2 * Math.PI * radius), + // Uses the clamped value, like the label and the fill color: an + // out-of-range reading would otherwise sweep the arc past a full circle + // while the label read 100%. + strokeLength: (progressWithinRange / 100) * (2 * Math.PI * radius), }), - [radius, strokeWidth, progress] + [radius, strokeWidth, progressWithinRange] ); const [offset, setOffset] = useState(circumference); @@ -52,7 +55,7 @@ export const Gauge = forwardRef(function Gauge( }, 100); return () => clearTimeout(timer); - }, [progress, circumference, strokeLength]); + }, [circumference, strokeLength]); const fillColor = getInfraGaugeColor(progressWithinRange, theme); diff --git a/client/src/Pages/Infrastructure/Details/Components/Gauges.tsx b/client/src/Pages/Infrastructure/Details/Components/Gauges.tsx index e2b053aef..75741e6c2 100644 --- a/client/src/Pages/Infrastructure/Details/Components/Gauges.tsx +++ b/client/src/Pages/Infrastructure/Details/Components/Gauges.tsx @@ -48,7 +48,7 @@ export const InfraDetailsGauges = ({ {snapshot?.disk?.map((disk, idx) => { return ( Date: Fri, 28 Aug 2026 12:44:14 -0400 Subject: [PATCH 03/22] fix(infrastructure): show the container-namespace notice when it applies The network tab warns when Capture can only see a container's own network namespace, but the condition required EVERY reported interface to be named lo, lo0 or docker0. A containerised Capture usually also reports the container's veth under a host-looking name (eth0, ens3, ...), so the common case failed that test and the notice never appeared - leaving those users with plausible-but-meaningless traffic and nothing explaining why. Widening it to "any container-only name present" would have been wrong in the other direction: a host-level Capture on a machine running Docker legitimately reports docker0 alongside its real NIC, and would then have been warned incorrectly. Interface names alone cannot separate the two cases. What does is that in the container case nothing carries real traffic, while a genuine host has a busy NIC. The notice now fires when a container-only interface is present AND every interface is idle, using a 1 KB/s threshold - real NICs sit orders of magnitude above it, a container's veth sits at or near zero. Checked against six scenarios: the reported case (lo + ens3 + docker0, all near zero) now warns; a host with a busy NIC plus an idle docker0 does not; a fully namespaced container still warns as before; and a quiet host with no container-only names stays silent. The notice text is updated to match, since it previously described only the all-container-names case and did not explain that a host-looking interface can be the container's own. Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd --- .../Details/Components/ChartsNetwork.tsx | 46 ++++++++++++++++--- client/src/locales/en.json | 2 +- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/client/src/Pages/Infrastructure/Details/Components/ChartsNetwork.tsx b/client/src/Pages/Infrastructure/Details/Components/ChartsNetwork.tsx index c84a327fa..dbe78335e 100644 --- a/client/src/Pages/Infrastructure/Details/Components/ChartsNetwork.tsx +++ b/client/src/Pages/Infrastructure/Details/Components/ChartsNetwork.tsx @@ -63,19 +63,51 @@ const getChartConfigs = ( return configs; }; -// Loopback / bridge names Capture sees when it can only observe the container's -// own network namespace. When ALL reported interfaces match this set we assume -// the "no real interfaces visible" case and surface the notice. +// Loopback / bridge names Capture reports when it can only observe the +// container's own network namespace. const CONTAINER_ONLY_IFACE_NAMES = new Set(["lo", "lo0", "docker0"]); +// Below this, an interface is carrying no traffic worth charting. Real host +// NICs sit orders of magnitude above it; a container's veth sits at or near +// zero. +const IDLE_INTERFACE_BYTES_PER_SECOND = 1024; + +const interfaceIsIdle = (checks: HardwareCheckStats[], name: string): boolean => + checks.every((check) => { + const iface = check.net?.find((candidate) => candidate.name === name); + if (!iface) { + return true; + } + return ( + (iface.bytesSentPerSecond || 0) < IDLE_INTERFACE_BYTES_PER_SECOND && + (iface.deltaBytesRecv || 0) < IDLE_INTERFACE_BYTES_PER_SECOND + ); + }); + +// Capture running inside a container sees only that container's network +// namespace. It reports the loopback and bridge names above, and usually also +// the container's own veth under a host-looking name (eth0, ens3, ...) - so +// requiring EVERY interface to be container-only missed the common case, and +// those users saw plausible-but-meaningless traffic with no explanation. +// +// Interface names alone cannot separate the two cases: a host-level agent on a +// machine running Docker legitimately reports docker0 too. What distinguishes +// them is that in the container case nothing carries real traffic. So the +// notice fires when a container-only interface is present AND every interface +// is idle, which leaves a genuine host reporting docker0 alongside a busy NIC +// untouched. const onlyContainerVisibleInterfaces = (checks: HardwareCheckStats[]): boolean => { const named = new Set(); checks.forEach((c) => c.net?.forEach((iface) => named.add(iface.name))); - if (named.size === 0) return false; - for (const name of named) { - if (!CONTAINER_ONLY_IFACE_NAMES.has(name)) return false; + if (named.size === 0) { + return false; } - return true; + + const names = [...named]; + if (!names.some((name) => CONTAINER_ONLY_IFACE_NAMES.has(name))) { + return false; + } + return names.every((name) => interfaceIsIdle(checks, name)); }; export const InfraNetworkCharts = ({ diff --git a/client/src/locales/en.json b/client/src/locales/en.json index e82f84949..b9881b4e6 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -926,7 +926,7 @@ "memory": "Memory usage", "netBytesRecv": "{{name}} - Bytes Received", "netBytesSent": "{{name}} - Bytes Sent", - "loopbackOnlyNotice": "Capture is only reporting loopback or container-bridge interfaces (lo, lo0, docker0). This is normal when Capture runs inside a container that can only see its own network namespace — run Capture directly on the host to see real network activity.", + "loopbackOnlyNotice": "Capture is only seeing container network interfaces, and none of them are carrying real traffic. This is normal when Capture runs inside a container that can only observe its own network namespace — interfaces with host-like names (eth0, ens3) are the container's virtual interfaces, not the host's. Run Capture directly on the host to see real network activity.", "temp": "CPU temperature" } }, From 9a6602b9e1b1a1a9b8ca38d613b7033c1fd5dd34 Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Fri, 28 Aug 2026 19:13:33 -0400 Subject: [PATCH 04/22] fix(client): add hover states to tables, cards, stat boxes and icon buttons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clickable surfaces across the app set `cursor: pointer` but gave no visual response on hover, so rows and cards read as static text. Two separate causes: - `DataTable` never passed MUI's opt-in `hover` prop on `TableRow`, which is `false` by default, so no table in the app shaded on hover. One change here covers all 16 page tables (Uptime, Incidents, Tags, Team, Notifications, Maintenance, Proxies, Status pages, Checks, Geo checks, Infrastructure, PageSpeed and the three Logs tables). - `IconButton` gates its hover style behind `props => !props.disableRipple`, and the theme sets `disableRipple: true` globally, so the row action gear and the pagination arrows lost their background. `MenuItem` is unaffected — its hover sits on the base style — so action menu items already worked and are left alone. Adds a `HOVER.ROW` (0.025) constant and an `action.rowHover` palette token, defined in both light and dark palettes so the overlay direction is correct on each surface. Kept separate from MUI's shared `action.hover` so menu items, which read that token, are not restyled as a side effect. Also fixes `cursor` on expandable rows, which showed `default` despite being clickable to expand. Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd --- client/src/Components/actions-menu/index.tsx | 11 +++++++- .../Components/design-elements/StatBox.tsx | 7 ++++- .../src/Components/design-elements/Table.tsx | 28 +++++++++++++++++-- client/src/Utils/Theme/Palette.ts | 7 +++++ client/src/Utils/Theme/Theme.ts | 6 ++++ client/src/Utils/Theme/constants.ts | 1 + 6 files changed, 55 insertions(+), 5 deletions(-) diff --git a/client/src/Components/actions-menu/index.tsx b/client/src/Components/actions-menu/index.tsx index 3b2f3688a..726ef6821 100644 --- a/client/src/Components/actions-menu/index.tsx +++ b/client/src/Components/actions-menu/index.tsx @@ -3,6 +3,7 @@ import Menu from "@mui/material/Menu"; import MenuItem from "@mui/material/MenuItem"; import IconButton from "@mui/material/IconButton"; import { Settings } from "lucide-react"; +import { useTheme } from "@mui/material/styles"; export type ActionMenuItem = { id: number | string; @@ -12,6 +13,7 @@ export type ActionMenuItem = { }; export const ActionsMenu = ({ items }: { items: ActionMenuItem[] }) => { + const theme = useTheme(); const [anchorEl, setAnchorEl] = useState(null); const open = Boolean(anchorEl); @@ -27,7 +29,14 @@ export const ActionsMenu = ({ items }: { items: ActionMenuItem[] }) => { return (
- + diff --git a/client/src/Components/design-elements/Table.tsx b/client/src/Components/design-elements/Table.tsx index a02f9be8f..e4e636f80 100644 --- a/client/src/Components/design-elements/Table.tsx +++ b/client/src/Components/design-elements/Table.tsx @@ -108,6 +108,11 @@ export function DataTable< borderRadius: theme.shape.borderRadius, padding: theme.spacing(LAYOUT.XS), cursor: onRowClick ? "pointer" : "default", + ...(onRowClick && { + "&:hover": { + backgroundColor: theme.palette.action.rowHover, + }, + }), }} key={key} > @@ -207,6 +212,10 @@ export function DataTable< "& .MuiTableBody-root .MuiTableRow-root:last-child .MuiTableCell-root": { borderBottom: "none", }, + "& .MuiTableBody-root .MuiTableRow-root.is-clickable:hover .MuiTableCell-root": + { + backgroundColor: theme.palette.action.rowHover, + }, }} > @@ -231,8 +240,9 @@ export function DataTable< return ( { @@ -314,7 +324,13 @@ function TablePaginationActions(props: TablePaginationActionsProps) { return ( Date: Fri, 28 Aug 2026 19:19:00 -0400 Subject: [PATCH 05/22] feat(client): group sidebar navigation into four labelled sections MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sidebar listed twelve items in a flat column ordered roughly by when each feature was added, so nothing signalled that Uptime, Pagespeed and Infrastructure are the same kind of thing, or that Tags and Proxies are configuration rather than views. Adding a new monitor type made this worse: the natural place for a new entry is the end of the list, three rows below the item it most resembles. Groups the menu into Monitoring, Alerting, Reporting and Configuration. `getMenu` now returns groups of items rather than a flat array; the sidebar renders an eyebrow label per group. When collapsed to 64px there is no room for a label, so the labels are replaced by dividers between groups — the grouping survives as rhythm. Settings moves from the middle of the list to the end of Configuration. Adds four `components.sidebar.menu.groups.*` keys across all 19 locales. Also corrects the i18n note in CLAUDE.md, which claimed translations are managed through PoEditor. Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd --- CLAUDE.md | 2 +- client/src/Components/sidebar/Menu.tsx | 124 ++++++++++++++---------- client/src/Components/sidebar/index.tsx | 53 +++++++--- client/src/locales/ar.json | 8 +- client/src/locales/ca.json | 8 +- client/src/locales/cs.json | 8 +- client/src/locales/de.json | 8 +- client/src/locales/en.json | 8 +- client/src/locales/es.json | 8 +- client/src/locales/fi.json | 8 +- client/src/locales/fr.json | 8 +- client/src/locales/it.json | 8 +- client/src/locales/ja.json | 8 +- client/src/locales/pl.json | 8 +- client/src/locales/pt-BR.json | 8 +- client/src/locales/ru.json | 8 +- client/src/locales/th.json | 8 +- client/src/locales/tr.json | 8 +- client/src/locales/uk.json | 8 +- client/src/locales/vi.json | 8 +- client/src/locales/zh-CN.json | 8 +- client/src/locales/zh-TW.json | 8 +- 22 files changed, 248 insertions(+), 83 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 3e601b453..498858e16 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -109,7 +109,7 @@ client/src/ - **Data Fetching**: SWR + Axios - **Database**: MongoDB with Mongoose ODM - **Queue/Cache**: Redis + BullMQ + Pulse (cron scheduling) -- **i18n**: i18next + react-i18next (translations via PoEditor) +- **i18n**: i18next + react-i18next (non-English locales are machine translated, not managed through a translation service) --- diff --git a/client/src/Components/sidebar/Menu.tsx b/client/src/Components/sidebar/Menu.tsx index 93cd94623..e7c4b9581 100644 --- a/client/src/Components/sidebar/Menu.tsx +++ b/client/src/Components/sidebar/Menu.tsx @@ -23,64 +23,84 @@ import { export const getMenu = (t: Function) => { return [ { - name: t("components.sidebar.menu.uptime"), - path: "uptime", - icon: , + group: t("components.sidebar.menu.groups.monitoring"), + items: [ + { + name: t("components.sidebar.menu.uptime"), + path: "uptime", + icon: , + }, + { + name: t("components.sidebar.menu.pagespeed"), + path: "pagespeed", + icon: , + }, + { + name: t("components.sidebar.menu.infrastructure"), + path: "infrastructure", + icon: , + }, + ], }, { - name: t("components.sidebar.menu.pagespeed"), - path: "pagespeed", - icon: , + group: t("components.sidebar.menu.groups.alerting"), + items: [ + { + name: t("components.sidebar.menu.incidents"), + path: "incidents", + icon: , + }, + { + name: t("components.sidebar.menu.notifications"), + path: "notifications", + icon: , + }, + { + name: t("components.sidebar.menu.maintenance"), + path: "maintenance", + icon: , + }, + ], }, { - name: t("components.sidebar.menu.infrastructure"), - path: "infrastructure", - icon: , + group: t("components.sidebar.menu.groups.reporting"), + items: [ + { + name: t("components.sidebar.menu.statusPages"), + path: "status", + icon: , + }, + { + name: t("components.sidebar.menu.checks"), + path: "checks", + icon: , + }, + { + name: t("components.sidebar.menu.logs"), + path: "logs", + icon: , + }, + ], }, { - name: t("components.sidebar.menu.notifications"), - path: "notifications", - icon: , - }, - { - name: t("components.sidebar.menu.tags"), - path: "tags", - icon: , - }, - { - name: t("components.sidebar.menu.proxies"), - path: "proxies", - icon: , - }, - { - name: t("components.sidebar.menu.checks"), - path: "checks", - icon: , - }, - { - name: t("components.sidebar.menu.incidents"), - path: "incidents", - icon: , - }, - { - name: t("components.sidebar.menu.statusPages"), - path: "status", - icon: , - }, - { - name: t("components.sidebar.menu.maintenance"), - path: "maintenance", - icon: , - }, - { - name: t("components.sidebar.menu.logs"), - path: "logs", - icon: , - }, - { - name: t("components.sidebar.menu.settings"), - icon: , - path: "settings", + group: t("components.sidebar.menu.groups.configuration"), + items: [ + { + name: t("components.sidebar.menu.tags"), + path: "tags", + icon: , + }, + { + name: t("components.sidebar.menu.proxies"), + path: "proxies", + icon: , + }, + { + name: t("components.sidebar.menu.settings"), + path: "settings", + icon: , + }, + ], }, ]; }; diff --git a/client/src/Components/sidebar/index.tsx b/client/src/Components/sidebar/index.tsx index d92441b01..6c7bdfb78 100644 --- a/client/src/Components/sidebar/index.tsx +++ b/client/src/Components/sidebar/index.tsx @@ -1,6 +1,8 @@ import { useEffect } from "react"; import Backdrop from "@mui/material/Backdrop"; import Stack from "@mui/material/Stack"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; import List from "@mui/material/List"; import Divider from "@mui/material/Divider"; import { LAYOUT } from "@/Utils/Theme/constants"; @@ -89,17 +91,46 @@ export const Sidebar = () => { pt={theme.spacing(LAYOUT.MD)} pb={theme.spacing(LAYOUT.MD)} /> - {menu.map((item) => { - const selected = location.pathname.startsWith(`/${item.path}`); - return ( - handleNavClick(item.path)} - /> - ); - })} + {menu.map((group, groupIndex) => ( + + {collapsed ? ( + groupIndex > 0 && ( + + ) + ) : ( + + {group.group} + + )} + {group.items.map((item) => { + const selected = location.pathname.startsWith(`/${item.path}`); + return ( + handleNavClick(item.path)} + /> + ); + })} + + ))} diff --git a/client/src/locales/ar.json b/client/src/locales/ar.json index 55f34650d..44e849fbc 100644 --- a/client/src/locales/ar.json +++ b/client/src/locales/ar.json @@ -181,7 +181,13 @@ "statusPages": "صفحات الحالة", "maintenance": "الصيانة", "logs": "السجلات", - "settings": "الإعدادات" + "settings": "الإعدادات", + "groups": { + "monitoring": "المراقبة", + "alerting": "التنبيهات", + "reporting": "التقارير", + "configuration": "الإعدادات العامة" + } }, "bottomMenu": { "support": "الدعم", diff --git a/client/src/locales/ca.json b/client/src/locales/ca.json index 7e39eca86..541230c14 100644 --- a/client/src/locales/ca.json +++ b/client/src/locales/ca.json @@ -189,7 +189,13 @@ "statusPages": "Pàgines d'estat", "maintenance": "Manteniment", "logs": "Registres", - "settings": "Configuració" + "settings": "Configuració", + "groups": { + "monitoring": "Monitoratge", + "alerting": "Alertes", + "reporting": "Informes", + "configuration": "Configuració" + } }, "bottomMenu": { "support": "Suport", diff --git a/client/src/locales/cs.json b/client/src/locales/cs.json index d04b9b23b..7ca4d84e9 100644 --- a/client/src/locales/cs.json +++ b/client/src/locales/cs.json @@ -181,7 +181,13 @@ "statusPages": "Stavové stránky", "maintenance": "Údržba", "logs": "Záznamy", - "settings": "Nastavení" + "settings": "Nastavení", + "groups": { + "monitoring": "Monitorování", + "alerting": "Upozornění", + "reporting": "Přehledy", + "configuration": "Konfigurace" + } }, "bottomMenu": { "support": "Podpora", diff --git a/client/src/locales/de.json b/client/src/locales/de.json index d43d5d52a..2a8ff08f1 100644 --- a/client/src/locales/de.json +++ b/client/src/locales/de.json @@ -181,7 +181,13 @@ "statusPages": "Statusseite", "maintenance": "Wartung", "logs": "Protokolle", - "settings": "Einstellungen" + "settings": "Einstellungen", + "groups": { + "monitoring": "Überwachung", + "alerting": "Benachrichtigungen", + "reporting": "Berichte", + "configuration": "Konfiguration" + } }, "bottomMenu": { "support": "Support", diff --git a/client/src/locales/en.json b/client/src/locales/en.json index e82f84949..27efb63c8 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -211,7 +211,13 @@ "statusPages": "Status pages", "maintenance": "Maintenance", "logs": "Logs", - "settings": "Settings" + "settings": "Settings", + "groups": { + "monitoring": "Monitoring", + "alerting": "Alerting", + "reporting": "Reporting", + "configuration": "Configuration" + } }, "bottomMenu": { "support": "Support", diff --git a/client/src/locales/es.json b/client/src/locales/es.json index 589399a83..154c9eb58 100644 --- a/client/src/locales/es.json +++ b/client/src/locales/es.json @@ -181,7 +181,13 @@ "statusPages": "Páginas de estado", "maintenance": "Mantenimiento", "logs": "Registros", - "settings": "Configuración" + "settings": "Configuración", + "groups": { + "monitoring": "Monitorización", + "alerting": "Alertas", + "reporting": "Informes", + "configuration": "Configuración" + } }, "bottomMenu": { "support": "Soporte", diff --git a/client/src/locales/fi.json b/client/src/locales/fi.json index 030698d44..a25f9211c 100644 --- a/client/src/locales/fi.json +++ b/client/src/locales/fi.json @@ -181,7 +181,13 @@ "statusPages": "Tilasivut", "maintenance": "Huolto", "logs": "Lokit", - "settings": "Asetukset" + "settings": "Asetukset", + "groups": { + "monitoring": "Valvonta", + "alerting": "Hälytykset", + "reporting": "Raportointi", + "configuration": "Määritykset" + } }, "bottomMenu": { "support": "Tuki", diff --git a/client/src/locales/fr.json b/client/src/locales/fr.json index 1e0a6b096..fe967457c 100644 --- a/client/src/locales/fr.json +++ b/client/src/locales/fr.json @@ -181,7 +181,13 @@ "statusPages": "Pages de statut", "maintenance": "Maintenance", "logs": "Journaux", - "settings": "Paramètres" + "settings": "Paramètres", + "groups": { + "monitoring": "Surveillance", + "alerting": "Alertes", + "reporting": "Rapports", + "configuration": "Configuration" + } }, "bottomMenu": { "support": "Support", diff --git a/client/src/locales/it.json b/client/src/locales/it.json index 3d6d1ea16..4d931a759 100644 --- a/client/src/locales/it.json +++ b/client/src/locales/it.json @@ -193,7 +193,13 @@ "statusPages": "Pagine di stato", "maintenance": "Manutenzione", "logs": "Log", - "settings": "Impostazioni" + "settings": "Impostazioni", + "groups": { + "monitoring": "Monitoraggio", + "alerting": "Avvisi", + "reporting": "Report", + "configuration": "Configurazione" + } }, "bottomMenu": { "support": "Supporto", diff --git a/client/src/locales/ja.json b/client/src/locales/ja.json index 16e2ec641..215bffc65 100644 --- a/client/src/locales/ja.json +++ b/client/src/locales/ja.json @@ -181,7 +181,13 @@ "statusPages": "ステータスページ", "maintenance": "メンテナンス", "logs": "ログ", - "settings": "設定" + "settings": "設定", + "groups": { + "monitoring": "監視", + "alerting": "アラート", + "reporting": "レポート", + "configuration": "設定" + } }, "bottomMenu": { "support": "サポート", diff --git a/client/src/locales/pl.json b/client/src/locales/pl.json index 81509ee6f..617fac273 100644 --- a/client/src/locales/pl.json +++ b/client/src/locales/pl.json @@ -214,7 +214,13 @@ "settings": "Ustawienia", "statusPages": "Strony statusu", "tags": "Tagi", - "uptime": "Czas działania" + "uptime": "Czas działania", + "groups": { + "monitoring": "Monitorowanie", + "alerting": "Alerty", + "reporting": "Raporty", + "configuration": "Konfiguracja" + } }, "starPrompt": { "description": "Zobacz najnowsze wydania i pomóż rozwijać społeczność na GitHub", diff --git a/client/src/locales/pt-BR.json b/client/src/locales/pt-BR.json index c75ead3ef..7650f34b0 100644 --- a/client/src/locales/pt-BR.json +++ b/client/src/locales/pt-BR.json @@ -181,7 +181,13 @@ "statusPages": "Pagina de status", "maintenance": "Manutenção", "logs": "Registros", - "settings": "Configurações" + "settings": "Configurações", + "groups": { + "monitoring": "Monitoramento", + "alerting": "Alertas", + "reporting": "Relatórios", + "configuration": "Configuração" + } }, "bottomMenu": { "support": "Suporte", diff --git a/client/src/locales/ru.json b/client/src/locales/ru.json index cf5e486f7..fca859a26 100644 --- a/client/src/locales/ru.json +++ b/client/src/locales/ru.json @@ -181,7 +181,13 @@ "statusPages": "Страницы статуса", "maintenance": "Обслуживание", "logs": "Логи", - "settings": "Настройки" + "settings": "Настройки", + "groups": { + "monitoring": "Мониторинг", + "alerting": "Оповещения", + "reporting": "Отчёты", + "configuration": "Конфигурация" + } }, "bottomMenu": { "support": "Поддержка", diff --git a/client/src/locales/th.json b/client/src/locales/th.json index 66d354adc..071c8b12e 100644 --- a/client/src/locales/th.json +++ b/client/src/locales/th.json @@ -181,7 +181,13 @@ "statusPages": "หน้าสถานะ", "maintenance": "การบำรุงรักษา", "logs": "บันทึก", - "settings": "การตั้งค่า" + "settings": "การตั้งค่า", + "groups": { + "monitoring": "การตรวจสอบ", + "alerting": "การแจ้งเตือน", + "reporting": "รายงาน", + "configuration": "การกำหนดค่า" + } }, "bottomMenu": { "support": "การสนับสนุน", diff --git a/client/src/locales/tr.json b/client/src/locales/tr.json index 26f8aace0..10a855ba2 100644 --- a/client/src/locales/tr.json +++ b/client/src/locales/tr.json @@ -181,7 +181,13 @@ "statusPages": "Durum sayfaları", "maintenance": "Bakım", "logs": "Günlükler", - "settings": "Ayarlar" + "settings": "Ayarlar", + "groups": { + "monitoring": "İzleme", + "alerting": "Uyarılar", + "reporting": "Raporlama", + "configuration": "Yapılandırma" + } }, "bottomMenu": { "support": "Destek", diff --git a/client/src/locales/uk.json b/client/src/locales/uk.json index d1cb983d1..99c8b9e9d 100644 --- a/client/src/locales/uk.json +++ b/client/src/locales/uk.json @@ -181,7 +181,13 @@ "statusPages": "Сторінки статусу", "maintenance": "Обслуговування", "logs": "Журнали", - "settings": "Налаштування" + "settings": "Налаштування", + "groups": { + "monitoring": "Моніторинг", + "alerting": "Сповіщення", + "reporting": "Звіти", + "configuration": "Конфігурація" + } }, "bottomMenu": { "support": "Підтримка", diff --git a/client/src/locales/vi.json b/client/src/locales/vi.json index 81da1c6fc..0f2903793 100644 --- a/client/src/locales/vi.json +++ b/client/src/locales/vi.json @@ -181,7 +181,13 @@ "statusPages": "Trang trạng thái", "maintenance": "Bảo trì", "logs": "Nhật ký", - "settings": "Cài đặt" + "settings": "Cài đặt", + "groups": { + "monitoring": "Giám sát", + "alerting": "Cảnh báo", + "reporting": "Báo cáo", + "configuration": "Cấu hình" + } }, "bottomMenu": { "support": "Hỗ trợ", diff --git a/client/src/locales/zh-CN.json b/client/src/locales/zh-CN.json index 1a455d41c..e9b0d251c 100644 --- a/client/src/locales/zh-CN.json +++ b/client/src/locales/zh-CN.json @@ -181,7 +181,13 @@ "statusPages": "状态页", "maintenance": "维护", "logs": "日志", - "settings": "设置" + "settings": "设置", + "groups": { + "monitoring": "监控", + "alerting": "告警", + "reporting": "报表", + "configuration": "配置" + } }, "bottomMenu": { "support": "支持", diff --git a/client/src/locales/zh-TW.json b/client/src/locales/zh-TW.json index a15a2a2bb..102082d49 100644 --- a/client/src/locales/zh-TW.json +++ b/client/src/locales/zh-TW.json @@ -181,7 +181,13 @@ "statusPages": "狀態頁面", "maintenance": "維護", "logs": "日誌", - "settings": "設定" + "settings": "設定", + "groups": { + "monitoring": "監控", + "alerting": "警示", + "reporting": "報表", + "configuration": "設定" + } }, "bottomMenu": { "support": "支援", From 30881a0af1c9d4e7817a0b18bb5b60b7e6c08682 Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Fri, 28 Aug 2026 19:28:29 -0400 Subject: [PATCH 06/22] fix(client): drop invalid li wrapper from sidebar groups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The group wrapper was rendering as `
  • `, but the enclosing `List` is `component="nav"` and so renders a `