diff --git a/Client/src/Pages/Monitors/Configure/index.jsx b/Client/src/Pages/Monitors/Configure/index.jsx index c34be3a2f..12f97ab1e 100644 --- a/Client/src/Pages/Monitors/Configure/index.jsx +++ b/Client/src/Pages/Monitors/Configure/index.jsx @@ -62,6 +62,10 @@ const Configure = () => { useEffect(() => { const data = monitors.find((monitor) => monitor._id === monitorId); + if (!data) { + console.error("Error fetching monitor of id: " + monitorId); + navigate("/not-found"); + } setMonitor({ ...data, }); diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index 852f602e7..bbfba0b43 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -12,7 +12,7 @@ import Button from "../../../Components/Button"; import WestRoundedIcon from "@mui/icons-material/WestRounded"; import GreenCheck from "../../../assets/icons/checkbox-green.svg?react"; import RedCheck from "../../../assets/icons/checkbox-red.svg?react"; -import SettingsIcon from "../../../assets/icons/settings.svg?react"; +import SettingsIcon from "../../../assets/icons/settings-bold.svg?react"; import { formatDuration, formatDurationRounded, @@ -43,45 +43,53 @@ const DetailsPage = () => { useEffect(() => { const fetchMonitor = async () => { - const res = await axiosInstance.get( - `/monitors/${monitorId}?sortOrder=asc`, - { - headers: { - Authorization: `Bearer ${authToken}`, - }, - } - ); - setMonitor(res.data.data); - const data = { - cols: [ - { id: 1, name: "Status" }, - { id: 2, name: "Date & Time" }, - { id: 3, name: "Message" }, - ], - rows: res.data.data.checks.map((check, idx) => { - const status = check.status === true ? "up" : "down"; + try { + const res = await axiosInstance.get( + `/monitors/${monitorId}?sortOrder=asc`, + { + headers: { + Authorization: `Bearer ${authToken}`, + }, + } + ); + setMonitor(res.data.data); + const data = { + cols: [ + { id: 1, name: "Status" }, + { id: 2, name: "Date & Time" }, + { id: 3, name: "Message" }, + ], + rows: res.data.data.checks.map((check, idx) => { + const status = check.status === true ? "up" : "down"; - return { - id: check._id, - data: [ - { - id: idx, - data: ( - - ), - }, - { id: idx + 1, data: new Date(check.createdAt).toLocaleString() }, - { id: idx + 2, data: check.statusCode }, - ], - }; - }), - }; + return { + id: check._id, + data: [ + { + id: idx, + data: ( + + ), + }, + { + id: idx + 1, + data: new Date(check.createdAt).toLocaleString(), + }, + { id: idx + 2, data: check.statusCode }, + ], + }; + }), + }; - setData(data); + setData(data); + } catch (error) { + console.error("Error fetching monitor of id: " + monitorId); + navigate("/not-found"); + } }; fetchMonitor(); }, [monitorId, authToken]); @@ -195,7 +203,11 @@ const DetailsPage = () => { level="tertiary" label="Configure" animate="rotate90" - img={} + img={ + + } onClick={() => navigate(`/monitors/configure/${monitorId}`)} sx={{ ml: "auto", diff --git a/Client/src/Pages/Monitors/index.css b/Client/src/Pages/Monitors/index.css index a9317d5da..180bac0b8 100644 --- a/Client/src/Pages/Monitors/index.css +++ b/Client/src/Pages/Monitors/index.css @@ -9,7 +9,7 @@ .monitors h2.MuiTypography-root { font-size: var(--env-var-font-size-large); } -.monitors .MuiStack-root > button { +.monitors .MuiStack-root > button:not(.MuiIconButton-root) { font-size: var(--env-var-font-size-medium); height: var(--env-var-height-2); min-width: fit-content; diff --git a/Client/src/Pages/Monitors/index.jsx b/Client/src/Pages/Monitors/index.jsx index 145faaec3..3c1d7bc64 100644 --- a/Client/src/Pages/Monitors/index.jsx +++ b/Client/src/Pages/Monitors/index.jsx @@ -41,16 +41,26 @@ import { const Host = ({ params }) => { return ( - { event.stopPropagation(); + window.open(params.url, "_blank", "noreferrer"); + }} + sx={{ + "&:focus": { + outline: "none", + }, + mr: "3px", }} > - - + + {params.title} diff --git a/Client/src/Pages/PageSpeed/CreatePageSpeed/index.jsx b/Client/src/Pages/PageSpeed/CreatePageSpeed/index.jsx index f3b10e174..cb82310c4 100644 --- a/Client/src/Pages/PageSpeed/CreatePageSpeed/index.jsx +++ b/Client/src/Pages/PageSpeed/CreatePageSpeed/index.jsx @@ -22,18 +22,19 @@ const CreatePageSpeed = () => { const { user, authToken } = useSelector((state) => state.auth); const frequencies = [ - { _id: 1, name: "1 minute" }, + { _id: 3, name: "3 minutes" }, + { _id: 5, name: "5 minutes" }, + { _id: 10, name: "10 minutes" }, + { _id: 20, name: "20 minutes" }, + { _id: 60, name: "1 hour" }, { _id: 1440, name: "1 day" }, - { _id: 2880, name: "2 days" }, - { _id: 4320, name: "3 days" }, - { _id: 7200, name: "5 days" }, { _id: 10080, name: "1 week" }, ]; const [form, setForm] = useState({ name: "", url: "", - interval: 1, + interval: 3, }); const [errors, setErrors] = useState({}); diff --git a/Client/src/Pages/PageSpeed/Details/index.jsx b/Client/src/Pages/PageSpeed/Details/index.jsx index 1af0fbba1..ad3b66abc 100644 --- a/Client/src/Pages/PageSpeed/Details/index.jsx +++ b/Client/src/Pages/PageSpeed/Details/index.jsx @@ -400,7 +400,7 @@ const PageSpeedDetails = () => { {pieData?.map((pie) => ( + diff --git a/Server/controllers/queueController.js b/Server/controllers/queueController.js index 813fa9808..cb3e132af 100644 --- a/Server/controllers/queueController.js +++ b/Server/controllers/queueController.js @@ -4,7 +4,7 @@ const SERVICE_NAME = "JobQueue"; const getJobs = async (req, res, next) => { try { - const jobs = await req.jobQueue.getJobs(); + const jobs = await req.jobQueue.getJobStats(); return res.status(200).json({ jobs }); } catch (error) { error.service = SERVICE_NAME; diff --git a/Server/index.js b/Server/index.js index e92a5bb45..1eb5d886b 100644 --- a/Server/index.js +++ b/Server/index.js @@ -19,6 +19,8 @@ const JobQueue = require("./service/jobQueue"); const EmailService = require("./service/emailService"); const PageSpeedService = require("./service/pageSpeedService"); +let cleaningUp = false; + // Need to wrap server setup in a function to handle async nature of JobQueue const startApp = async () => { // ************************** @@ -117,6 +119,11 @@ const startApp = async () => { const pageSpeedService = new PageSpeedService(); const cleanup = async () => { + if (cleaningUp) { + console.log("Already cleaning up"); + return; + } + cleaningUp = true; console.log("Shutting down gracefully"); await jobQueue.obliterate(); console.log("Finished cleanup"); diff --git a/Server/service/jobQueue.js b/Server/service/jobQueue.js index 3fdb91710..12c151abd 100644 --- a/Server/service/jobQueue.js +++ b/Server/service/jobQueue.js @@ -124,6 +124,8 @@ class JobQueue { const load = jobs.length / this.workers.length; return { jobs, load }; } catch (error) { + console.log(error); + throw error; } } @@ -143,8 +145,10 @@ class JobQueue { async scaleWorkers(workerStats) { if (this.workers.length === 0) { // There are no workers, need to add one - const worker = this.createWorker(); - this.workers.push(worker); + for (let i = 0; i < 5; i++) { + const worker = this.createWorker(); + this.workers.push(worker); + } return true; } @@ -168,15 +172,17 @@ class JobQueue { const excessCapacity = workerCapacity - workerStats.jobs.length; // Calculate how many workers to remove const workersToRemove = Math.floor(excessCapacity / JOBS_PER_WORKER); - for (let i = 0; i < workersToRemove; i++) { - const worker = this.workers.pop(); - try { - await worker.close(); - } catch (error) { - // Catch the error instead of throwing it - logger.error(errorMessages.JOB_QUEUE_WORKER_CLOSE, { - service: SERVICE_NAME, - }); + if (this.workers.length > 5) { + for (let i = 0; i < workersToRemove; i++) { + const worker = this.workers.pop(); + try { + await worker.close(); + } catch (error) { + // Catch the error instead of throwing it + logger.error(errorMessages.JOB_QUEUE_WORKER_CLOSE, { + service: SERVICE_NAME, + }); + } } } return true; @@ -196,6 +202,25 @@ class JobQueue { const jobs = await this.queue.getRepeatableJobs(); return jobs; } catch (error) { + console.log(error); + + throw error; + } + } + + async getJobStats() { + try { + const jobs = await this.queue.getJobs(); + const ret = await Promise.all( + jobs.map(async (job) => { + const state = await job.getState(); + return { url: job.data.url, state }; + }) + ); + return { jobs: ret, workers: this.workers.length }; + } catch (error) { + console.log(error); + throw error; } } @@ -210,6 +235,7 @@ class JobQueue { */ async addJob(jobName, payload) { try { + console.log("Adding job", payload.url); // Execute job immediately await this.queue.add(jobName, payload); @@ -221,6 +247,7 @@ class JobQueue { const workerStats = await this.getWorkerStats(); await this.scaleWorkers(workerStats); } catch (error) { + console.log(error); throw error; } } @@ -264,9 +291,12 @@ class JobQueue { await this.queue.removeRepeatableByKey(job.key); await this.queue.remove(job.id); } - this.workers.forEach(async (worker) => { - await worker.close(); - }); + await Promise.all( + this.workers.map(async (worker) => { + await worker.close(); + }) + ); + await this.queue.obliterate(); logger.info(successMessages.JOB_QUEUE_OBLITERATE, { service: SERVICE_NAME,