From 61a66644daab4a5163c099a5997f5a63f3a1fff3 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 20 Jun 2025 10:58:29 +0800 Subject: [PATCH 01/14] disable job on addition if monitor is paused --- server/service/PulseQueue/PulseQueue.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/service/PulseQueue/PulseQueue.js b/server/service/PulseQueue/PulseQueue.js index 0452985a0..f4a1b38a1 100644 --- a/server/service/PulseQueue/PulseQueue.js +++ b/server/service/PulseQueue/PulseQueue.js @@ -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(); }; From 0a0c84b7f4b949b47ccd16f436a8e917d754103c Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 20 Jun 2025 14:26:59 +0800 Subject: [PATCH 02/14] update link component to allow for internal links --- client/src/Components/Link/index.jsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/client/src/Components/Link/index.jsx b/client/src/Components/Link/index.jsx index 7a623a3b0..987c56f37 100644 --- a/client/src/Components/Link/index.jsx +++ b/client/src/Components/Link/index.jsx @@ -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 ( {label} @@ -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; From d8573be7ac135fcfd8072a59df4c727a92c3b0aa Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 20 Jun 2025 14:27:22 +0800 Subject: [PATCH 03/14] add util methods --- client/src/Utils/NetworkService.js | 12 ++++++++++++ client/src/Utils/monitorUtils.js | 9 +++++++++ 2 files changed, 21 insertions(+) diff --git a/client/src/Utils/NetworkService.js b/client/src/Utils/NetworkService.js index eee795268..85feafbe6 100644 --- a/client/src/Utils/NetworkService.js +++ b/client/src/Utils/NetworkService.js @@ -1022,6 +1022,18 @@ class NetworkService { const { id, notification } = config; 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`); + } } export default NetworkService; diff --git a/client/src/Utils/monitorUtils.js b/client/src/Utils/monitorUtils.js index cc92a86ae..e3645b0d0 100644 --- a/client/src/Utils/monitorUtils.js +++ b/client/src/Utils/monitorUtils.js @@ -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", +}; From e610acb9c7ee7709002762eb0c75cb758963b307 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 20 Jun 2025 14:27:50 +0800 Subject: [PATCH 04/14] Add Queue page and components --- .../Queue/components/FailedJobTable/index.jsx | 82 +++++++++ .../Pages/Queue/components/JobTable/index.jsx | 168 ++++++++++++++++++ .../Queue/components/MetricsTable/index.jsx | 60 +++++++ client/src/Pages/Queue/index.jsx | 69 +++++++ 4 files changed, 379 insertions(+) create mode 100644 client/src/Pages/Queue/components/FailedJobTable/index.jsx create mode 100644 client/src/Pages/Queue/components/JobTable/index.jsx create mode 100644 client/src/Pages/Queue/components/MetricsTable/index.jsx create mode 100644 client/src/Pages/Queue/index.jsx diff --git a/client/src/Pages/Queue/components/FailedJobTable/index.jsx b/client/src/Pages/Queue/components/FailedJobTable/index.jsx new file mode 100644 index 000000000..d7284f9da --- /dev/null +++ b/client/src/Pages/Queue/components/FailedJobTable/index.jsx @@ -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 ( + + {t("queuePage.failedJobTable.title")} + { + const path = TypeToPathMap[row.monitorType]; + navigate(`/${path}/${row.monitorId}`); + }, + rowSX: { + cursor: "pointer", + "&:hover td": { + backgroundColor: theme.palette.tertiary.main, + transition: "background-color .3s ease", + }, + }, + }} + /> + + ); +}; + +FailedJobTable.propTypes = { + metrics: PropTypes.object, +}; + +export default FailedJobTable; diff --git a/client/src/Pages/Queue/components/JobTable/index.jsx b/client/src/Pages/Queue/components/JobTable/index.jsx new file mode 100644 index 000000000..3bce34fff --- /dev/null +++ b/client/src/Pages/Queue/components/JobTable/index.jsx @@ -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 ( + + {t("queuePage.jobTable.title")} + { + const path = TypeToPathMap[row.monitorType]; + navigate(`/${path}/${row.monitorId}`); + }, + rowSX: { + cursor: "pointer", + "&:hover td": { + backgroundColor: theme.palette.tertiary.main, + transition: "background-color .3s ease", + }, + }, + }} + /> + + ); +}; + +JobTable.propTypes = { + jobs: PropTypes.array, +}; + +export default JobTable; diff --git a/client/src/Pages/Queue/components/MetricsTable/index.jsx b/client/src/Pages/Queue/components/MetricsTable/index.jsx new file mode 100644 index 000000000..818b4e543 --- /dev/null +++ b/client/src/Pages/Queue/components/MetricsTable/index.jsx @@ -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 {row.key}; + }, + }, + { + id: "value", + content: t("queuePage.metricsTable.valueHeader"), + render: (row) => { + return {row.value}; + }, + }, + ]; + + const data = keys + .filter((key) => key !== "jobsWithFailures") + .map((key) => { + return { key: camelToTitle(key), value: metrics[key] }; + }); + + return ( + + {t("queuePage.metricsTable.title")} + + + ); +}; + +Metrics.propTypes = { + metrics: PropTypes.object, +}; + +export default Metrics; diff --git a/client/src/Pages/Queue/index.jsx b/client/src/Pages/Queue/index.jsx new file mode 100644 index 000000000..eb732560c --- /dev/null +++ b/client/src/Pages/Queue/index.jsx @@ -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
Loading...
; + if (error || flushError) return
Error: {error.message}
; + + return ( + + + + + + + + + + + + ); +}; + +export default QueueDetails; From 6c51fc91b48c6a73de7badd6760b6e4bdab53567 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 20 Jun 2025 14:28:20 +0800 Subject: [PATCH 05/14] add dev settings --- client/src/Pages/Settings/SettingsDev.jsx | 37 +++++++++++++++++++++++ client/src/Pages/Settings/index.jsx | 5 +++ 2 files changed, 42 insertions(+) create mode 100644 client/src/Pages/Settings/SettingsDev.jsx diff --git a/client/src/Pages/Settings/SettingsDev.jsx b/client/src/Pages/Settings/SettingsDev.jsx new file mode 100644 index 000000000..1e7c3ea89 --- /dev/null +++ b/client/src/Pages/Settings/SettingsDev.jsx @@ -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 ( + + + + {t("settingsDev")} + + {t("settingsDevDescription")} + + + + + + ); +}; + +export default SettingsDev; diff --git a/client/src/Pages/Settings/index.jsx b/client/src/Pages/Settings/index.jsx index 76eaccd6c..ef57a16c7 100644 --- a/client/src/Pages/Settings/index.jsx +++ b/client/src/Pages/Settings/index.jsx @@ -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} /> + Date: Fri, 20 Jun 2025 14:28:33 +0800 Subject: [PATCH 06/14] add routes and localisation --- client/src/Routes/index.jsx | 5 +++++ client/src/locales/en.json | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/client/src/Routes/index.jsx b/client/src/Routes/index.jsx index 29daa2942..ba27482ff 100644 --- a/client/src/Routes/index.jsx +++ b/client/src/Routes/index.jsx @@ -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={} /> + } + /> Date: Fri, 20 Jun 2025 14:28:43 +0800 Subject: [PATCH 07/14] Add queue hooks --- client/src/Hooks/queueHooks.js | 57 ++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 client/src/Hooks/queueHooks.js diff --git a/client/src/Hooks/queueHooks.js b/client/src/Hooks/queueHooks.js new file mode 100644 index 000000000..f7a25b3d3 --- /dev/null +++ b/client/src/Hooks/queueHooks.js @@ -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 }; From 92d4e9ea12275c1782ec8560bf97b69407eaf962 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 20 Jun 2025 14:29:08 +0800 Subject: [PATCH 08/14] Add additional metrics --- server/service/PulseQueue/PulseQueue.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/server/service/PulseQueue/PulseQueue.js b/server/service/PulseQueue/PulseQueue.js index f4a1b38a1..50a959559 100644 --- a/server/service/PulseQueue/PulseQueue.js +++ b/server/service/PulseQueue/PulseQueue.js @@ -139,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++; @@ -150,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; }; @@ -167,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, }; }); }; From 85a996635ff39a40a17155e66cca22ccfce780ff Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 20 Jun 2025 14:29:27 +0800 Subject: [PATCH 09/14] add controller method for getting all metrics --- server/controllers/queueController.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/server/controllers/queueController.js b/server/controllers/queueController.js index b1a7badee..968106a5a 100755 --- a/server/controllers/queueController.js +++ b/server/controllers/queueController.js @@ -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)); From a7f795795fb84db132cf0a205347dc7272b7bde9 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 20 Jun 2025 14:29:36 +0800 Subject: [PATCH 10/14] add route --- server/routes/queueRoute.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/routes/queueRoute.js b/server/routes/queueRoute.js index dc9b40917..c40d21528 100755 --- a/server/routes/queueRoute.js +++ b/server/routes/queueRoute.js @@ -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"]), From 2b279af0934cc7eb2fa0de4ab82af59654010347 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 20 Jun 2025 14:30:17 +0800 Subject: [PATCH 11/14] localization --- server/locales/en.json | 1 + server/service/stringService.js | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/server/locales/en.json b/server/locales/en.json index a693c6eef..74296d0a5 100755 --- a/server/locales/en.json +++ b/server/locales/en.json @@ -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", diff --git a/server/service/stringService.js b/server/service/stringService.js index b02254255..499aa769f 100755 --- a/server/service/stringService.js +++ b/server/service/stringService.js @@ -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"); } From 3351115e3c8ea011ae4dde05bdbdd2e969a5342f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 20 Jun 2025 14:47:19 +0800 Subject: [PATCH 12/14] fix props --- client/src/Pages/Settings/SettingsDev.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/Pages/Settings/SettingsDev.jsx b/client/src/Pages/Settings/SettingsDev.jsx index 1e7c3ea89..721a17d5c 100644 --- a/client/src/Pages/Settings/SettingsDev.jsx +++ b/client/src/Pages/Settings/SettingsDev.jsx @@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import Link from "../../Components/Link"; -const SettingsDev = (isAdmin, HEADER_SX) => { +const SettingsDev = ({ isAdmin, HEADER_SX }) => { const { t } = useTranslation(); if (!isAdmin) return null; From 105353c9f6e609530399a36cf7aa07ccc02746f8 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Sat, 21 Jun 2025 16:15:34 +0800 Subject: [PATCH 13/14] add factory utils for creating header function --- client/src/Components/Table/TableUtils.js | 11 +++ .../Pages/Queue/components/JobTable/index.jsx | 86 +++---------------- 2 files changed, 23 insertions(+), 74 deletions(-) create mode 100644 client/src/Components/Table/TableUtils.js diff --git a/client/src/Components/Table/TableUtils.js b/client/src/Components/Table/TableUtils.js new file mode 100644 index 000000000..a9f95a5b7 --- /dev/null +++ b/client/src/Components/Table/TableUtils.js @@ -0,0 +1,11 @@ +export const createHeaderFactory = (getCellSx = () => {}) => { + return ({ id, content, onClick = () => {}, render = () => {} }) => { + return { + id, + content, + onClick, + getCellSx, + render, + }; + }; +}; diff --git a/client/src/Pages/Queue/components/JobTable/index.jsx b/client/src/Pages/Queue/components/JobTable/index.jsx index 3bce34fff..3b694a991 100644 --- a/client/src/Pages/Queue/components/JobTable/index.jsx +++ b/client/src/Pages/Queue/components/JobTable/index.jsx @@ -7,6 +7,7 @@ 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(); @@ -33,110 +34,47 @@ const JobTable = ({ jobs = [] }) => { return {}; }; - const headers = [ + const createHeader = createHeaderFactory(buildSx); + const headersData = [ { id: "id", content: t("queuePage.jobTable.idHeader"), - render: (row) => { - return row.monitorId; - }, - getCellSx: (row) => { - return buildSx(row); - }, + render: (row) => row.monitorId, }, { id: "url", content: t("queuePage.jobTable.urlHeader"), - render: (row) => { - return row.monitorUrl; - }, - getCellSx: (row) => { - return buildSx(row); - }, + render: (row) => row.monitorUrl, }, { id: "type", content: t("queuePage.jobTable.typeHeader"), - render: (row) => { - return row.monitorType; - }, - getCellSx: (row) => { - return buildSx(row); - }, + render: (row) => row.monitorType, }, { id: "active", content: t("queuePage.jobTable.activeHeader"), - render: (row) => { - return row.active.toString(); - }, - getCellSx: (row) => { - return buildSx(row); - }, + render: (row) => row.active.toString(), }, { id: "runCount", content: t("queuePage.jobTable.runCountHeader"), - render: (row) => { - return row.runCount; - }, - getCellSx: (row) => { - return buildSx(row); - }, + render: (row) => row.runCount, }, { id: "failCount", content: t("queuePage.jobTable.failCountHeader"), - render: (row) => { - return row.failCount; - }, - getCellSx: (row) => { - return buildSx(row); - }, + render: (row) => row.failCount, }, { 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); - }, + render: (row) => row.lastRunAt || "-", }, ]; + const headers = headersData.map((header) => createHeader(header)); + return ( {t("queuePage.jobTable.title")} From f3072e04475fe1fd1a04a7b0c3c3bf503157709c Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Sat, 21 Jun 2025 16:20:31 +0800 Subject: [PATCH 14/14] add missing columns --- .../Pages/Queue/components/JobTable/index.jsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/client/src/Pages/Queue/components/JobTable/index.jsx b/client/src/Pages/Queue/components/JobTable/index.jsx index 3b694a991..f4aa0768c 100644 --- a/client/src/Pages/Queue/components/JobTable/index.jsx +++ b/client/src/Pages/Queue/components/JobTable/index.jsx @@ -71,6 +71,25 @@ const JobTable = ({ jobs = [] }) => { 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));