mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-01-25 19:29:39 -06:00
Merge pull request #2495 from bluewave-labs/feat/dev-settings
feat: dev settings
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import { Link as MuiLink, useTheme } from "@mui/material";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
/**
|
||||
@@ -10,7 +12,7 @@ import PropTypes from "prop-types";
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
|
||||
const Link = ({ level, label, url }) => {
|
||||
const Link = ({ level, label, url, external = true }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const levelConfig = {
|
||||
@@ -49,11 +51,12 @@ const Link = ({ level, label, url }) => {
|
||||
const { sx, color } = levelConfig[level];
|
||||
return (
|
||||
<MuiLink
|
||||
href={url}
|
||||
component={external ? "a" : RouterLink}
|
||||
to={external ? undefined : url}
|
||||
href={external ? url : undefined}
|
||||
sx={{ width: "fit-content", ...sx }}
|
||||
color={color}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
{...(external && { target: "_blank", rel: "noreferrer" })}
|
||||
>
|
||||
{label}
|
||||
</MuiLink>
|
||||
@@ -64,6 +67,7 @@ Link.propTypes = {
|
||||
url: PropTypes.string.isRequired,
|
||||
level: PropTypes.oneOf(["primary", "secondary", "tertiary", "error"]),
|
||||
label: PropTypes.string.isRequired,
|
||||
external: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default Link;
|
||||
|
||||
11
client/src/Components/Table/TableUtils.js
Normal file
11
client/src/Components/Table/TableUtils.js
Normal file
@@ -0,0 +1,11 @@
|
||||
export const createHeaderFactory = (getCellSx = () => {}) => {
|
||||
return ({ id, content, onClick = () => {}, render = () => {} }) => {
|
||||
return {
|
||||
id,
|
||||
content,
|
||||
onClick,
|
||||
getCellSx,
|
||||
render,
|
||||
};
|
||||
};
|
||||
};
|
||||
57
client/src/Hooks/queueHooks.js
Normal file
57
client/src/Hooks/queueHooks.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { networkService } from "../main";
|
||||
import { createToast } from "../Utils/toastUtils";
|
||||
|
||||
const useFetchQueueData = (trigger) => {
|
||||
const [jobs, setJobs] = useState(undefined);
|
||||
const [metrics, setMetrics] = useState(undefined);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchJobs = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await networkService.getQueueData();
|
||||
if (response.status === 200) {
|
||||
setJobs(response.data.data.jobs);
|
||||
setMetrics(response.data.data.metrics);
|
||||
}
|
||||
} catch (error) {
|
||||
setError(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchJobs();
|
||||
}, [trigger]);
|
||||
|
||||
return [jobs, metrics, isLoading, error];
|
||||
};
|
||||
|
||||
const useFlushQueue = () => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState(undefined);
|
||||
|
||||
const flushQueue = async (trigger, setTrigger) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await networkService.flushQueue();
|
||||
createToast({
|
||||
body: "Queue flushed",
|
||||
});
|
||||
} catch (error) {
|
||||
setError(error);
|
||||
createToast({
|
||||
body: error.message,
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
setTrigger(!trigger);
|
||||
}
|
||||
};
|
||||
return [flushQueue, isLoading, error];
|
||||
};
|
||||
|
||||
export { useFetchQueueData, useFlushQueue };
|
||||
82
client/src/Pages/Queue/components/FailedJobTable/index.jsx
Normal file
82
client/src/Pages/Queue/components/FailedJobTable/index.jsx
Normal file
@@ -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;
|
||||
125
client/src/Pages/Queue/components/JobTable/index.jsx
Normal file
125
client/src/Pages/Queue/components/JobTable/index.jsx
Normal file
@@ -0,0 +1,125 @@
|
||||
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";
|
||||
import { createHeaderFactory } from "../../../../Components/Table/TableUtils";
|
||||
|
||||
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: "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;
|
||||
60
client/src/Pages/Queue/components/MetricsTable/index.jsx
Normal file
60
client/src/Pages/Queue/components/MetricsTable/index.jsx
Normal file
@@ -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;
|
||||
69
client/src/Pages/Queue/index.jsx
Normal file
69
client/src/Pages/Queue/index.jsx
Normal file
@@ -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;
|
||||
37
client/src/Pages/Settings/SettingsDev.jsx
Normal file
37
client/src/Pages/Settings/SettingsDev.jsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import Box from "@mui/material/Box";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import ConfigBox from "../../Components/ConfigBox";
|
||||
// Utils
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Link from "../../Components/Link";
|
||||
|
||||
const SettingsDev = ({ isAdmin, HEADER_SX }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!isAdmin) return null;
|
||||
|
||||
return (
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography
|
||||
component="h1"
|
||||
variant="h2"
|
||||
>
|
||||
{t("settingsDev")}
|
||||
</Typography>
|
||||
<Typography sx={HEADER_SX}>{t("settingsDevDescription")}</Typography>
|
||||
</Box>
|
||||
<Box>
|
||||
<Link
|
||||
level="secondary"
|
||||
label={t("settingsDevViewJobQueueDetails")}
|
||||
url="/queue"
|
||||
external={false}
|
||||
/>
|
||||
</Box>
|
||||
</ConfigBox>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsDev;
|
||||
@@ -8,6 +8,7 @@ import SettingsPagespeed from "./SettingsPagespeed";
|
||||
import SettingsDemoMonitors from "./SettingsDemoMonitors";
|
||||
import SettingsAbout from "./SettingsAbout";
|
||||
import SettingsEmail from "./SettingsEmail";
|
||||
import SettingsDev from "./SettingsDev";
|
||||
import Button from "@mui/material/Button";
|
||||
// Utils
|
||||
import { settingsValidation } from "../../Validation/validation";
|
||||
@@ -208,6 +209,10 @@ const Settings = () => {
|
||||
emailPasswordHasBeenReset={emailPasswordHasBeenReset}
|
||||
setEmailPasswordHasBeenReset={setEmailPasswordHasBeenReset}
|
||||
/>
|
||||
<SettingsDev
|
||||
isAdmin={isAdmin}
|
||||
HEADER_SX={HEADING_SX}
|
||||
/>
|
||||
<SettingsAbout />
|
||||
<Stack
|
||||
direction="row"
|
||||
|
||||
@@ -51,6 +51,7 @@ import ProtectedRoute from "../Components/ProtectedRoute";
|
||||
import CreateNewMaintenanceWindow from "../Pages/Maintenance/CreateMaintenance";
|
||||
import withAdminCheck from "../Components/HOC/withAdminCheck";
|
||||
import BulkImport from "../Pages/Uptime/BulkImport";
|
||||
import Queue from "../Pages/Queue";
|
||||
|
||||
const Routes = () => {
|
||||
const AdminCheckedRegister = withAdminCheck(AuthRegister);
|
||||
@@ -186,6 +187,10 @@ const Routes = () => {
|
||||
path="account/team"
|
||||
element={<Account open={"team"} />}
|
||||
/>
|
||||
<Route
|
||||
path="queue"
|
||||
element={<Queue />}
|
||||
/>
|
||||
</Route>
|
||||
|
||||
<Route
|
||||
|
||||
@@ -1023,6 +1023,18 @@ class NetworkService {
|
||||
return this.axiosInstance.put(`/notifications/${id}`, notification);
|
||||
}
|
||||
|
||||
async getQueueData() {
|
||||
return this.axiosInstance.get(`/queue/all-metrics`, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async flushQueue() {
|
||||
return this.axiosInstance.post(`/queue/flush`);
|
||||
|
||||
|
||||
async exportMonitors() {
|
||||
const response = await this.axiosInstance.get("/monitors/export", {
|
||||
responseType: "blob",
|
||||
|
||||
@@ -36,3 +36,12 @@ export const parseDomainName = (url) => {
|
||||
|
||||
return url;
|
||||
};
|
||||
|
||||
export const TypeToPathMap = {
|
||||
http: "uptime",
|
||||
port: "uptime",
|
||||
docker: "uptime",
|
||||
ping: "uptime",
|
||||
hardware: "infrastructure",
|
||||
pagespeed: "pagespeed",
|
||||
};
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
"settingsRemoveAllMonitorsDialogTitle": "Do you want to remove all monitors?",
|
||||
"settingsRemoveAllMonitorsDialogConfirm": "Yes, remove all monitors",
|
||||
"settingsAbout": "About",
|
||||
"settingsDev": "Developer",
|
||||
"settingsDevDescription": "Developer settings",
|
||||
"settingsDevViewJobQueueDetails": "View job queue details",
|
||||
"settingsDevelopedBy": "Developed by Bluewave Labs.",
|
||||
"settingsSave": "Save",
|
||||
"settingsSuccessSaved": "Settings saved successfully",
|
||||
@@ -753,6 +756,36 @@
|
||||
"settingsEmailPool": "Pool - Enable connection pooling",
|
||||
"sendTestNotifications": "Send test notifications",
|
||||
"selectAll": "Select all",
|
||||
"queuePage": {
|
||||
"title": "Queue",
|
||||
"refreshButton": "Refresh",
|
||||
"flushButton": "Flush queue",
|
||||
"jobTable": {
|
||||
"title": "Jobs currently in queue",
|
||||
"idHeader": "Monitor ID",
|
||||
"urlHeader": "URL",
|
||||
"typeHeader": "Type",
|
||||
"activeHeader": "Active",
|
||||
"lockedAtHeader": "Locked at",
|
||||
"runCountHeader": "Run count",
|
||||
"failCountHeader": "Fail count",
|
||||
"lastRunHeader": "Last run at",
|
||||
"lastFinishedAtHeader": "Last finished at",
|
||||
"lastRunTookHeader": "Last run took"
|
||||
},
|
||||
"metricsTable": {
|
||||
"title": "Queue metrics",
|
||||
"metricHeader": "Metric",
|
||||
"valueHeader": "Value"
|
||||
},
|
||||
"failedJobTable": {
|
||||
"title": "Failed jobs",
|
||||
"monitorIdHeader": "Monitor ID",
|
||||
"monitorUrlHeader": "Monitor URL",
|
||||
"failCountHeader": "Fail count",
|
||||
"failedAtHeader": "Last failed at",
|
||||
"failReasonHeader": "Fail reason"
|
||||
}
|
||||
"export": {
|
||||
"title": "Export Monitors",
|
||||
"success": "Monitors exported successfully!",
|
||||
|
||||
@@ -25,7 +25,7 @@ class JobQueueController {
|
||||
try {
|
||||
const jobs = await this.jobQueue.getJobs();
|
||||
return res.success({
|
||||
msg: this.stringService.queueGetMetrics,
|
||||
msg: this.stringService.queueGetJobs,
|
||||
data: jobs,
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -34,6 +34,20 @@ class JobQueueController {
|
||||
}
|
||||
};
|
||||
|
||||
getAllMetrics = async (req, res, next) => {
|
||||
try {
|
||||
const jobs = await this.jobQueue.getJobs();
|
||||
const metrics = await this.jobQueue.getMetrics();
|
||||
return res.success({
|
||||
msg: this.stringService.queueGetAllMetrics,
|
||||
data: { jobs, metrics },
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "getAllMetrics"));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
addJob = async (req, res, next) => {
|
||||
try {
|
||||
await this.jobQueue.addJob(Math.random().toString(36).substring(7));
|
||||
|
||||
@@ -123,6 +123,7 @@
|
||||
"monitorCertificate": "Got monitor certificate successfully",
|
||||
"monitorDemoAdded": "Successfully added demo monitors",
|
||||
"queueGetMetrics": "Got metrics successfully",
|
||||
"queueGetJobs": "Got jobs successfully",
|
||||
"queueAddJob": "Job added successfully",
|
||||
"queueObliterate": "Queue obliterated",
|
||||
"jobQueueDeleteJobSuccess": "Job removed successfully",
|
||||
|
||||
@@ -19,6 +19,12 @@ class QueueRoutes {
|
||||
this.queueController.getJobs
|
||||
);
|
||||
|
||||
this.router.get(
|
||||
"/all-metrics",
|
||||
isAllowed(["admin", "superadmin"]),
|
||||
this.queueController.getAllMetrics
|
||||
);
|
||||
|
||||
this.router.post(
|
||||
"/jobs",
|
||||
isAllowed(["admin", "superadmin"]),
|
||||
|
||||
@@ -57,6 +57,9 @@ class PulseQueue {
|
||||
job.unique({ "data.monitor._id": monitor._id });
|
||||
job.attrs.jobId = monitorId.toString();
|
||||
job.repeatEvery(`${intervalInSeconds} seconds`);
|
||||
if (monitor.isActive === false) {
|
||||
job.disable();
|
||||
}
|
||||
await job.save();
|
||||
};
|
||||
|
||||
@@ -136,6 +139,8 @@ class PulseQueue {
|
||||
const jobs = await this.pulse.jobs();
|
||||
const metrics = jobs.reduce(
|
||||
(acc, job) => {
|
||||
acc.totalRuns += job.attrs.runCount || 0;
|
||||
acc.totalFailures += job.attrs.failCount || 0;
|
||||
acc.jobs++;
|
||||
if (job.attrs.failCount > 0 && job.attrs.failedAt >= job.attrs.lastFinishedAt) {
|
||||
acc.failingJobs++;
|
||||
@@ -147,13 +152,22 @@ class PulseQueue {
|
||||
acc.jobsWithFailures.push({
|
||||
monitorId: job.attrs.data.monitor._id,
|
||||
monitorUrl: job.attrs.data.monitor.url,
|
||||
monitorType: job.attrs.data.monitor.type,
|
||||
failedAt: job.attrs.failedAt,
|
||||
failCount: job.attrs.failCount,
|
||||
failReason: job.attrs.failReason,
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{ jobs: 0, activeJobs: 0, failingJobs: 0, jobsWithFailures: [] }
|
||||
{
|
||||
jobs: 0,
|
||||
activeJobs: 0,
|
||||
failingJobs: 0,
|
||||
jobsWithFailures: [],
|
||||
totalRuns: 0,
|
||||
totalFailures: 0,
|
||||
}
|
||||
);
|
||||
return metrics;
|
||||
};
|
||||
@@ -164,10 +178,18 @@ class PulseQueue {
|
||||
return {
|
||||
monitorId: job.attrs.data.monitor._id,
|
||||
monitorUrl: job.attrs.data.monitor.url,
|
||||
monitorType: job.attrs.data.monitor.type,
|
||||
active: !job.attrs.disabled,
|
||||
lockedAt: job.attrs.lockedAt,
|
||||
runCount: job.attrs.runCount || 0,
|
||||
failCount: job.attrs.failCount || 0,
|
||||
failReason: job.attrs.failReason,
|
||||
lastRunAt: job.attrs.lastRunAt,
|
||||
lastFinishedAt: job.attrs.lastFinishedAt,
|
||||
lastRunTook: job.attrs.lockedAt
|
||||
? null
|
||||
: job.attrs.lastFinishedAt - job.attrs.lastRunAt,
|
||||
lastFailedAt: job.attrs.failedAt,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
@@ -276,6 +276,10 @@ class StringService {
|
||||
return this.translationService.getTranslation("queueGetMetrics");
|
||||
}
|
||||
|
||||
get queueGetJobs() {
|
||||
return this.translationService.getTranslation("queueGetJobs");
|
||||
}
|
||||
|
||||
get queueAddJob() {
|
||||
return this.translationService.getTranslation("queueAddJob");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user