This commit is contained in:
Alex Holliday
2026-02-06 18:56:10 +00:00
parent 8470631162
commit ea115ede53
19 changed files with 10 additions and 997 deletions
@@ -1,188 +0,0 @@
import Stack from "@mui/material/Stack";
import CustomGauge from "@/Components/v1/Charts/CustomGauge/index.jsx";
import Typography from "@mui/material/Typography";
// Utils
import { useTheme } from "@emotion/react";
import PropTypes from "prop-types";
import { getPercentage, formatBytes } from "../../utils/utils.js";
import { useTranslation } from "react-i18next";
import { Box } from "@mui/material";
const BaseContainer = ({ children }) => {
const theme = useTheme();
return (
<Box
sx={{
padding: theme.spacing(3),
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>
);
};
const InfrastructureStyleGauge = ({
value,
heading,
metricOne,
valueOne,
metricTwo,
valueTwo,
}) => {
const theme = useTheme();
const MetricRow = ({ label, value }) => (
<Stack
justifyContent="space-between"
direction="row"
alignItems="center"
gap={theme.spacing(2)}
>
<Typography>{label}</Typography>
<Typography
sx={{
borderRadius: theme.spacing(2),
backgroundColor: theme.palette.tertiary.main,
width: "40%",
mb: theme.spacing(2),
mt: theme.spacing(2),
pr: theme.spacing(2),
textAlign: "right",
}}
>
{value}
</Typography>
</Stack>
);
return (
<BaseContainer>
<Stack
direction="column"
gap={theme.spacing(2)}
alignItems="center"
>
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
width: "100%",
}}
>
<CustomGauge
progress={value}
radius={100}
/>
<Typography
component="h2"
sx={{ fontWeight: 600 }}
>
{heading}
</Typography>
</Box>
<Box sx={{ width: "100%", borderTop: `1px solid ${theme.palette.divider}` }}>
<MetricRow
label={metricOne}
value={valueOne}
/>
{metricTwo && valueTwo && (
<MetricRow
label={metricTwo}
value={valueTwo}
/>
)}
</Box>
</Stack>
</BaseContainer>
);
};
const Gauges = ({ diagnostics, isLoading }) => {
const heapTotalSize = getPercentage(
diagnostics?.v8HeapStats?.totalHeapSizeBytes,
diagnostics?.v8HeapStats?.heapSizeLimitBytes
);
const heapUsedSize = getPercentage(
diagnostics?.v8HeapStats?.usedHeapSizeBytes,
diagnostics?.v8HeapStats?.heapSizeLimitBytes
);
const actualHeapUsed = getPercentage(
diagnostics?.v8HeapStats?.usedHeapSizeBytes,
diagnostics?.v8HeapStats?.totalHeapSizeBytes
);
const theme = useTheme();
const { t } = useTranslation();
return (
<Stack
direction="row"
gap={theme.spacing(8)}
flexWrap="wrap"
>
<InfrastructureStyleGauge
value={heapTotalSize}
heading={t("diagnosticsPage.gauges.heapAllocationTitle")}
metricOne={t("diagnosticsPage.gauges.heapAllocationSubtitle")}
valueOne={`${heapTotalSize?.toFixed(1) || 0}%`}
metricTwo={t("total")}
valueTwo={formatBytes(diagnostics?.v8HeapStats?.heapSizeLimitBytes)}
/>
<InfrastructureStyleGauge
value={heapUsedSize}
heading={t("diagnosticsPage.gauges.heapUsageTitle")}
metricOne={t("diagnosticsPage.gauges.heapUsageSubtitle")}
valueOne={`${heapUsedSize?.toFixed(1) || 0}%`}
metricTwo={t("used")}
valueTwo={formatBytes(diagnostics?.v8HeapStats?.usedHeapSizeBytes)}
/>
<InfrastructureStyleGauge
value={actualHeapUsed}
heading={t("diagnosticsPage.gauges.heapUtilizationTitle")}
metricOne={t("diagnosticsPage.gauges.heapUtilizationSubtitle")}
valueOne={`${actualHeapUsed?.toFixed(1) || 0}%`}
metricTwo={t("total")}
valueTwo={formatBytes(diagnostics?.v8HeapStats?.totalHeapSizeBytes)}
/>
<InfrastructureStyleGauge
value={diagnostics?.cpuUsage?.usagePercentage}
heading={t("diagnosticsPage.gauges.instantCpuUsageTitle")}
metricOne={t("diagnosticsPage.gauges.instantCpuUsageSubtitle")}
valueOne={`${diagnostics?.cpuUsage?.usagePercentage?.toFixed(1) || 0}%`}
metricTwo=""
valueTwo=""
/>
</Stack>
);
};
Gauges.propTypes = {
diagnostics: PropTypes.object,
isLoading: PropTypes.bool,
};
InfrastructureStyleGauge.propTypes = {
value: PropTypes.number,
heading: PropTypes.string,
metricOne: PropTypes.string,
valueOne: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
metricTwo: PropTypes.string,
valueTwo: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
};
BaseContainer.propTypes = {
children: PropTypes.node.isRequired,
};
export default Gauges;
@@ -1,97 +0,0 @@
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import Card from "@mui/material/Card";
import CardContent from "@mui/material/CardContent";
import CircularProgress from "@mui/material/CircularProgress";
import { useTheme } from "@emotion/react";
import PropTypes from "prop-types";
import { getHumanReadableDuration } from "../../../../../Utils/timeUtilsLegacy.js";
import { formatBytes } from "../../utils/utils.js";
import { useTranslation } from "react-i18next";
const StatsCard = ({ title, value, unit = "", isLoading }) => {
const theme = useTheme();
return (
<Card sx={{ width: 150, maxWidth: 150, height: 80, maxHeight: 80 }}>
{isLoading ? (
<Stack
alignItems="center"
justifyContent="center"
height={80}
maxHeight={80}
>
<CircularProgress color="accent" />
</Stack>
) : (
<CardContent>
<Typography
variant="body1"
color={theme.palette.primary.contrastText}
>
{title}
</Typography>
<Typography variant="body1">
{value} {unit}
</Typography>
</CardContent>
)}
</Card>
);
};
StatsCard.propTypes = {
title: PropTypes.string,
value: PropTypes.string,
unit: PropTypes.string,
isLoading: PropTypes.bool,
};
const Stats = ({ diagnostics, isLoading }) => {
const theme = useTheme();
const { t } = useTranslation();
return (
<Stack
direction="row"
gap={theme.spacing(4)}
flexWrap="wrap"
>
<StatsCard
title={t("diagnosticsPage.stats.eventLoopDelayTitle")}
value={getHumanReadableDuration(diagnostics?.eventLoopDelayMs)}
isLoading={isLoading}
/>
<StatsCard
title={t("diagnosticsPage.stats.uptimeTitle")}
value={getHumanReadableDuration(diagnostics?.uptimeMs)}
isLoading={isLoading}
/>
<StatsCard
title={t("diagnosticsPage.stats.usedHeapSizeTitle")}
value={formatBytes(diagnostics?.v8HeapStats?.usedHeapSizeBytes)}
isLoading={isLoading}
/>
<StatsCard
title={t("diagnosticsPage.stats.totalHeapSizeTitle")}
value={formatBytes(diagnostics?.v8HeapStats?.totalHeapSizeBytes)}
isLoading={isLoading}
/>
<StatsCard
title={t("diagnosticsPage.stats.osMemoryLimitTitle")}
value={formatBytes(diagnostics?.osStats?.totalMemoryBytes)}
isLoading={isLoading}
/>
</Stack>
);
};
Stats.propTypes = {
diagnostics: PropTypes.object,
isLoading: PropTypes.bool,
};
export default Stats;
@@ -1,77 +0,0 @@
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import Gauges from "./components/gauges/index.jsx";
import Button from "@mui/material/Button";
import StatBox from "@/Components/v1/StatBox/index.jsx";
import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
import { useFetchDiagnostics } from "../../../Hooks/logHooks.js";
import { getHumanReadableDuration } from "../../../Utils/timeUtilsLegacy.js";
import { formatBytes, getPercentage } from "./utils/utils.js";
const Diagnostics = () => {
// Local state
// Hooks
const theme = useTheme();
const { t } = useTranslation();
const [diagnostics, fetchDiagnostics, isLoading, error] = useFetchDiagnostics();
// Setup
return (
<Stack gap={theme.spacing(10)}>
<StatusBoxes flexWrap="wrap">
<StatBox
gradient={true}
status="up"
heading={t("status")}
subHeading={
error
? t("logsPage.logLevelSelect.values.error")
: isLoading
? t("commonSaving")
: diagnostics
? t("diagnosticsPage.diagnosticDescription")
: t("general.noOptionsFound", { unit: "data" })
}
/>
<StatBox
heading={t("diagnosticsPage.stats.eventLoopDelayTitle")}
subHeading={getHumanReadableDuration(diagnostics?.eventLoopDelayMs)}
/>
<StatBox
heading={t("diagnosticsPage.stats.uptimeTitle")}
subHeading={getHumanReadableDuration(diagnostics?.uptimeMs)}
/>
<StatBox
heading={t("diagnosticsPage.stats.usedHeapSizeTitle")}
subHeading={formatBytes(diagnostics?.v8HeapStats?.usedHeapSizeBytes)}
/>
<StatBox
heading={t("diagnosticsPage.stats.totalHeapSizeTitle")}
subHeading={formatBytes(diagnostics?.v8HeapStats?.totalHeapSizeBytes)}
/>
<StatBox
heading={t("diagnosticsPage.stats.osMemoryLimitTitle")}
subHeading={formatBytes(diagnostics?.osStats?.totalMemoryBytes)}
/>
</StatusBoxes>
<Gauges
diagnostics={diagnostics}
isLoading={isLoading}
/>
<Box>
<Button
variant="contained"
color="accent"
onClick={fetchDiagnostics}
loading={isLoading}
>
{t("queuePage.refreshButton")}
</Button>
</Box>
</Stack>
);
};
export default Diagnostics;
@@ -1,16 +0,0 @@
export const getPercentage = (value, total) => {
if (!value || !total) return 0;
return (value / total) * 100;
};
export const formatBytes = (bytes) => {
if (!bytes) return "N/A";
if (bytes === 0) return "0 Bytes";
if (bytes >= 1024 * 1024 * 1024) {
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
}
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
};
-176
View File
@@ -1,176 +0,0 @@
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 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 LevelBadge = ({ level }) => {
const theme = useTheme();
const levelColors = {
info: theme.palette.success.main,
warn: theme.palette.warning.main,
error: theme.palette.error.main,
debug: theme.palette.accent.main,
};
const color = levelColors[level] || theme.palette.primary.contrastText;
return (
<Box
component="span"
sx={{
color: color,
fontWeight: 600,
textTransform: "uppercase",
fontSize: 12,
}}
>
{level}
</Box>
);
};
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);
const theme = useTheme();
const { t } = useTranslation();
const [logs, isLoading, error] = useFetchLogs();
const LOG_LEVELS = [
{ _id: "all", name: t("logsPage.logLevelSelect.values.all") },
{ _id: "info", name: t("logsPage.logLevelSelect.values.info") },
{ _id: "warn", name: t("logsPage.logLevelSelect.values.warn") },
{ _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)}
>
<Typography>{t("logsPage.logLevelSelect.title")}</Typography>
<Select
items={LOG_LEVELS}
value={logLevel}
onChange={handleLogLevelChange}
/>
</Stack>
<DataTable
shouldRender={!isLoading}
headers={headers}
data={paginatedLogs || []}
config={{
emptyView: t("logsPage.noLogs"),
}}
/>
{filteredLogs?.length > 0 && (
<Pagination
paginationLabel={t("logsPage.table.logs")}
itemCount={filteredLogs?.length || 0}
page={page}
rowsPerPage={rowsPerPage}
handleChangePage={handleChangePage}
handleChangeRowsPerPage={handleChangeRowsPerPage}
/>
)}
</Stack>
);
};
export default Logs;
@@ -1,82 +0,0 @@
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import DataTable from "@/Components/v1/Table/index.jsx";
import { useNavigate } from "react-router-dom";
import { useTheme } from "@emotion/react";
import { TypeToPathMap } from "../../../../../Utils/monitorUtilsLegacy.js";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
const FailedJobTable = ({ metrics = {} }) => {
const { t } = useTranslation();
const theme = useTheme();
const jobsWithFailures = metrics?.jobsWithFailures;
const navigate = useNavigate();
const headers = [
{
id: "monitorId",
content: t("queuePage.failedJobTable.monitorIdHeader"),
render: (row) => {
return row.monitorId;
},
},
{
id: "monitorUrl",
content: t("queuePage.failedJobTable.monitorUrlHeader"),
render: (row) => {
return row.monitorUrl;
},
},
{
id: "failCount",
content: t("queuePage.failedJobTable.failCountHeader"),
render: (row) => {
return row.failCount;
},
},
{
id: "failedAt",
content: t("queuePage.failedJobTable.failedAtHeader"),
render: (row) => {
return row.failedAt;
},
},
{
id: "failReason",
content: t("queuePage.failedJobTable.failReasonHeader"),
render: (row) => {
return row.failReason;
},
},
];
return (
<Stack gap={theme.spacing(2)}>
<Typography variant="h2">{t("queuePage.failedJobTable.title")}</Typography>
<DataTable
headers={headers}
data={jobsWithFailures}
config={{
onRowClick: (row) => {
const path = TypeToPathMap[row.monitorType];
navigate(`/${path}/${row.monitorId}`);
},
rowSX: {
cursor: "pointer",
"&:hover td": {
backgroundColor: theme.palette.tertiary.main,
transition: "background-color .3s ease",
},
},
}}
/>
</Stack>
);
};
FailedJobTable.propTypes = {
metrics: PropTypes.object,
};
export default FailedJobTable;
@@ -1,130 +0,0 @@
import Stack from "@mui/material/Stack";
import DataTable from "@/Components/v1/Table/index.jsx";
import Typography from "@mui/material/Typography";
// Utils
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router-dom";
import { TypeToPathMap } from "../../../../../Utils/monitorUtilsLegacy.js";
import { useTranslation } from "react-i18next";
import { createHeaderFactory } from "@/Components/v1/Table/TableUtils.js";
const JobTable = ({ jobs = [] }) => {
const theme = useTheme();
const { t } = useTranslation();
const navigate = useNavigate();
const buildSx = (row) => {
if (row.lockedAt) {
return {
color: `${theme.palette.success.main} !important`,
};
}
if (!row.active) {
return {
color: `${theme.palette.warning.main} !important`,
};
}
if (row.failCount > 0 && row.lastFailedAt >= row.lastFinishedAt) {
return {
color: `${theme.palette.error.main} !important`,
};
}
return {};
};
const createHeader = createHeaderFactory(buildSx);
const headersData = [
{
id: "id",
content: t("queuePage.jobTable.idHeader"),
render: (row) => row.monitorId,
},
{
id: "url",
content: t("queuePage.jobTable.urlHeader"),
render: (row) => row.monitorUrl,
},
{
id: "interval",
content: t("queuePage.jobTable.intervalHeader"),
render: (row) => `${row.monitorInterval} ms`,
},
{
id: "type",
content: t("queuePage.jobTable.typeHeader"),
render: (row) => row.monitorType,
},
{
id: "active",
content: t("queuePage.jobTable.activeHeader"),
render: (row) => row.active.toString(),
},
{
id: "runCount",
content: t("queuePage.jobTable.runCountHeader"),
render: (row) => row.runCount,
},
{
id: "failCount",
content: t("queuePage.jobTable.failCountHeader"),
render: (row) => row.failCount,
},
{
id: "lastRun",
content: t("queuePage.jobTable.lastRunHeader"),
render: (row) => row.lastRunAt || "-",
},
{
id: "lockedAt",
content: t("queuePage.jobTable.lockedAtHeader"),
render: (row) => row.lockedAt || "-",
},
{
id: "lastFinish",
content: t("queuePage.jobTable.lastFinishedAtHeader"),
render: (row) => row.lastFinishedAt || "-",
},
{
id: "lastRunTook",
content: t("queuePage.jobTable.lastRunTookHeader"),
render: (row) => {
const value = row.lastRunTook ? row.lastRunTook + " ms" : "-";
return value;
},
},
];
const headers = headersData.map((header) => createHeader(header));
return (
<Stack gap={theme.spacing(2)}>
<Typography variant="h2">{t("queuePage.jobTable.title")}</Typography>
<DataTable
headers={headers}
data={jobs}
config={{
onRowClick: (row) => {
const path = TypeToPathMap[row.monitorType];
navigate(`/${path}/${row.monitorId}`);
},
rowSX: {
cursor: "pointer",
"&:hover td": {
backgroundColor: theme.palette.tertiary.main,
transition: "background-color .3s ease",
},
},
}}
/>
</Stack>
);
};
JobTable.propTypes = {
jobs: PropTypes.array,
};
export default JobTable;
@@ -1,38 +0,0 @@
import Stack from "@mui/material/Stack";
import StatBox from "@/Components/v1/StatBox/index.jsx";
import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx";
import { useTranslation } from "react-i18next";
import { useTheme } from "@emotion/react";
const camelToTitle = (str) => {
return str
.replace(/([A-Z])/g, " $1")
.toLowerCase()
.replace(/^./, (m) => m.toUpperCase());
};
const Metrics = ({ metrics = {} }) => {
const { t } = useTranslation();
const theme = useTheme();
const data = Object.keys(metrics)
.filter((key) => key !== "jobsWithFailures")
.map((key) => {
return { key, title: camelToTitle(key), value: metrics[key] };
});
return (
<StatusBoxes flexWrap="wrap">
{data.map((metric) => {
return (
<StatBox
key={metric.key}
heading={metric.title}
subHeading={metric.value}
/>
);
})}
</StatusBoxes>
);
};
export default Metrics;
@@ -1,60 +0,0 @@
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import DataTable from "@/Components/v1/Table/index.jsx";
// Utils
import { useTranslation } from "react-i18next";
import { useTheme } from "@emotion/react";
import PropTypes from "prop-types";
const camelToTitle = (str) => {
return str
.replace(/([A-Z])/g, " $1")
.toLowerCase()
.replace(/^./, (m) => m.toUpperCase());
};
const Metrics = ({ metrics = {} }) => {
const { t } = useTranslation();
const theme = useTheme();
const keys = Object.keys(metrics);
const headers = [
{
id: "metric",
content: t("queuePage.metricsTable.metricHeader"),
render: (row) => {
return <Typography>{row.key}</Typography>;
},
},
{
id: "value",
content: t("queuePage.metricsTable.valueHeader"),
render: (row) => {
return <Typography>{row.value}</Typography>;
},
},
];
const data = keys
.filter((key) => key !== "jobsWithFailures")
.map((key) => {
return { key: camelToTitle(key), value: metrics[key] };
});
return (
<Stack gap={theme.spacing(2)}>
<Typography variant="h2">{t("queuePage.metricsTable.title")}</Typography>
<DataTable
headers={headers}
data={data}
/>
</Stack>
);
};
Metrics.propTypes = {
metrics: PropTypes.object,
};
export default Metrics;
-73
View File
@@ -1,73 +0,0 @@
// Components
import Stack from "@mui/material/Stack";
import JobTable from "./components/JobTable/index.jsx";
import Metrics from "./components/Metrics/index.jsx";
import FailedJobTable from "./components/FailedJobTable/index.jsx";
import ButtonGroup from "@mui/material/ButtonGroup";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import Divider from "@mui/material/Divider";
// Utils
import { useState } from "react";
import { useFetchQueueData, useFlushQueue } from "../../../Hooks/logHooks.js";
import { useTranslation } from "react-i18next";
import { useTheme } from "@emotion/react";
const QueueDetails = () => {
// Local state
const [trigger, setTrigger] = useState(false);
// Hooks
const { t } = useTranslation();
const theme = useTheme();
const [jobs, metrics, isLoading, error] = useFetchQueueData(trigger);
const [flushQueue, isFlushing, flushError] = useFlushQueue();
if (isLoading) return <div>Loading...</div>;
if (error || flushError) return <div>Error: {error.message}</div>;
return (
<Stack gap={theme.spacing(4)}>
<Stack
gap={theme.spacing(20)}
mt={theme.spacing(10)}
>
<Metrics metrics={metrics} />
<JobTable jobs={jobs} />
<FailedJobTable metrics={metrics} />
<ButtonGroup
variant="contained"
color="accent"
sx={{
position: "sticky",
bottom: 0,
zIndex: 1000,
backgroundColor: theme.palette.primary.main,
p: theme.spacing(4),
border: `1px solid ${theme.palette.primary.lowContrast}`,
borderRadius: theme.spacing(2),
}}
>
<Button
onClick={() => {
setTrigger(!trigger);
}}
loading={isLoading}
>
{t("queuePage.refreshButton")}
</Button>
<Button
onClick={() => flushQueue(trigger, setTrigger)}
loading={isFlushing}
>
{t("queuePage.flushButton")}
</Button>
</ButtonGroup>
</Stack>
</Stack>
);
};
export default QueueDetails;
+2 -2
View File
@@ -1,6 +1,6 @@
import Stack from "@mui/material/Stack";
import { Stats } from "@/Pages/Logs/Stats";
import { StatGauges } from "@/Pages/Logs/StatGauges";
import { Stats } from "@/Pages/Logs/components/Stats";
import { StatGauges } from "@/Pages/Logs/components/StatGauges";
import { useTheme } from "@mui/material";
import { useGet } from "@/Hooks/UseApi";
+1 -1
View File
@@ -3,7 +3,7 @@ import MenuItem from "@mui/material/MenuItem";
import Typography from "@mui/material/Typography";
import { useSelector } from "react-redux";
import { Select } from "@/Components/v2/inputs";
import { TableLogs } from "./TableLogs";
import { TableLogs } from "@/Pages/Logs/components/TableLogs";
import { useTheme } from "@mui/material";
import { useGet } from "@/Hooks/UseApi";
+2 -2
View File
@@ -1,12 +1,12 @@
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { TableJobs, TableFailedJobs } from "@/Pages/Logs/TableJobs";
import { TableJobs, TableFailedJobs } from "./components/TableJobs";
import { useTheme } from "@mui/material";
import { useTranslation } from "react-i18next";
import { useGet, usePost } from "@/Hooks/UseApi";
import type { QueueData } from "@/Types/Queue";
import { Metrics } from "@/Pages/Logs/Metrics";
import { Metrics } from "@/Pages/Logs/components/Metrics";
import { Button } from "@/Components/v2/inputs";
export const TabQueue = () => {
@@ -15,7 +15,7 @@ const getPercentage = (value: number, total: number) => {
return (value / total) * 100;
};
const formatPerecentage = new Intl.NumberFormat("en-US", {
const formatPercentage = new Intl.NumberFormat("en-US", {
style: "percent",
minimumFractionDigits: 1,
maximumFractionDigits: 1,
@@ -51,7 +51,7 @@ export const StatGauges = ({ diagnostics }: StatGaugesProps) => {
<DetailGauge
title={t("pages.logs.diagnostics.gauges.heapAllocation")}
progress={heapTotalSize}
upperValue={formatPerecentage.format(heapTotalSize / 100)}
upperValue={formatPercentage.format(heapTotalSize / 100)}
lowerLabel={t("pages.logs.diagnostics.gauges.total")}
lowerValue={prettyBytes(diagnostics.v8HeapStats?.heapSizeLimitBytes ?? 0)}
/>
@@ -59,7 +59,7 @@ export const StatGauges = ({ diagnostics }: StatGaugesProps) => {
title={t("pages.logs.diagnostics.gauges.heapUsage")}
progress={heapUsedSize}
upperLabel={t("pages.logs.diagnostics.gauges.availableMemoryPercentage")}
upperValue={formatPerecentage.format(heapUsedSize / 100)}
upperValue={formatPercentage.format(heapUsedSize / 100)}
lowerLabel={t("pages.logs.diagnostics.gauges.used")}
lowerValue={prettyBytes(diagnostics.v8HeapStats?.usedHeapSizeBytes ?? 0)}
/>
@@ -67,7 +67,7 @@ export const StatGauges = ({ diagnostics }: StatGaugesProps) => {
title={t("pages.logs.diagnostics.gauges.heapUtilization")}
progress={actualHeapUsed}
upperLabel={t("pages.logs.diagnostics.gauges.allocatedPercentage")}
upperValue={formatPerecentage.format(actualHeapUsed / 100)}
upperValue={formatPercentage.format(actualHeapUsed / 100)}
lowerLabel={t("pages.logs.diagnostics.gauges.total")}
lowerValue={prettyBytes(diagnostics.v8HeapStats?.usedHeapSizeBytes ?? 0)}
/>
@@ -75,7 +75,7 @@ export const StatGauges = ({ diagnostics }: StatGaugesProps) => {
title={t("pages.logs.diagnostics.gauges.instantCpuUsage")}
progress={diagnostics.cpuUsage?.usagePercentage ?? 0}
upperLabel={t("pages.logs.diagnostics.gauges.usedSPercentage")}
upperValue={formatPerecentage.format(
upperValue={formatPercentage.format(
(diagnostics.cpuUsage?.usagePercentage ?? 0) / 100
)}
/>
-50
View File
@@ -1,50 +0,0 @@
import Stack from "@mui/material/Stack";
import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
import Tabs from "@mui/material/Tabs";
import Tab from "@mui/material/Tab";
import Queue from "./Queue/index.jsx";
import LogsComponent from "./Logs/index.jsx";
import Diagnostics from "./Diagnostics/index.jsx";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
import { useState } from "react";
const Logs = () => {
const { t } = useTranslation();
const theme = useTheme();
// Local state
const [value, setValue] = useState(0);
// Handlers
const handleChange = (event, newValue) => {
setValue(newValue);
};
const BREADCRUMBS = [{ name: t("logsPage.title"), path: "/logs" }];
return (
<Stack gap={theme.spacing(20)}>
<Breadcrumbs list={BREADCRUMBS} />
<Tabs
value={value}
onChange={handleChange}
sx={{
position: "sticky",
top: theme.spacing(0),
backdropFilter: "blur(10px)",
zIndex: theme.zIndex.appBar,
}}
>
<Tab label={t("logsPage.tabs.logs")} />
<Tab label={t("logsPage.tabs.queue")} />
<Tab label={t("logsPage.tabs.diagnostics")} />
</Tabs>
{value === 0 && <LogsComponent />}
{value === 1 && <Queue />}
{value === 2 && <Diagnostics />}
</Stack>
);
};
export default Logs;