mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-05-25 04:19:54 -05:00
Add Queue page and components
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import DataTable from "../../../../Components/Table";
|
||||
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { TypeToPathMap } from "../../../../Utils/monitorUtils";
|
||||
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;
|
||||
@@ -0,0 +1,168 @@
|
||||
import Stack from "@mui/material/Stack";
|
||||
import DataTable from "../../../../Components/Table";
|
||||
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/monitorUtils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
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 headers = [
|
||||
{
|
||||
id: "id",
|
||||
content: t("queuePage.jobTable.idHeader"),
|
||||
render: (row) => {
|
||||
return row.monitorId;
|
||||
},
|
||||
getCellSx: (row) => {
|
||||
return buildSx(row);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "url",
|
||||
content: t("queuePage.jobTable.urlHeader"),
|
||||
render: (row) => {
|
||||
return row.monitorUrl;
|
||||
},
|
||||
getCellSx: (row) => {
|
||||
return buildSx(row);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "type",
|
||||
content: t("queuePage.jobTable.typeHeader"),
|
||||
render: (row) => {
|
||||
return row.monitorType;
|
||||
},
|
||||
getCellSx: (row) => {
|
||||
return buildSx(row);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "active",
|
||||
content: t("queuePage.jobTable.activeHeader"),
|
||||
render: (row) => {
|
||||
return row.active.toString();
|
||||
},
|
||||
getCellSx: (row) => {
|
||||
return buildSx(row);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "runCount",
|
||||
content: t("queuePage.jobTable.runCountHeader"),
|
||||
render: (row) => {
|
||||
return row.runCount;
|
||||
},
|
||||
getCellSx: (row) => {
|
||||
return buildSx(row);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "failCount",
|
||||
content: t("queuePage.jobTable.failCountHeader"),
|
||||
render: (row) => {
|
||||
return row.failCount;
|
||||
},
|
||||
getCellSx: (row) => {
|
||||
return buildSx(row);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "lastRun",
|
||||
content: t("queuePage.jobTable.lastRunHeader"),
|
||||
render: (row) => {
|
||||
return row.lastRunAt || "-";
|
||||
},
|
||||
getCellSx: (row) => {
|
||||
return buildSx(row);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "lockedAt",
|
||||
content: t("queuePage.jobTable.lockedAtHeader"),
|
||||
render: (row) => {
|
||||
return row.lockedAt || "-";
|
||||
},
|
||||
getCellSx: (row) => {
|
||||
return buildSx(row);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "lastFinish",
|
||||
content: t("queuePage.jobTable.lastFinishedAtHeader"),
|
||||
render: (row) => {
|
||||
return row.lastFinishedAt || "-";
|
||||
},
|
||||
getCellSx: (row) => {
|
||||
return buildSx(row);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "lastRunTook",
|
||||
content: t("queuePage.jobTable.lastRunTookHeader"),
|
||||
render: (row) => {
|
||||
const value = row.lastRunTook ? row.lastRunTook + " ms" : "-";
|
||||
return value;
|
||||
},
|
||||
getCellSx: (row) => {
|
||||
return buildSx(row);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
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;
|
||||
@@ -0,0 +1,60 @@
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import DataTable from "../../../../Components/Table";
|
||||
|
||||
// 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;
|
||||
@@ -0,0 +1,69 @@
|
||||
// Components
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Breadcrumbs from "../../Components/Breadcrumbs";
|
||||
import JobTable from "./components/JobTable";
|
||||
import MetricsTable from "./components/MetricsTable";
|
||||
import FailedJobTable from "./components/FailedJobTable";
|
||||
import ButtonGroup from "@mui/material/ButtonGroup";
|
||||
import Button from "@mui/material/Button";
|
||||
|
||||
// Utils
|
||||
import { useState } from "react";
|
||||
import { useFetchQueueData, useFlushQueue } from "../../Hooks/queueHooks";
|
||||
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();
|
||||
|
||||
const BREADCRUMBS = [{ name: t("queuePage.title"), path: "/queue" }];
|
||||
if (isLoading) return <div>Loading...</div>;
|
||||
if (error || flushError) return <div>Error: {error.message}</div>;
|
||||
|
||||
return (
|
||||
<Stack gap={theme.spacing(20)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<JobTable jobs={jobs} />
|
||||
<MetricsTable metrics={metrics} />
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
export default QueueDetails;
|
||||
Reference in New Issue
Block a user