mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-05-19 07:58:46 -05:00
Improve UI consistency for Logs and Incidents pages
## Changes ### Logs page - Convert server logs from monospace text to DataTable with pagination - Add table columns: Timestamp, Level, Service, Method, Message - Add colored level badges (info=green, warn=yellow, error=red, debug=blue) - Add pagination with configurable rows per page - Align diagnostics gauge cards with 16px gap and responsive widths ### Incidents page - Redesign summary cards with consistent styling - Remove shadows and use 1px border matching other sections - Reduce icon sizes to 18-24px (sidebar-style) - Reduce font sizes (13px titles, 18-32px values) - Add titles to all three summary cards - Fix "Most Affected Monitor" showing "Unknown Monitor" when no incidents - Show "N/A" for avg resolution time when no incidents - Remove cluttered "Resolutions" progress bar section - Remove horizontal dividers between statistics rows - Standardize card padding and gap to 16px ### Uptime page - Fix oversized monitor names in table (removed h6 variant)
This commit is contained in:
@@ -23,12 +23,12 @@ const Host = ({ url, title, percentageColor, percentage, showURL, status }) => {
|
||||
direction="row"
|
||||
position="relative"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(5)}
|
||||
gap={theme.spacing(4)}
|
||||
>
|
||||
<Typography
|
||||
variant="h6"
|
||||
component="span"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
@@ -38,9 +38,9 @@ const Host = ({ url, title, percentageColor, percentage, showURL, status }) => {
|
||||
<>
|
||||
<Dot />
|
||||
<Typography
|
||||
variant="h6"
|
||||
component="span"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
fontWeight: 500,
|
||||
color: percentageColor,
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -43,39 +43,36 @@ const ActiveIncidentsPanel = ({ totalCount = 0, isLoading = false, error = null
|
||||
|
||||
if (!totalCount || totalCount === 0) {
|
||||
return (
|
||||
<SummaryCard>
|
||||
<SummaryCard title={t("incidentsPage.incidentsActivePanelTitle")}>
|
||||
<Stack
|
||||
direction="column"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
padding={theme.spacing(10)}
|
||||
gap={theme.spacing(4)}
|
||||
padding={theme.spacing(6)}
|
||||
gap={theme.spacing(2)}
|
||||
sx={{ flex: 1 }}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
color: theme.palette.success.main,
|
||||
"& svg": {
|
||||
width: 60,
|
||||
height: 60,
|
||||
|
||||
width: 24,
|
||||
height: 24,
|
||||
"& path": { stroke: "currentColor", strokeWidth: 2 },
|
||||
},
|
||||
mb: theme.spacing(2),
|
||||
mb: theme.spacing(1),
|
||||
}}
|
||||
>
|
||||
<CheckIcon />
|
||||
</Box>
|
||||
|
||||
<Typography
|
||||
variant="h1"
|
||||
color={theme.palette.primary.contrastTextSecondary}
|
||||
sx={{
|
||||
fontSize: 13,
|
||||
textTransform: "uppercase",
|
||||
fontWeight: 600,
|
||||
fontWeight: 500,
|
||||
textAlign: "center",
|
||||
color: theme.palette.success.lowContrast,
|
||||
letterSpacing: theme.spacing(0.4),
|
||||
}}
|
||||
>
|
||||
{t("incidentsPage.allSystemsAreOperational")}
|
||||
@@ -86,21 +83,20 @@ const ActiveIncidentsPanel = ({ totalCount = 0, isLoading = false, error = null
|
||||
}
|
||||
|
||||
return (
|
||||
<SummaryCard isHighPriority={true}>
|
||||
<SummaryCard title={t("incidentsPage.incidentsActivePanelTitle")}>
|
||||
<Stack
|
||||
direction="column"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
spacing={theme.spacing(4)}
|
||||
gap={theme.spacing(2)}
|
||||
sx={{ flex: 1 }}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
color: theme.palette.error.lowContrast,
|
||||
padding: theme.spacing(2),
|
||||
"& svg": {
|
||||
width: 60,
|
||||
height: 60,
|
||||
|
||||
width: 24,
|
||||
height: 24,
|
||||
"& path": { stroke: "currentColor", strokeWidth: 2 },
|
||||
},
|
||||
}}
|
||||
@@ -109,27 +105,15 @@ const ActiveIncidentsPanel = ({ totalCount = 0, isLoading = false, error = null
|
||||
</Box>
|
||||
|
||||
<Typography
|
||||
variant="h1"
|
||||
sx={{
|
||||
fontSize: `calc(${theme.typography.h1.fontSize} * 2.5)`,
|
||||
fontWeight: 700,
|
||||
fontSize: 32,
|
||||
fontWeight: 600,
|
||||
color: theme.palette.error.lowContrast,
|
||||
lineHeight: 1,
|
||||
}}
|
||||
>
|
||||
{totalCount}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h2"
|
||||
sx={{
|
||||
textTransform: "uppercase",
|
||||
fontWeight: 700,
|
||||
letterSpacing: theme.spacing(0.4),
|
||||
paddingTop: theme.spacing(3),
|
||||
}}
|
||||
>
|
||||
{t("incidentsPage.incidentsActivePanelTitle")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</SummaryCard>
|
||||
);
|
||||
|
||||
@@ -53,7 +53,7 @@ const IncidentsSummaryPanel = ({ updateTrigger }) => {
|
||||
<>
|
||||
<Grid
|
||||
container
|
||||
spacing={3}
|
||||
spacing={"16px"}
|
||||
>
|
||||
<Grid
|
||||
item
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";
|
||||
import PanelSkeleton from "../IncidentsSummaryPanel/skeleton.jsx";
|
||||
import IncidentItem from "./IncidentItem.jsx";
|
||||
import SummaryCard from "../SummaryCard/index.jsx";
|
||||
import CheckIcon from "@/assets/icons/check-icon.svg?react";
|
||||
|
||||
/**
|
||||
* LatestIncidentsPanel Component
|
||||
@@ -51,21 +52,40 @@ const LatestIncidentsPanel = ({ incidents = [], isLoading = false, error = null
|
||||
return (
|
||||
<SummaryCard title={t("incidentsPage.incidentsLatestPanelTitle")}>
|
||||
{!incidents || incidents.length === 0 ? (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
<Stack
|
||||
direction="column"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
padding={theme.spacing(6)}
|
||||
gap={theme.spacing(2)}
|
||||
sx={{ flex: 1 }}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
color: theme.palette.success.main,
|
||||
"& svg": {
|
||||
width: 24,
|
||||
height: 24,
|
||||
"& path": { stroke: "currentColor", strokeWidth: 2 },
|
||||
},
|
||||
mb: theme.spacing(1),
|
||||
}}
|
||||
>
|
||||
<CheckIcon />
|
||||
</Box>
|
||||
|
||||
<Typography
|
||||
variant="body2"
|
||||
textAlign="center"
|
||||
sx={{
|
||||
fontSize: 13,
|
||||
textTransform: "uppercase",
|
||||
fontWeight: 500,
|
||||
textAlign: "center",
|
||||
color: theme.palette.success.lowContrast,
|
||||
}}
|
||||
>
|
||||
{t("incidentsPage.incidentsLatestPanelEmpty")}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
) : (
|
||||
<Stack gap={theme.spacing(4)}>
|
||||
{incidents.map((incident, index) => (
|
||||
|
||||
@@ -3,10 +3,8 @@ import { Box, Stack, Typography } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import PanelSkeleton from "../IncidentsSummaryPanel/skeleton.jsx";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Divider } from "@mui/material";
|
||||
import Clock from "@/assets/icons/maintenance.svg?react";
|
||||
import Incidents from "@/assets/icons/incidents.svg?react";
|
||||
import ResolutionItem from "@/assets/icons/interval-check.svg?react";
|
||||
import NotificationIcon from "@/assets/icons/notifications.svg?react";
|
||||
import SummaryCard from "../SummaryCard/index.jsx";
|
||||
|
||||
@@ -43,10 +41,15 @@ const StatisticsPanel = ({ isLoading = false, error = null, summary = {} }) => {
|
||||
const iconWrapperStyle = {
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
mx: theme.spacing(3),
|
||||
mx: theme.spacing(2),
|
||||
color: theme.palette.primary.contrastTextTertiary,
|
||||
"& svg": {
|
||||
width: 18,
|
||||
height: 18,
|
||||
},
|
||||
"& svg path": {
|
||||
stroke: "currentColor",
|
||||
strokeWidth: 1.5,
|
||||
},
|
||||
};
|
||||
if (isLoading) {
|
||||
@@ -76,6 +79,13 @@ const StatisticsPanel = ({ isLoading = false, error = null, summary = {} }) => {
|
||||
);
|
||||
}
|
||||
|
||||
const getMostAffectedMonitor = () => {
|
||||
if (!summary.total || summary.total === 0) {
|
||||
return t("incidentsPage.none");
|
||||
}
|
||||
return summary.topMonitor?.monitorName || t("incidentsPage.none");
|
||||
};
|
||||
|
||||
return (
|
||||
<SummaryCard title={t("incidentsPage.incidentsStatisticsPanelTitle")}>
|
||||
<Stack gap={theme.spacing(4)}>
|
||||
@@ -99,11 +109,10 @@ const StatisticsPanel = ({ isLoading = false, error = null, summary = {} }) => {
|
||||
lineHeight: 1.2,
|
||||
}}
|
||||
>
|
||||
{t("incidentsPage.totalIncidents")} : {summary.total || 0}
|
||||
{t("incidentsPage.totalIncidents")}: {summary.total || 0}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Divider />
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
@@ -120,12 +129,10 @@ const StatisticsPanel = ({ isLoading = false, error = null, summary = {} }) => {
|
||||
lineHeight: 1.2,
|
||||
}}
|
||||
>
|
||||
{t("incidentsPage.mostAffectedMonitor")} :{" "}
|
||||
{summary.topMonitor?.monitorName || t("incidentsPage.unknownMonitor")}
|
||||
{t("incidentsPage.mostAffectedMonitor")}: {getMostAffectedMonitor()}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Divider />
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
@@ -138,88 +145,14 @@ const StatisticsPanel = ({ isLoading = false, error = null, summary = {} }) => {
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
{t("incidentsPage.avgResolutionTime")} :{" "}
|
||||
{summary.avgResolutionTimeHours || 0} {t("incidentsPage.hours")}
|
||||
{t("incidentsPage.avgResolutionTime")}:{" "}
|
||||
{summary.total > 0 ? `${summary.avgResolutionTimeHours || 0} ${t("incidentsPage.hours")}` : "N/A"}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Divider />
|
||||
|
||||
<Box padding={theme.spacing(2)}>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(2)}
|
||||
mb={theme.spacing(1.5)}
|
||||
>
|
||||
<Box sx={iconWrapperStyle}>
|
||||
<ResolutionItem />
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
fontWeight: 500,
|
||||
lineHeight: 1.2,
|
||||
}}
|
||||
>
|
||||
{t("incidentsPage.resolutions")}: {totalResolutions}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
height: 8,
|
||||
borderRadius: 4,
|
||||
overflow: "hidden",
|
||||
bgcolor: theme.palette.accent.main,
|
||||
width: "100%",
|
||||
marginTop: theme.spacing(4),
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: `${automaticPercentage}%`,
|
||||
|
||||
bgcolor: theme.palette.warningSecondary.lowContrast,
|
||||
height: "100%",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
paddingTop: theme.spacing(4),
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
{summary.totalAutomaticResolutions > 0 && (
|
||||
<Typography
|
||||
variant="body2"
|
||||
fontWeight={500}
|
||||
color={theme.palette.warningSecondary.contrastText}
|
||||
>
|
||||
{t("incidentsPage.automatic")} ({summary.totalAutomaticResolutions})
|
||||
</Typography>
|
||||
)}
|
||||
{summary.totalManualResolutions > 0 && (
|
||||
<Typography
|
||||
variant="body2"
|
||||
fontWeight={500}
|
||||
color={theme.palette.accent.main}
|
||||
>
|
||||
{t("incidentsPage.manual")} ({summary.totalManualResolutions})
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
</SummaryCard>
|
||||
);
|
||||
|
||||
@@ -8,17 +8,14 @@ const SummaryCard = ({ children, isHighPriority = false, sx = {}, title = null }
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
padding: theme.spacing(4),
|
||||
borderRadius: 3,
|
||||
padding: "16px",
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
border: isHighPriority
|
||||
? `${theme.spacing(1.5)} solid ${theme.palette.error.lowContrast}`
|
||||
: `${theme.spacing(1)} solid ${theme.palette.divider}`,
|
||||
boxShadow: theme.palette.tertiary.cardShadow,
|
||||
border: `1px solid ${theme.palette.primary.lowContrast}`,
|
||||
color: theme.palette.primary.contrastTextTertiary,
|
||||
fontSize: theme.typography.body1.fontSize,
|
||||
...sx,
|
||||
@@ -33,13 +30,12 @@ const SummaryCard = ({ children, isHighPriority = false, sx = {}, title = null }
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h6"
|
||||
component="h2"
|
||||
sx={{
|
||||
textTransform: "uppercase",
|
||||
fontWeight: 700,
|
||||
fontSize: theme.typography.h2.fontSize,
|
||||
|
||||
letterSpacing: theme.spacing(0.5),
|
||||
fontWeight: 500,
|
||||
fontSize: 13,
|
||||
color: theme.palette.primary.contrastTextSecondary,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
@@ -49,9 +45,9 @@ const SummaryCard = ({ children, isHighPriority = false, sx = {}, title = null }
|
||||
</Box>
|
||||
)}
|
||||
<Stack
|
||||
mt={theme.spacing(4)}
|
||||
paddingTop={theme.spacing(5)}
|
||||
gap={theme.spacing(4)}
|
||||
mt={theme.spacing(2)}
|
||||
paddingTop={theme.spacing(2)}
|
||||
gap={theme.spacing(2)}
|
||||
sx={{ height: "100%" }}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -12,13 +12,16 @@ import { Box } from "@mui/material";
|
||||
const BaseContainer = ({children}) => {
|
||||
const theme = useTheme()
|
||||
return(
|
||||
<Box
|
||||
<Box
|
||||
sx={{
|
||||
padding: theme.spacing(3),
|
||||
borderRadius: theme.spacing(2),
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
minWidth: 250,
|
||||
width: "fit-content",
|
||||
borderRadius: 4,
|
||||
border: `1px solid ${theme.palette.primary.lowContrast}`,
|
||||
minWidth: 200,
|
||||
width: `calc(25% - (3 * ${theme.spacing(8)} / 4))`,
|
||||
[theme.breakpoints.down("md")]: {
|
||||
width: `calc(50% - (1 * ${theme.spacing(8)} / 2))`,
|
||||
},
|
||||
}}>
|
||||
{children}
|
||||
</Box>
|
||||
@@ -99,7 +102,7 @@ const Gauges = ({ diagnostics, isLoading }) => {
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={theme.spacing(8)}
|
||||
gap={theme.spacing(8)}
|
||||
flexWrap="wrap"
|
||||
>
|
||||
<InfrastructureStyleGauge
|
||||
|
||||
@@ -2,14 +2,17 @@ import Stack from "@mui/material/Stack";
|
||||
import Box from "@mui/material/Box";
|
||||
import Select from "@/Components/v1/Inputs/Select/index.jsx";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Divider from "@mui/material/Divider";
|
||||
import DataTable from "@/Components/v1/Table/index.jsx";
|
||||
import Pagination from "@/Components/v1/Table/TablePagination/index.jsx";
|
||||
|
||||
import { useFetchLogs } from "../../../Hooks/logHooks.js";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const formatLog = (theme, log, idx) => {
|
||||
const LevelBadge = ({ level }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const levelColors = {
|
||||
info: theme.palette.success.main,
|
||||
warn: theme.palette.warning.main,
|
||||
@@ -17,31 +20,38 @@ const formatLog = (theme, log, idx) => {
|
||||
debug: theme.palette.accent.main,
|
||||
};
|
||||
|
||||
const color = levelColors[log.level] || theme.palette.primary.contrastText;
|
||||
const color = levelColors[level] || theme.palette.primary.contrastText;
|
||||
|
||||
return (
|
||||
<span key={idx}>
|
||||
<span>[{log.timestamp}]</span>{" "}
|
||||
<span style={{ color, fontWeight: "bold" }}>{log.level.toUpperCase()}</span>
|
||||
{": "}
|
||||
{`(${log.service})`}
|
||||
{`(${log.method})`}
|
||||
{": "}
|
||||
{log.message}
|
||||
<br />
|
||||
</span>
|
||||
<Box
|
||||
component="span"
|
||||
sx={{
|
||||
color: color,
|
||||
fontWeight: 600,
|
||||
textTransform: "uppercase",
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{level}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const Logs = () => {
|
||||
// Local state
|
||||
const [logLevel, setLogLevel] = useState("all");
|
||||
const formatTimestamp = (timestamp) => {
|
||||
if (!timestamp) return "-";
|
||||
const date = new Date(timestamp);
|
||||
return date.toLocaleString();
|
||||
};
|
||||
|
||||
const Logs = () => {
|
||||
const [logLevel, setLogLevel] = useState("all");
|
||||
const [page, setPage] = useState(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(15);
|
||||
|
||||
// Hooks
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const [logs, isLoading, error] = useFetchLogs();
|
||||
// Setup
|
||||
|
||||
const LOG_LEVELS = [
|
||||
{ _id: "all", name: t("logsPage.logLevelSelect.values.all") },
|
||||
{ _id: "info", name: t("logsPage.logLevelSelect.values.info") },
|
||||
@@ -49,52 +59,116 @@ const Logs = () => {
|
||||
{ _id: "error", name: t("logsPage.logLevelSelect.values.error") },
|
||||
{ _id: "debug", name: t("logsPage.logLevelSelect.values.debug") },
|
||||
];
|
||||
|
||||
const headers = [
|
||||
{
|
||||
id: "timestamp",
|
||||
content: t("logsPage.table.timestamp"),
|
||||
render: (row) => (
|
||||
<Typography sx={{ fontSize: 13, fontFamily: "monospace" }}>
|
||||
{formatTimestamp(row.timestamp)}
|
||||
</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "level",
|
||||
content: t("logsPage.table.level"),
|
||||
render: (row) => <LevelBadge level={row.level} />,
|
||||
},
|
||||
{
|
||||
id: "service",
|
||||
content: t("logsPage.table.service"),
|
||||
render: (row) => (
|
||||
<Typography sx={{ fontSize: 13 }}>{row.service || "-"}</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "method",
|
||||
content: t("logsPage.table.method"),
|
||||
render: (row) => (
|
||||
<Typography sx={{ fontSize: 13, fontFamily: "monospace" }}>{row.method || "-"}</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "message",
|
||||
content: t("logsPage.table.message"),
|
||||
render: (row) => (
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: 13,
|
||||
maxWidth: 400,
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
{row.message || "-"}
|
||||
</Typography>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const filteredLogs = logs
|
||||
?.filter((log) => {
|
||||
if (logLevel === "all") return true;
|
||||
return log.level === logLevel;
|
||||
})
|
||||
.reverse()
|
||||
.map((log, idx) => ({ ...log, id: idx }));
|
||||
|
||||
const paginatedLogs = filteredLogs?.slice(
|
||||
page * rowsPerPage,
|
||||
page * rowsPerPage + rowsPerPage
|
||||
);
|
||||
|
||||
const handleChangePage = (event, newPage) => {
|
||||
setPage(newPage);
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (event) => {
|
||||
setRowsPerPage(parseInt(event.target.value, 10));
|
||||
setPage(0);
|
||||
};
|
||||
|
||||
const handleLogLevelChange = (e) => {
|
||||
setLogLevel(e.target.value);
|
||||
setPage(0);
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack gap={theme.spacing(4)}>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(4)}
|
||||
sx={{
|
||||
position: "sticky",
|
||||
top: theme.spacing(17),
|
||||
backdropFilter: "blur(10px)",
|
||||
paddingTop: theme.spacing(4),
|
||||
paddingLeft: theme.spacing(6),
|
||||
}}
|
||||
>
|
||||
<Typography>{t("logsPage.logLevelSelect.title")}</Typography>
|
||||
<Select
|
||||
items={LOG_LEVELS}
|
||||
value={logLevel}
|
||||
onChange={(e) => {
|
||||
setLogLevel(e.target.value);
|
||||
}}
|
||||
onChange={handleLogLevelChange}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<Box
|
||||
component="pre"
|
||||
sx={{
|
||||
fontFamily: "monospace",
|
||||
color: theme.palette.primary.contrastText,
|
||||
padding: 2,
|
||||
borderRadius: 1,
|
||||
overflowX: "auto",
|
||||
whiteSpace: "pre-wrap",
|
||||
wordWrap: "break-word",
|
||||
<DataTable
|
||||
shouldRender={!isLoading}
|
||||
headers={headers}
|
||||
data={paginatedLogs || []}
|
||||
config={{
|
||||
emptyView: t("logsPage.noLogs"),
|
||||
}}
|
||||
>
|
||||
<code>
|
||||
{logs
|
||||
?.filter((log) => {
|
||||
if (logLevel === "all") return true;
|
||||
return log.level === logLevel;
|
||||
})
|
||||
.reverse()
|
||||
.map((log, idx) => formatLog(theme, log, idx))}
|
||||
</code>
|
||||
</Box>
|
||||
/>
|
||||
|
||||
{filteredLogs?.length > 0 && (
|
||||
<Pagination
|
||||
paginationLabel={t("logsPage.table.logs")}
|
||||
itemCount={filteredLogs?.length || 0}
|
||||
page={page}
|
||||
rowsPerPage={rowsPerPage}
|
||||
handleChangePage={handleChangePage}
|
||||
handleChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -791,7 +791,16 @@
|
||||
"error": "Error",
|
||||
"debug": "Debug"
|
||||
}
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"timestamp": "Timestamp",
|
||||
"level": "Level",
|
||||
"service": "Service",
|
||||
"method": "Method",
|
||||
"message": "Message",
|
||||
"logs": "logs"
|
||||
},
|
||||
"noLogs": "No logs found"
|
||||
},
|
||||
"queuePage": {
|
||||
"title": "Queue",
|
||||
@@ -1195,6 +1204,7 @@
|
||||
"incidentsOptionsHeaderFilterResolved": "Resolved",
|
||||
"incidentsTableResolved": "Closed",
|
||||
"incidentsTableActionResolveManually": "Resolve Manually",
|
||||
"hours": "hours"
|
||||
"hours": "hours",
|
||||
"none": "None"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user