merge develop
This commit is contained in:
commit
a7ee90e1ee
35 files changed
+1226
-633
No files matched your search
@@ -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)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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} />,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
@@ -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 />
|
||||
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
+691
-518
File diff suppressed because it is too large.
Load diff
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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;
|
||||
@@ -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": "المنطقة الزمنية للعرض",
|
||||
|
||||
@@ -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ó",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "表示タイムゾーン",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Часовой пояс отображения",
|
||||
|
||||
@@ -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": "เขตเวลาที่แสดง",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Часовий пояс відображення",
|
||||
|
||||
@@ -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ị",
|
||||
|
||||
@@ -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": "显示时区",
|
||||
|
||||
@@ -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": "顯示時區",
|
||||
|
||||
Reference in new issue
Block a user