merge develop

This commit is contained in:
Alex Holliday committed 2026-08-31 10:26:38 -07:00
commit a7ee90e1ee
35 files changed
+1226 -633

No files matched your search

+1 -1
View File
@@ -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)
---
+10 -1
View File
@@ -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 | any>(null);
const open = Boolean(anchorEl);
@@ -27,7 +29,14 @@ export const ActionsMenu = ({ items }: { items: ActionMenuItem[] }) => {
return (
<div>
<IconButton onClick={handleClick}>
<IconButton
onClick={handleClick}
sx={{
"&:hover": {
backgroundColor: theme.palette.action.controlHover,
},
}}
>
<Settings
size={20}
strokeWidth={1.5}
@@ -12,6 +12,7 @@ type BaseChartProps = React.PropsWithChildren<{
title: string;
width?: number | string;
maxWidth?: number | string;
flexBasis?: number | string;
padding?: number | string | ResponsiveStyleValue<number | string>;
onClick?: () => void;
}>;
@@ -21,6 +22,7 @@ export const BaseChart = ({
title,
width = "100%",
maxWidth = "100%",
flexBasis = "0%",
padding,
onClick,
}: BaseChartProps) => {
@@ -32,6 +34,7 @@ export const BaseChart = ({
padding: padding ?? theme.spacing(LAYOUT.MD),
display: "flex",
flex: 1,
flexBasis,
width: width,
maxWidth: maxWidth,
...(onClick && {
@@ -39,9 +39,9 @@ export const Gauge = forwardRef<HTMLDivElement, GaugeProps>(function Gauge(
() => ({
circumference: 2 * Math.PI * radius,
totalSize: radius * 2 + strokeWidth * 2,
strokeLength: (progress / 100) * (2 * Math.PI * radius),
strokeLength: (progressWithinRange / 100) * (2 * Math.PI * radius),
}),
[radius, strokeWidth, progress]
[radius, strokeWidth, progressWithinRange]
);
const [offset, setOffset] = useState(circumference);
@@ -52,7 +52,7 @@ export const Gauge = forwardRef<HTMLDivElement, GaugeProps>(function Gauge(
}, 100);
return () => clearTimeout(timer);
}, [progress, circumference, strokeLength]);
}, [circumference, strokeLength]);
const fillColor = getInfraGaugeColor(progressWithinRange, theme);
@@ -123,6 +123,7 @@ export const DetailGauge = ({
lowerLabel,
lowerValue,
maxWidth = 225,
flexBasis = "0%",
}: {
title: string;
progress: number;
@@ -131,6 +132,7 @@ export const DetailGauge = ({
lowerLabel?: string;
lowerValue?: string | number;
maxWidth?: number;
flexBasis?: number | string;
}) => {
const theme = useTheme();
return (
@@ -138,6 +140,7 @@ export const DetailGauge = ({
icon={null}
title={title}
maxWidth={maxWidth}
flexBasis={flexBasis}
>
<Stack
alignItems={"center"}
@@ -11,9 +11,10 @@ import type { SxProps } from "@mui/material";
type GradientBox = React.PropsWithChildren<{
palette?: PaletteKey;
sx?: SxProps;
interactive?: boolean;
}>;
export const GradientBox = ({ children, palette, sx }: GradientBox) => {
export const GradientBox = ({ children, palette, sx, interactive }: GradientBox) => {
const theme = useTheme();
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
const isLight = theme.palette.mode === "light";
@@ -30,6 +31,12 @@ export const GradientBox = ({ children, palette, sx }: GradientBox) => {
padding: `${theme.spacing(4)} ${theme.spacing(8)}`,
width: isSmall ? `100%` : `calc(25% - (3 * ${theme.spacing(8)} / 4))`,
background: bg,
...(interactive && {
cursor: "pointer",
"&:hover": {
backgroundImage: `linear-gradient(${theme.palette.action.rowHover}, ${theme.palette.action.rowHover}), ${bg}`,
},
}),
...sx,
}}
>
@@ -62,10 +69,8 @@ export const StatBox = ({
return (
<GradientBox
palette={palette}
sx={{
...(sx as object),
...(onClick ? { cursor: "pointer", "&:hover": { opacity: 0.95 } } : {}),
}}
interactive={Boolean(onClick)}
sx={{ ...(sx as object) }}
>
<Stack onClick={onClick}>
<Box sx={{ display: "flex", alignItems: "center", gap: theme.spacing(2) }}>
@@ -79,6 +79,7 @@ export function DataTable<
};
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
const isInteractive = Boolean(onRowClick) || expandableRows;
if (data.length === 0 || headers.length === 0) {
return (
@@ -108,6 +109,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 +213,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,
},
}}
>
<TableHead>
@@ -231,8 +241,9 @@ export function DataTable<
return (
<Fragment key={key}>
<TableRow
className={isInteractive ? "is-clickable" : undefined}
sx={{
cursor: onRowClick ? "pointer" : "default",
cursor: isInteractive ? "pointer" : "default",
...(getRowSx?.(row) as object),
}}
onClick={() => {
@@ -292,6 +303,12 @@ interface TablePaginationActionsProps {
onPageChange: (event: React.MouseEvent<HTMLButtonElement>, newPage: number) => void;
}
const controlHoverSx = (theme: Theme): SxProps<Theme> => ({
"&:hover": {
backgroundColor: theme.palette.action.controlHover,
},
});
function TablePaginationActions(props: TablePaginationActionsProps) {
const theme = useTheme();
const { count, page, rowsPerPage, onPageChange } = props;
@@ -321,6 +338,7 @@ function TablePaginationActions(props: TablePaginationActionsProps) {
onClick={handleFirstPageButtonClick}
disabled={page === 0}
aria-label="first page"
sx={controlHoverSx(theme)}
>
{theme.direction === "rtl" ? (
<ChevronsRight
@@ -338,6 +356,7 @@ function TablePaginationActions(props: TablePaginationActionsProps) {
onClick={handleBackButtonClick}
disabled={page === 0}
aria-label="previous page"
sx={controlHoverSx(theme)}
>
{theme.direction === "rtl" ? (
<ChevronRight
@@ -355,6 +374,7 @@ function TablePaginationActions(props: TablePaginationActionsProps) {
onClick={handleNextButtonClick}
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
aria-label="next page"
sx={controlHoverSx(theme)}
>
{theme.direction === "rtl" ? (
<ChevronLeft
@@ -372,6 +392,7 @@ function TablePaginationActions(props: TablePaginationActionsProps) {
onClick={handleLastPageButtonClick}
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
aria-label="last page"
sx={controlHoverSx(theme)}
>
{theme.direction === "rtl" ? (
<ChevronsLeft
@@ -419,6 +440,7 @@ function HasMoreTablePaginationActions(props: HasMoreTablePaginationActionsProps
onClick={handleFirstPageButtonClick}
disabled={page === 0}
aria-label="first page"
sx={controlHoverSx(theme)}
>
{theme.direction === "rtl" ? (
<ChevronsRight
@@ -436,6 +458,7 @@ function HasMoreTablePaginationActions(props: HasMoreTablePaginationActionsProps
onClick={handleBackButtonClick}
disabled={page === 0}
aria-label="previous page"
sx={controlHoverSx(theme)}
>
{theme.direction === "rtl" ? (
<ChevronRight
@@ -453,6 +476,7 @@ function HasMoreTablePaginationActions(props: HasMoreTablePaginationActionsProps
onClick={handleNextButtonClick}
disabled={hasMore === false}
aria-label="next page"
sx={controlHoverSx(theme)}
>
{theme.direction === "rtl" ? (
<ChevronLeft
@@ -470,6 +494,7 @@ function HasMoreTablePaginationActions(props: HasMoreTablePaginationActionsProps
<IconButton
disabled={hasMore === false}
aria-label="next page"
sx={controlHoverSx(theme)}
>
<Ellipsis
size={20}
+77 -57
View File
@@ -24,69 +24,89 @@ import {
export const getMenu = (t: Function) => {
return [
{
name: t("components.sidebar.menu.uptime"),
path: "uptime",
icon: <Icon icon={Globe} />,
group: t("components.sidebar.menu.groups.monitoring"),
items: [
{
name: t("components.sidebar.menu.uptime"),
path: "uptime",
icon: <Icon icon={Globe} />,
},
{
name: t("components.sidebar.menu.pagespeed"),
path: "pagespeed",
icon: <Icon icon={Gauge} />,
},
{
name: t("components.sidebar.menu.infrastructure"),
path: "infrastructure",
icon: <Icon icon={Link} />,
},
{
name: t("components.sidebar.menu.docker"),
path: "docker",
icon: <Icon icon={Container} />,
},
],
},
{
name: t("components.sidebar.menu.pagespeed"),
path: "pagespeed",
icon: <Icon icon={Gauge} />,
group: t("components.sidebar.menu.groups.alerting"),
items: [
{
name: t("components.sidebar.menu.incidents"),
path: "incidents",
icon: <Icon icon={AlertTriangle} />,
},
{
name: t("components.sidebar.menu.notifications"),
path: "notifications",
icon: <Icon icon={Bell} />,
},
{
name: t("components.sidebar.menu.maintenance"),
path: "maintenance",
icon: <Icon icon={Wrench} />,
},
],
},
{
name: t("components.sidebar.menu.infrastructure"),
path: "infrastructure",
icon: <Icon icon={Link} />,
group: t("components.sidebar.menu.groups.reporting"),
items: [
{
name: t("components.sidebar.menu.statusPages"),
path: "status",
icon: <Icon icon={Wifi} />,
},
{
name: t("components.sidebar.menu.checks"),
path: "checks",
icon: <Icon icon={FileText} />,
},
{
name: t("components.sidebar.menu.logs"),
path: "logs",
icon: <Icon icon={ScrollText} />,
},
],
},
{
name: t("components.sidebar.menu.docker"),
path: "docker",
icon: <Icon icon={Container} />,
},
{
name: t("components.sidebar.menu.notifications"),
path: "notifications",
icon: <Icon icon={Bell} />,
},
{
name: t("components.sidebar.menu.tags"),
path: "tags",
icon: <Icon icon={Tag} />,
},
{
name: t("components.sidebar.menu.proxies"),
path: "proxies",
icon: <Icon icon={Waypoints} />,
},
{
name: t("components.sidebar.menu.checks"),
path: "checks",
icon: <Icon icon={FileText} />,
},
{
name: t("components.sidebar.menu.incidents"),
path: "incidents",
icon: <Icon icon={AlertTriangle} />,
},
{
name: t("components.sidebar.menu.statusPages"),
path: "status",
icon: <Icon icon={Wifi} />,
},
{
name: t("components.sidebar.menu.maintenance"),
path: "maintenance",
icon: <Icon icon={Wrench} />,
},
{
name: t("components.sidebar.menu.logs"),
path: "logs",
icon: <Icon icon={ScrollText} />,
},
{
name: t("components.sidebar.menu.settings"),
icon: <Icon icon={Settings} />,
path: "settings",
group: t("components.sidebar.menu.groups.configuration"),
items: [
{
name: t("components.sidebar.menu.tags"),
path: "tags",
icon: <Icon icon={Tag} />,
},
{
name: t("components.sidebar.menu.proxies"),
path: "proxies",
icon: <Icon icon={Waypoints} />,
},
{
name: t("components.sidebar.menu.settings"),
path: "settings",
icon: <Icon icon={Settings} />,
},
],
},
];
};
+50 -12
View File
@@ -1,9 +1,12 @@
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";
import { typographyLevels } from "@/Utils/Theme/Palette";
import { useSidebar } from "@/Hooks/useSidebar.js";
import { Logo } from "@/Components/sidebar/Logo";
import { getMenu, getAccountMenu } from "@/Components/sidebar/Menu";
@@ -29,7 +32,7 @@ export const Sidebar = () => {
const navigate = useNavigate();
const location = useLocation();
const { t } = useTranslation();
const { width, transition, collapsed } = useSidebar();
const { width, transition, transitionTiming, collapsed } = useSidebar();
const theme = useTheme();
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
const menu = getMenu(t);
@@ -89,17 +92,52 @@ export const Sidebar = () => {
pt={theme.spacing(LAYOUT.MD)}
pb={theme.spacing(LAYOUT.MD)}
/>
{menu.map((item) => {
const selected = location.pathname.startsWith(`/${item.path}`);
return (
<NavItem
key={item.path}
item={item}
selected={selected}
onClick={() => handleNavClick(item.path)}
/>
);
})}
{menu.map((group) => (
<Box key={group.group}>
<Box
position="relative"
overflow="hidden"
>
<Typography
variant="eyebrow"
component="div"
color={theme.palette.text.disabled}
fontSize={typographyLevels.s}
paddingLeft={theme.spacing(5)}
paddingTop={theme.spacing(LAYOUT.SM)}
paddingBottom={theme.spacing(LAYOUT.XXS)}
whiteSpace="nowrap"
sx={{
opacity: collapsed ? 0 : 1,
transition: `opacity ${transitionTiming}`,
}}
>
{group.group}
</Typography>
<Divider
sx={{
position: "absolute",
top: "50%",
left: 0,
right: 0,
opacity: collapsed ? 1 : 0,
transition: `opacity ${transitionTiming}`,
}}
/>
</Box>
{group.items.map((item) => {
const selected = location.pathname.startsWith(`/${item.path}`);
return (
<NavItem
key={item.path}
item={item}
selected={selected}
onClick={() => handleNavClick(item.path)}
/>
);
})}
</Box>
))}
</List>
<StarPrompt />
+3 -1
View File
@@ -6,7 +6,8 @@ const SIDEBAR_WIDTH_VAR = 250;
const SIDEBAR_COLLAPSED_WIDTH_VAR = 64;
// Transition timing for sidebar width changes
const SIDEBAR_TRANSITION = "width 650ms cubic-bezier(0.36, -0.01, 0, 0.77)";
const SIDEBAR_TRANSITION_TIMING = "650ms cubic-bezier(0.36, -0.01, 0, 0.77)";
const SIDEBAR_TRANSITION = `width ${SIDEBAR_TRANSITION_TIMING}`;
export const useSidebar = () => {
const collapsed = useSelector(
@@ -19,6 +20,7 @@ export const useSidebar = () => {
expandedWidth: SIDEBAR_WIDTH_VAR,
width: collapsed ? SIDEBAR_COLLAPSED_WIDTH_VAR : SIDEBAR_WIDTH_VAR,
transition: SIDEBAR_TRANSITION,
transitionTiming: SIDEBAR_TRANSITION_TIMING,
};
};
@@ -63,19 +63,42 @@ 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
);
});
const onlyContainerVisibleInterfaces = (checks: HardwareCheckStats[]): boolean => {
const named = new Set<string>();
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.every((name) => CONTAINER_ONLY_IFACE_NAMES.has(name))) {
return true;
}
if (!names.some((name) => CONTAINER_ONLY_IFACE_NAMES.has(name))) {
return false;
}
return names.every((name) => interfaceIsIdle(checks, name));
};
export const InfraNetworkCharts = ({
@@ -8,6 +8,8 @@ import { useTheme } from "@mui/material";
import useMediaQuery from "@mui/material/useMediaQuery";
import type { CheckSnapshot } from "@/Types/Check";
const GAUGE_MAX_WIDTH = 260;
export const InfraDetailsGauges = ({
snapshot,
}: {
@@ -26,37 +28,42 @@ export const InfraDetailsGauges = ({
direction={isSmall ? "column" : "row"}
spacing={theme.spacing(8)}
alignItems={"stretch"}
flexWrap={"wrap"}
useFlexGap
>
<DetailGauge
title={t("pages.infrastructure.gauges.memory.title")}
maxWidth={260}
maxWidth={GAUGE_MAX_WIDTH}
progress={(snapshot?.memory?.usage_percent || 0) * 100}
upperLabel={t("pages.infrastructure.gauges.memory.upperLabel")}
upperValue={prettyBytes(snapshot?.memory?.used_bytes || 0)}
lowerLabel={t("pages.infrastructure.gauges.memory.lowerLabel")}
lowerValue={prettyBytes(snapshot?.memory?.total_bytes || 0)}
flexBasis={isSmall ? "auto" : GAUGE_MAX_WIDTH}
/>
<DetailGauge
title={t("pages.infrastructure.gauges.cpu.title")}
maxWidth={260}
maxWidth={GAUGE_MAX_WIDTH}
progress={(snapshot?.cpu?.usage_percent || 0) * 100}
upperLabel={t("pages.infrastructure.gauges.cpu.upperLabel")}
upperValue={getFrequency(snapshot?.cpu?.current_frequency || 0)}
lowerLabel={t("pages.infrastructure.gauges.cpu.lowerLabel")}
lowerValue={getFrequency(snapshot?.cpu?.frequency || 0)}
flexBasis={isSmall ? "auto" : GAUGE_MAX_WIDTH}
/>
{snapshot?.disk?.map((disk, idx) => {
return (
<DetailGauge
key={disk?.device || 0 + idx}
key={disk?.device || `disk-${idx}`}
// title={`Disk ${idx} usage`}
title={t("pages.infrastructure.gauges.disk.title", { idx })}
maxWidth={260}
maxWidth={GAUGE_MAX_WIDTH}
progress={(disk.usage_percent || 0) * 100}
upperLabel={t("pages.infrastructure.gauges.disk.upperLabel")}
upperValue={prettyBytes(disk?.used_bytes || 0)}
lowerLabel={t("pages.infrastructure.gauges.disk.lowerLabel")}
lowerValue={prettyBytes(disk?.total_bytes || 0)}
flexBasis={isSmall ? "auto" : GAUGE_MAX_WIDTH}
/>
);
})}
+24 -1
View File
@@ -1,7 +1,8 @@
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { logger } from "@/Utils/logger";
import { SPACING, LAYOUT } from "@/Utils/Theme/constants";
import { BasePage, ConfigBox } from "@/Components/design-elements";
import { BasePage, ColoredLabel, ConfigBox } from "@/Components/design-elements";
import { Button } from "@/Components/inputs";
import { useTheme } from "@mui/material";
@@ -15,6 +16,7 @@ import { useGet, usePost, usePatch } from "@/Hooks/UseApi";
import { mutate } from "swr";
import { useParams, useNavigate } from "react-router-dom";
import type { Monitor } from "@/Types/Monitor";
import type { Tag } from "@/Types/Tag";
import { useForm, FormProvider } from "react-hook-form";
import { useEffect } from "react";
import { zodResolver } from "@hookform/resolvers/zod/dist/zod.js";
@@ -41,6 +43,7 @@ const CreateMaintenanceWindowPage = () => {
);
const { data: monitors } = useGet<Monitor[]>("/monitors/team");
const { data: tags } = useGet<Tag[]>("/tags/team");
const { post, loading: isPosting } = usePost();
const { patch, loading: isPatching } = usePatch();
@@ -203,6 +206,26 @@ const CreateMaintenanceWindowPage = () => {
"pages.maintenanceWindow.form.startTime.monitors.option.addMonitors.label"
)}
options={monitors ?? []}
renderOptionContent={(monitor) => (
<Stack
direction="row"
alignItems="center"
gap={theme.spacing(2)}
flexWrap="wrap"
>
<Typography>{monitor.name}</Typography>
{monitor.tags.map((tagId) => {
const tag = tags?.find(({ id }) => id === tagId);
return tag ? (
<ColoredLabel
key={tag.id}
text={tag.name}
color={tag.color}
/>
) : null;
})}
</Stack>
)}
/>
}
/>
File diff suppressed because it is too large. Load diff
+9
View File
@@ -1,4 +1,5 @@
import { alpha } from "@mui/material/styles";
import { HOVER } from "@/Utils/Theme/constants";
const typographyBase = 13;
@@ -39,6 +40,10 @@ export const lightPalette = {
primary: {
main: colors.brandGreen,
},
action: {
rowHover: alpha(colors.gray900, HOVER.ROW),
controlHover: alpha(colors.gray900, HOVER.CONTROL),
},
secondary: {
main: colors.gray200,
},
@@ -65,6 +70,10 @@ export const darkPalette = {
primary: {
main: colors.brandGreen,
},
action: {
rowHover: alpha("#FFFFFF", HOVER.ROW),
controlHover: alpha("#FFFFFF", HOVER.CONTROL),
},
secondary: {
main: colors.gray700,
},
+4
View File
@@ -13,6 +13,10 @@ declare module "@mui/material/styles" {
eyebrow?: React.CSSProperties;
fontFamilyMonospace?: string;
}
interface TypeAction {
rowHover: string;
controlHover: string;
}
interface Palette {
sidebar: { accent: string };
rowStatus: { running: string; paused: string };
+2
View File
@@ -20,4 +20,6 @@ export const LAYOUT = {
export const HOVER = {
DARKEN: 0.06, // This is a coefficient for darkening function
ROW: 0.025, // Overlay alpha for hoverable rows and cards
CONTROL: 0.05, // Stronger overlay for controls nested inside a hoverable row
} as const;
+14 -1
View File
@@ -181,7 +181,13 @@
"statusPages": "صفحات الحالة",
"maintenance": "الصيانة",
"logs": "السجلات",
"settings": "الإعدادات"
"settings": "الإعدادات",
"groups": {
"monitoring": "المراقبة",
"alerting": "التنبيهات",
"reporting": "التقارير",
"configuration": "الإعدادات العامة"
}
},
"bottomMenu": {
"support": "الدعم",
@@ -1141,6 +1147,13 @@
}
},
"settings": {
"tabs": {
"general": "عام",
"monitoring": "المراقبة",
"email": "البريد الإلكتروني",
"integrations": "التكاملات",
"data": "البيانات"
},
"form": {
"timezone": {
"title": "المنطقة الزمنية للعرض",
+14 -1
View File
@@ -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",
@@ -1141,6 +1147,13 @@
}
},
"settings": {
"tabs": {
"general": "General",
"monitoring": "Monitoratge",
"email": "Correu electrònic",
"integrations": "Integracions",
"data": "Dades"
},
"form": {
"timezone": {
"title": "Zona horària de visualització",
+14 -1
View File
@@ -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",
@@ -1141,6 +1147,13 @@
}
},
"settings": {
"tabs": {
"general": "Obecné",
"monitoring": "Monitorování",
"email": "E-mail",
"integrations": "Integrace",
"data": "Data"
},
"form": {
"timezone": {
"title": "Zobrazované časové pásmo",
+14 -1
View File
@@ -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",
@@ -1141,6 +1147,13 @@
}
},
"settings": {
"tabs": {
"general": "Allgemein",
"monitoring": "Überwachung",
"email": "E-Mail",
"integrations": "Integrationen",
"data": "Daten"
},
"form": {
"timezone": {
"title": "Anzeigesprache Zeitzone",
+17 -4
View File
@@ -212,7 +212,13 @@
"statusPages": "Status pages",
"maintenance": "Maintenance",
"logs": "Logs",
"settings": "Settings"
"settings": "Settings",
"groups": {
"monitoring": "Monitoring",
"alerting": "Alerting",
"reporting": "Reporting",
"configuration": "Configuration"
}
},
"bottomMenu": {
"support": "Support",
@@ -945,7 +951,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"
}
},
@@ -1352,6 +1358,13 @@
}
},
"settings": {
"tabs": {
"general": "General",
"monitoring": "Monitoring",
"email": "Email",
"integrations": "Integrations",
"data": "Data"
},
"form": {
"timezone": {
"title": "Display timezone",
@@ -1409,7 +1422,7 @@
}
},
"url": {
"title": "Monitor IP/URL on Status Page",
"title": "Monitor IP/URL on status page",
"description": "Display the IP address or URL of monitor on the public Status page. If it's disabled, only the monitor name will be shown to protect sensitive information.",
"option": {
"showURL": {
@@ -1441,7 +1454,7 @@
}
},
"thresholds": {
"title": "Global Thresholds",
"title": "Global thresholds",
"description": "Set global CPU, Memory, Disk, and Temperature thresholds for infrastructure monitoring. These apply to all hardware monitors unless overridden.",
"option": {
"cpu": {
+14 -1
View File
@@ -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",
@@ -1141,6 +1147,13 @@
}
},
"settings": {
"tabs": {
"general": "General",
"monitoring": "Monitorización",
"email": "Correo electrónico",
"integrations": "Integraciones",
"data": "Datos"
},
"form": {
"timezone": {
"title": "Zona horaria de visualización",
+14 -1
View File
@@ -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",
@@ -1141,6 +1147,13 @@
}
},
"settings": {
"tabs": {
"general": "Yleiset",
"monitoring": "Valvonta",
"email": "Sähköposti",
"integrations": "Integraatiot",
"data": "Tiedot"
},
"form": {
"timezone": {
"title": "Näytettävä aikavyöhyke",
+14 -1
View File
@@ -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",
@@ -1141,6 +1147,13 @@
}
},
"settings": {
"tabs": {
"general": "Général",
"monitoring": "Surveillance",
"email": "E-mail",
"integrations": "Intégrations",
"data": "Données"
},
"form": {
"timezone": {
"title": "Fuseau horaire d'affichage",
+14 -1
View File
@@ -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",
@@ -1237,6 +1243,13 @@
}
},
"settings": {
"tabs": {
"general": "Generale",
"monitoring": "Monitoraggio",
"email": "Email",
"integrations": "Integrazioni",
"data": "Dati"
},
"form": {
"timezone": {
"title": "Fuso orario di visualizzazione",
+14 -1
View File
@@ -181,7 +181,13 @@
"statusPages": "ステータスページ",
"maintenance": "メンテナンス",
"logs": "ログ",
"settings": "設定"
"settings": "設定",
"groups": {
"monitoring": "監視",
"alerting": "アラート",
"reporting": "レポート",
"configuration": "設定"
}
},
"bottomMenu": {
"support": "サポート",
@@ -1141,6 +1147,13 @@
}
},
"settings": {
"tabs": {
"general": "一般",
"monitoring": "監視",
"email": "メール",
"integrations": "連携",
"data": "データ"
},
"form": {
"timezone": {
"title": "表示タイムゾーン",
+14 -1
View File
@@ -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",
@@ -1207,6 +1213,13 @@
}
},
"settings": {
"tabs": {
"general": "Ogólne",
"monitoring": "Monitorowanie",
"email": "E-mail",
"integrations": "Integracje",
"data": "Dane"
},
"form": {
"about": {
"developedBy": "Opracowane przez Bluewave Labs",
+14 -1
View File
@@ -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",
@@ -1141,6 +1147,13 @@
}
},
"settings": {
"tabs": {
"general": "Geral",
"monitoring": "Monitoramento",
"email": "E-mail",
"integrations": "Integrações",
"data": "Dados"
},
"form": {
"timezone": {
"title": "Fuso horário de exibição",
+14 -1
View File
@@ -181,7 +181,13 @@
"statusPages": "Страницы статуса",
"maintenance": "Обслуживание",
"logs": "Логи",
"settings": "Настройки"
"settings": "Настройки",
"groups": {
"monitoring": "Мониторинг",
"alerting": "Оповещения",
"reporting": "Отчёты",
"configuration": "Конфигурация"
}
},
"bottomMenu": {
"support": "Поддержка",
@@ -1141,6 +1147,13 @@
}
},
"settings": {
"tabs": {
"general": "Общие",
"monitoring": "Мониторинг",
"email": "Электронная почта",
"integrations": "Интеграции",
"data": "Данные"
},
"form": {
"timezone": {
"title": "Часовой пояс отображения",
+14 -1
View File
@@ -181,7 +181,13 @@
"statusPages": "หน้าสถานะ",
"maintenance": "การบำรุงรักษา",
"logs": "บันทึก",
"settings": "การตั้งค่า"
"settings": "การตั้งค่า",
"groups": {
"monitoring": "การตรวจสอบ",
"alerting": "การแจ้งเตือน",
"reporting": "รายงาน",
"configuration": "การกำหนดค่า"
}
},
"bottomMenu": {
"support": "การสนับสนุน",
@@ -1141,6 +1147,13 @@
}
},
"settings": {
"tabs": {
"general": "ทั่วไป",
"monitoring": "การตรวจสอบ",
"email": "อีเมล",
"integrations": "การเชื่อมต่อ",
"data": "ข้อมูล"
},
"form": {
"timezone": {
"title": "เขตเวลาที่แสดง",
+14 -1
View File
@@ -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",
@@ -1141,6 +1147,13 @@
}
},
"settings": {
"tabs": {
"general": "Genel",
"monitoring": "İzleme",
"email": "E-posta",
"integrations": "Entegrasyonlar",
"data": "Veri"
},
"form": {
"timezone": {
"title": "Görüntüleme saat dilimi",
+14 -1
View File
@@ -181,7 +181,13 @@
"statusPages": "Сторінки статусу",
"maintenance": "Обслуговування",
"logs": "Журнали",
"settings": "Налаштування"
"settings": "Налаштування",
"groups": {
"monitoring": "Моніторинг",
"alerting": "Сповіщення",
"reporting": "Звіти",
"configuration": "Конфігурація"
}
},
"bottomMenu": {
"support": "Підтримка",
@@ -1141,6 +1147,13 @@
}
},
"settings": {
"tabs": {
"general": "Загальні",
"monitoring": "Моніторинг",
"email": "Електронна пошта",
"integrations": "Інтеграції",
"data": "Дані"
},
"form": {
"timezone": {
"title": "Часовий пояс відображення",
+14 -1
View File
@@ -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ợ",
@@ -1141,6 +1147,13 @@
}
},
"settings": {
"tabs": {
"general": "Chung",
"monitoring": "Giám sát",
"email": "Email",
"integrations": "Tích hợp",
"data": "Dữ liệu"
},
"form": {
"timezone": {
"title": "Múi giờ hiển thị",
+14 -1
View File
@@ -181,7 +181,13 @@
"statusPages": "状态页",
"maintenance": "维护",
"logs": "日志",
"settings": "设置"
"settings": "设置",
"groups": {
"monitoring": "监控",
"alerting": "告警",
"reporting": "报表",
"configuration": "配置"
}
},
"bottomMenu": {
"support": "支持",
@@ -1142,6 +1148,13 @@
}
},
"settings": {
"tabs": {
"general": "常规",
"monitoring": "监控",
"email": "邮件",
"integrations": "集成",
"data": "数据"
},
"form": {
"timezone": {
"title": "显示时区",
+14 -1
View File
@@ -181,7 +181,13 @@
"statusPages": "狀態頁面",
"maintenance": "維護",
"logs": "日誌",
"settings": "設定"
"settings": "設定",
"groups": {
"monitoring": "監控",
"alerting": "警示",
"reporting": "報表",
"configuration": "設定"
}
},
"bottomMenu": {
"support": "支援",
@@ -1141,6 +1147,13 @@
}
},
"settings": {
"tabs": {
"general": "一般",
"monitoring": "監控",
"email": "郵件",
"integrations": "整合",
"data": "資料"
},
"form": {
"timezone": {
"title": "顯示時區",