From e9e509ab0458d01637c91b423c3345a296ba21cb Mon Sep 17 00:00:00 2001 From: Owaise Imdad Date: Tue, 17 Jun 2025 00:11:33 +0530 Subject: [PATCH 01/74] Addition of export. --- .../Components/MonitorCreateHeader/index.jsx | 33 ++++++++++++------ client/src/Hooks/monitorHooks.js | 34 +++++++++++++++++++ client/src/Utils/NetworkService.js | 7 ++++ client/src/locales/en.json | 7 +++- server/controllers/monitorController.js | 27 +++++++++++++++ server/routes/monitorRoute.js | 5 +++ 6 files changed, 102 insertions(+), 11 deletions(-) diff --git a/client/src/Components/MonitorCreateHeader/index.jsx b/client/src/Components/MonitorCreateHeader/index.jsx index b630cb259..7a4d75226 100644 --- a/client/src/Components/MonitorCreateHeader/index.jsx +++ b/client/src/Components/MonitorCreateHeader/index.jsx @@ -3,11 +3,13 @@ import { useNavigate } from "react-router-dom"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; import { useTheme } from "@emotion/react"; +import { useExportMonitors } from "../../Hooks/monitorHooks"; const CreateMonitorHeader = ({ isAdmin, label, isLoading = true, path, bulkPath }) => { const navigate = useNavigate(); const { t } = useTranslation(); const theme = useTheme(); + const [exportMonitors, isExporting] = useExportMonitors() // Use the provided label or fall back to the translated default @@ -29,16 +31,27 @@ const CreateMonitorHeader = ({ isAdmin, label, isLoading = true, path, bulkPath {label || t("createNew")} {bulkPath && ( - + <> + + + + )} ); diff --git a/client/src/Hooks/monitorHooks.js b/client/src/Hooks/monitorHooks.js index 7e46438e0..b3e4a9478 100644 --- a/client/src/Hooks/monitorHooks.js +++ b/client/src/Hooks/monitorHooks.js @@ -453,6 +453,39 @@ const useCreateBulkMonitors = () => { return [createBulkMonitors, isLoading]; }; +const useExportMonitors = () => { + const [isLoading, setIsLoading] = useState(false); + const { t } = useTranslation(); + + const exportMonitors = async () => { + setIsLoading(true); + try { + const blob = await networkService.exportMonitors(); + + // Create a download link + const url = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.setAttribute('download', 'monitors.csv'); + document.body.appendChild(link); + link.click(); + link.remove(); + window.URL.revokeObjectURL(url); + + createToast({ body: t('export.success') }); + return [true, null]; + } catch (err) { + const errorMessage = err?.response?.data?.msg || err.message; + createToast({ body: errorMessage || t('export.failed') }); + return [false, errorMessage]; + } finally { + setIsLoading(false); + } + }; + + return [exportMonitors, isLoading]; +}; + export { useFetchMonitorsWithSummary, useFetchMonitorsWithChecks, @@ -469,4 +502,5 @@ export { useDeleteAllMonitors, useDeleteMonitorStats, useCreateBulkMonitors, + useExportMonitors, }; diff --git a/client/src/Utils/NetworkService.js b/client/src/Utils/NetworkService.js index eee795268..6451f93e8 100644 --- a/client/src/Utils/NetworkService.js +++ b/client/src/Utils/NetworkService.js @@ -1022,6 +1022,13 @@ class NetworkService { const { id, notification } = config; return this.axiosInstance.put(`/notifications/${id}`, notification); } + + async exportMonitors() { + const response = await this.axiosInstance.get("/monitors/export", { + responseType: "blob", + }); + return response.data; + } } export default NetworkService; diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 783cdb179..d71cf3212 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -739,5 +739,10 @@ "ping": "Enter the IP address or hostname to ping (e.g., 192.168.1.100 or example.com) and add a clear display name that appears on the dashboard.", "docker": "Enter the Docker ID of your container. Docker IDs must be the full 64 char Docker ID. You can run docker inspect to get the full container ID.", "port": "Enter the URL or IP of the server, the port number and a clear display name that appears on the dashboard." - } + }, + "export": { + "title": "Export Monitors", + "success": "Monitors exported successfully!", + "failed": "Failed to export monitors" + } } diff --git a/server/controllers/monitorController.js b/server/controllers/monitorController.js index 6c6136fb5..5d71f9cb6 100755 --- a/server/controllers/monitorController.js +++ b/server/controllers/monitorController.js @@ -702,6 +702,33 @@ class MonitorController { next(handleError(error, SERVICE_NAME, "seedDb")); } }; + + exportMonitorsToCSV = async (req, res, next) => { + try { + const { teamId } = req.user; + + const monitors = await this.db.getMonitorsByTeamId({ teamId }); + const csvData = monitors?.filteredMonitors?.map((monitor) => ({ + name: monitor.name, + description: monitor.description, + type: monitor.type, + url: monitor.url, + interval: monitor.interval, + port: monitor.port, + ignoreTlsErrors: monitor.ignoreTlsErrors, + isActive: monitor.isActive, + })); + + const csv = pkg.unparse(csvData); + + res.setHeader("Content-Type", "text/csv"); + res.setHeader("Content-Disposition", "attachment; filename=monitors.csv"); + + res.send(csv); + } catch (error) { + next(handleError(error, SERVICE_NAME, "exportMonitorsToCSV")); + } + }; } export default MonitorController; diff --git a/server/routes/monitorRoute.js b/server/routes/monitorRoute.js index 0dabab407..2c6ae870e 100755 --- a/server/routes/monitorRoute.js +++ b/server/routes/monitorRoute.js @@ -17,6 +17,11 @@ class MonitorRoutes { initRoutes() { this.router.get("/", this.monitorController.getAllMonitors); this.router.get("/uptime", this.monitorController.getAllMonitorsWithUptimeStats); + this.router.get( + "/export", + isAllowed(["admin", "superadmin"]), + this.monitorController.exportMonitorsToCSV + ); this.router.get("/stats/:monitorId", this.monitorController.getMonitorStatsById); this.router.get( "/hardware/details/:monitorId", From 11212c73717e431c62c8138a7d9d9932a270dba9 Mon Sep 17 00:00:00 2001 From: Owaise Imdad Date: Tue, 17 Jun 2025 00:17:46 +0530 Subject: [PATCH 02/74] Formatting was done. --- .../Components/MonitorCreateHeader/index.jsx | 2 +- client/src/Hooks/monitorHooks.js | 56 +++++++++---------- client/src/locales/en.json | 8 +-- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/client/src/Components/MonitorCreateHeader/index.jsx b/client/src/Components/MonitorCreateHeader/index.jsx index 7a4d75226..a62123531 100644 --- a/client/src/Components/MonitorCreateHeader/index.jsx +++ b/client/src/Components/MonitorCreateHeader/index.jsx @@ -9,7 +9,7 @@ const CreateMonitorHeader = ({ isAdmin, label, isLoading = true, path, bulkPath const navigate = useNavigate(); const { t } = useTranslation(); const theme = useTheme(); - const [exportMonitors, isExporting] = useExportMonitors() + const [exportMonitors, isExporting] = useExportMonitors(); // Use the provided label or fall back to the translated default diff --git a/client/src/Hooks/monitorHooks.js b/client/src/Hooks/monitorHooks.js index b3e4a9478..d7c8e4287 100644 --- a/client/src/Hooks/monitorHooks.js +++ b/client/src/Hooks/monitorHooks.js @@ -454,36 +454,36 @@ const useCreateBulkMonitors = () => { }; const useExportMonitors = () => { - const [isLoading, setIsLoading] = useState(false); - const { t } = useTranslation(); + const [isLoading, setIsLoading] = useState(false); + const { t } = useTranslation(); - const exportMonitors = async () => { - setIsLoading(true); - try { - const blob = await networkService.exportMonitors(); - - // Create a download link - const url = window.URL.createObjectURL(blob); - const link = document.createElement('a'); - link.href = url; - link.setAttribute('download', 'monitors.csv'); - document.body.appendChild(link); - link.click(); - link.remove(); - window.URL.revokeObjectURL(url); - - createToast({ body: t('export.success') }); - return [true, null]; - } catch (err) { - const errorMessage = err?.response?.data?.msg || err.message; - createToast({ body: errorMessage || t('export.failed') }); - return [false, errorMessage]; - } finally { - setIsLoading(false); - } - }; + const exportMonitors = async () => { + setIsLoading(true); + try { + const blob = await networkService.exportMonitors(); - return [exportMonitors, isLoading]; + // Create a download link + const url = window.URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.setAttribute("download", "monitors.csv"); + document.body.appendChild(link); + link.click(); + link.remove(); + window.URL.revokeObjectURL(url); + + createToast({ body: t("export.success") }); + return [true, null]; + } catch (err) { + const errorMessage = err?.response?.data?.msg || err.message; + createToast({ body: errorMessage || t("export.failed") }); + return [false, errorMessage]; + } finally { + setIsLoading(false); + } + }; + + return [exportMonitors, isLoading]; }; export { diff --git a/client/src/locales/en.json b/client/src/locales/en.json index d71cf3212..d13d44f7f 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -741,8 +741,8 @@ "port": "Enter the URL or IP of the server, the port number and a clear display name that appears on the dashboard." }, "export": { - "title": "Export Monitors", - "success": "Monitors exported successfully!", - "failed": "Failed to export monitors" - } + "title": "Export Monitors", + "success": "Monitors exported successfully!", + "failed": "Failed to export monitors" + } } From e71e2ef42cebeee6c8b5962f4e1023b5d5355d94 Mon Sep 17 00:00:00 2001 From: Owaise Imdad Date: Wed, 18 Jun 2025 01:07:22 +0530 Subject: [PATCH 03/74] Addressed all the comments in pr. --- .../src/Components/MonitorActions/index.jsx | 116 ++++++++++++++++++ .../Components/MonitorCreateHeader/index.jsx | 27 +--- client/src/Hooks/monitorHooks.js | 4 +- client/src/Utils/NetworkService.js | 2 +- client/src/locales/en.json | 5 + server/controllers/monitorController.js | 20 ++- server/middleware/responseHandler.js | 7 +- 7 files changed, 147 insertions(+), 34 deletions(-) create mode 100644 client/src/Components/MonitorActions/index.jsx diff --git a/client/src/Components/MonitorActions/index.jsx b/client/src/Components/MonitorActions/index.jsx new file mode 100644 index 000000000..7086cb307 --- /dev/null +++ b/client/src/Components/MonitorActions/index.jsx @@ -0,0 +1,116 @@ +import * as React from "react"; +import Button from "@mui/material/Button"; +import ButtonGroup from "@mui/material/ButtonGroup"; +import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown"; +import ClickAwayListener from "@mui/material/ClickAwayListener"; +import Grow from "@mui/material/Grow"; +import Paper from "@mui/material/Paper"; +import Popper from "@mui/material/Popper"; +import MenuItem from "@mui/material/MenuItem"; +import MenuList from "@mui/material/MenuList"; +import { useNavigate } from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import { createToast } from "../../Utils/toastUtils"; +import { useExportMonitors } from "../../Hooks/monitorHooks"; + +const options = ["Import Monitors", "Export Monitors"]; + +const MonitorActions = ({ isLoading }) => { + const [open, setOpen] = React.useState(false); + const anchorRef = React.useRef(null); + const [selectedIndex, setSelectedIndex] = React.useState(0); + const navigate = useNavigate(); + const { t } = useTranslation(); + const [exportMonitors, isExporting] = useExportMonitors(); + + const handleClick = async () => { + if (selectedIndex === 0) { + // Import + navigate("/uptime/bulk-import"); + } else { + // Export + const [success, error] = await exportMonitors(); + if (!success) { + createToast({ body: error || t("export.failed") }); + } + } + }; + + const handleMenuItemClick = (event, index) => { + setSelectedIndex(index); + setOpen(false); + }; + + const handleToggle = () => { + setOpen((prevOpen) => !prevOpen); + }; + + const handleClose = (event) => { + if (anchorRef.current && anchorRef.current.contains(event.target)) { + return; + } + setOpen(false); + }; + + return ( + + + + + + + {({ TransitionProps, placement }) => ( + + + + + {options.map((option, index) => ( + handleMenuItemClick(event, index)} + > + {option} + + ))} + + + + + )} + + + ); +}; + +export default MonitorActions; diff --git a/client/src/Components/MonitorCreateHeader/index.jsx b/client/src/Components/MonitorCreateHeader/index.jsx index a62123531..f2b6bbce4 100644 --- a/client/src/Components/MonitorCreateHeader/index.jsx +++ b/client/src/Components/MonitorCreateHeader/index.jsx @@ -3,13 +3,12 @@ import { useNavigate } from "react-router-dom"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; import { useTheme } from "@emotion/react"; -import { useExportMonitors } from "../../Hooks/monitorHooks"; +import MonitorActions from "../MonitorActions"; const CreateMonitorHeader = ({ isAdmin, label, isLoading = true, path, bulkPath }) => { const navigate = useNavigate(); const { t } = useTranslation(); const theme = useTheme(); - const [exportMonitors, isExporting] = useExportMonitors(); // Use the provided label or fall back to the translated default @@ -30,29 +29,7 @@ const CreateMonitorHeader = ({ isAdmin, label, isLoading = true, path, bulkPath > {label || t("createNew")} - {bulkPath && ( - <> - - - - - )} + {bulkPath && } ); }; diff --git a/client/src/Hooks/monitorHooks.js b/client/src/Hooks/monitorHooks.js index d7c8e4287..5f303d26a 100644 --- a/client/src/Hooks/monitorHooks.js +++ b/client/src/Hooks/monitorHooks.js @@ -460,10 +460,10 @@ const useExportMonitors = () => { const exportMonitors = async () => { setIsLoading(true); try { - const blob = await networkService.exportMonitors(); + const response = await networkService.exportMonitors(); // Create a download link - const url = window.URL.createObjectURL(blob); + const url = window.URL.createObjectURL(response); const link = document.createElement("a"); link.href = url; link.setAttribute("download", "monitors.csv"); diff --git a/client/src/Utils/NetworkService.js b/client/src/Utils/NetworkService.js index 6451f93e8..1a6b2a516 100644 --- a/client/src/Utils/NetworkService.js +++ b/client/src/Utils/NetworkService.js @@ -1027,7 +1027,7 @@ class NetworkService { const response = await this.axiosInstance.get("/monitors/export", { responseType: "blob", }); - return response.data; + return response; } } diff --git a/client/src/locales/en.json b/client/src/locales/en.json index d13d44f7f..9fde3e9dd 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -744,5 +744,10 @@ "title": "Export Monitors", "success": "Monitors exported successfully!", "failed": "Failed to export monitors" + }, + "monitorActions": { + "title": "Export/Import", + "import": "Import Monitors", + "export": "Export Monitors" } } diff --git a/server/controllers/monitorController.js b/server/controllers/monitorController.js index 5d71f9cb6..612fc328f 100755 --- a/server/controllers/monitorController.js +++ b/server/controllers/monitorController.js @@ -685,7 +685,7 @@ class MonitorController { explain, }); return res.success({ - msg: "OK", // TODO + msg: "OK", data: result, }); } catch (error) { @@ -708,6 +708,12 @@ class MonitorController { const { teamId } = req.user; const monitors = await this.db.getMonitorsByTeamId({ teamId }); + if (!monitors || monitors.length === 0) { + return res.success({ + msg: this.stringService.noMonitorsFound, + data: null, + }); + } const csvData = monitors?.filteredMonitors?.map((monitor) => ({ name: monitor.name, description: monitor.description, @@ -721,10 +727,14 @@ class MonitorController { const csv = pkg.unparse(csvData); - res.setHeader("Content-Type", "text/csv"); - res.setHeader("Content-Disposition", "attachment; filename=monitors.csv"); - - res.send(csv); + return res.success({ + msg: this.stringService.monitorsExported, + data: csv, + headers: { + "Content-Type": "text/csv", + "Content-Disposition": "attachment; filename=monitors.csv", + }, + }); } catch (error) { next(handleError(error, SERVICE_NAME, "exportMonitorsToCSV")); } diff --git a/server/middleware/responseHandler.js b/server/middleware/responseHandler.js index a58e2e845..e051e3fa5 100755 --- a/server/middleware/responseHandler.js +++ b/server/middleware/responseHandler.js @@ -16,7 +16,12 @@ const responseHandler = (req, res, next) => { * @param {*} [options.data=null] - Response data payload * @returns {Object} Express response object */ - res.success = ({ status = 200, msg = "OK", data = null }) => { + res.success = ({ status = 200, msg = "OK", data = null, headers = {} }) => { + // Set custom headers if provided + Object.entries(headers).forEach(([key, value]) => { + res.set(key, value); + }); + return res.status(status).json({ success: true, msg: msg, From 81d830d99e30de8deb18390e1b29de8e078b7e88 Mon Sep 17 00:00:00 2001 From: Owaise Imdad Date: Wed, 18 Jun 2025 22:24:43 +0530 Subject: [PATCH 04/74] Miss with respect to blob type. --- client/src/Hooks/monitorHooks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/Hooks/monitorHooks.js b/client/src/Hooks/monitorHooks.js index 5f303d26a..013e7fc25 100644 --- a/client/src/Hooks/monitorHooks.js +++ b/client/src/Hooks/monitorHooks.js @@ -463,7 +463,7 @@ const useExportMonitors = () => { const response = await networkService.exportMonitors(); // Create a download link - const url = window.URL.createObjectURL(response); + const url = window.URL.createObjectURL(response.data); const link = document.createElement("a"); link.href = url; link.setAttribute("download", "monitors.csv"); From e6b9b5df3c85fcf407556417b98502798f004e70 Mon Sep 17 00:00:00 2001 From: Owaise Imdad Date: Wed, 18 Jun 2025 22:31:48 +0530 Subject: [PATCH 05/74] Translation and localisation. --- client/src/Components/MonitorActions/index.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/Components/MonitorActions/index.jsx b/client/src/Components/MonitorActions/index.jsx index 7086cb307..d28822227 100644 --- a/client/src/Components/MonitorActions/index.jsx +++ b/client/src/Components/MonitorActions/index.jsx @@ -13,8 +13,6 @@ import { useTranslation } from "react-i18next"; import { createToast } from "../../Utils/toastUtils"; import { useExportMonitors } from "../../Hooks/monitorHooks"; -const options = ["Import Monitors", "Export Monitors"]; - const MonitorActions = ({ isLoading }) => { const [open, setOpen] = React.useState(false); const anchorRef = React.useRef(null); @@ -23,6 +21,8 @@ const MonitorActions = ({ isLoading }) => { const { t } = useTranslation(); const [exportMonitors, isExporting] = useExportMonitors(); + const options = [t('monitorActions.import'), t('monitorActions.export')]; + const handleClick = async () => { if (selectedIndex === 0) { // Import From 0f298d6450dce5a9552af9c5fb2e96eb52fbac51 Mon Sep 17 00:00:00 2001 From: Owaise Imdad Date: Wed, 18 Jun 2025 23:23:24 +0530 Subject: [PATCH 06/74] Code for network db settings. --- server/db/models/NetworkCheck.js | 46 +++++++++++++++++ server/db/mongo/modules/networkCheckModule.js | 49 +++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 server/db/models/NetworkCheck.js create mode 100644 server/db/mongo/modules/networkCheckModule.js diff --git a/server/db/models/NetworkCheck.js b/server/db/models/NetworkCheck.js new file mode 100644 index 000000000..a6f204b12 --- /dev/null +++ b/server/db/models/NetworkCheck.js @@ -0,0 +1,46 @@ +import mongoose from "mongoose"; +import { BaseCheckSchema } from "./Check.js"; + +const networkInterfaceSchema = mongoose.Schema({ + name: { type: String, required: true }, + bytes_sent: { type: Number, default: 0 }, + bytes_recv: { type: Number, default: 0 }, + packets_sent: { type: Number, default: 0 }, + packets_recv: { type: Number, default: 0 }, + err_in: { type: Number, default: 0 }, + err_out: { type: Number, default: 0 }, + drop_in: { type: Number, default: 0 }, + drop_out: { type: Number, default: 0 }, + fifo_in: { type: Number, default: 0 }, + fifo_out: { type: Number, default: 0 } +}); + +const captureSchema = mongoose.Schema({ + version: { type: String, default: "" }, + mode: { type: String, default: "" } +}); + +const NetworkCheckSchema = mongoose.Schema( + { + ...BaseCheckSchema.obj, + data: { + type: [networkInterfaceSchema], + default: () => [] + }, + capture: { + type: captureSchema, + default: () => ({}) + }, + errors: { + type: mongoose.Schema.Types.Mixed, + default: null + } + }, + { timestamps: true } +); + +NetworkCheckSchema.index({ createdAt: 1 }); +NetworkCheckSchema.index({ monitorId: 1, createdAt: 1 }); +NetworkCheckSchema.index({ monitorId: 1, createdAt: -1 }); + +export default mongoose.model("NetworkCheck", NetworkCheckSchema); \ No newline at end of file diff --git a/server/db/mongo/modules/networkCheckModule.js b/server/db/mongo/modules/networkCheckModule.js new file mode 100644 index 000000000..9aa860302 --- /dev/null +++ b/server/db/mongo/modules/networkCheckModule.js @@ -0,0 +1,49 @@ +import NetworkCheck from "../../models/NetworkCheck.js"; +import logger from "../../../utils/logger.js"; + +const SERVICE_NAME = "networkCheckModule"; + +const createNetworkCheck = async (networkCheckData) => { + try { + const { monitorId, status } = networkCheckData; + const n = (await NetworkCheck.countDocuments({ monitorId })) + 1; + + const networkCheck = await new NetworkCheck({ + ...networkCheckData, + n, + }); + + await networkCheck.save(); + return networkCheck; + } catch (error) { + logger.error({ + message: error.message, + service: SERVICE_NAME, + method: "createNetworkCheck", + stack: error.stack, + }); + throw error; + } +}; + +const getNetworkChecksByMonitorId = async (monitorId, limit = 100) => { + try { + const networkChecks = await NetworkCheck.find({ monitorId }) + .sort({ createdAt: -1 }) + .limit(limit); + return networkChecks; + } catch (error) { + logger.error({ + message: error.message, + service: SERVICE_NAME, + method: "getNetworkChecksByMonitorId", + stack: error.stack, + }); + throw error; + } +}; + +export { + createNetworkCheck, + getNetworkChecksByMonitorId, +}; \ No newline at end of file From 8fe3ed9bd1cfad8e58c2c3c90b3bd170d133f74c Mon Sep 17 00:00:00 2001 From: Owaise Imdad Date: Wed, 18 Jun 2025 23:30:39 +0530 Subject: [PATCH 07/74] Added formating. --- server/db/models/NetworkCheck.js | 60 ++++++++-------- server/db/mongo/modules/networkCheckModule.js | 69 +++++++++---------- 2 files changed, 63 insertions(+), 66 deletions(-) diff --git a/server/db/models/NetworkCheck.js b/server/db/models/NetworkCheck.js index a6f204b12..eca69eef9 100644 --- a/server/db/models/NetworkCheck.js +++ b/server/db/models/NetworkCheck.js @@ -2,45 +2,45 @@ import mongoose from "mongoose"; import { BaseCheckSchema } from "./Check.js"; const networkInterfaceSchema = mongoose.Schema({ - name: { type: String, required: true }, - bytes_sent: { type: Number, default: 0 }, - bytes_recv: { type: Number, default: 0 }, - packets_sent: { type: Number, default: 0 }, - packets_recv: { type: Number, default: 0 }, - err_in: { type: Number, default: 0 }, - err_out: { type: Number, default: 0 }, - drop_in: { type: Number, default: 0 }, - drop_out: { type: Number, default: 0 }, - fifo_in: { type: Number, default: 0 }, - fifo_out: { type: Number, default: 0 } + name: { type: String, required: true }, + bytes_sent: { type: Number, default: 0 }, + bytes_recv: { type: Number, default: 0 }, + packets_sent: { type: Number, default: 0 }, + packets_recv: { type: Number, default: 0 }, + err_in: { type: Number, default: 0 }, + err_out: { type: Number, default: 0 }, + drop_in: { type: Number, default: 0 }, + drop_out: { type: Number, default: 0 }, + fifo_in: { type: Number, default: 0 }, + fifo_out: { type: Number, default: 0 }, }); const captureSchema = mongoose.Schema({ - version: { type: String, default: "" }, - mode: { type: String, default: "" } + version: { type: String, default: "" }, + mode: { type: String, default: "" }, }); const NetworkCheckSchema = mongoose.Schema( - { - ...BaseCheckSchema.obj, - data: { - type: [networkInterfaceSchema], - default: () => [] - }, - capture: { - type: captureSchema, - default: () => ({}) - }, - errors: { - type: mongoose.Schema.Types.Mixed, - default: null - } - }, - { timestamps: true } + { + ...BaseCheckSchema.obj, + data: { + type: [networkInterfaceSchema], + default: () => [], + }, + capture: { + type: captureSchema, + default: () => ({}), + }, + errors: { + type: mongoose.Schema.Types.Mixed, + default: null, + }, + }, + { timestamps: true } ); NetworkCheckSchema.index({ createdAt: 1 }); NetworkCheckSchema.index({ monitorId: 1, createdAt: 1 }); NetworkCheckSchema.index({ monitorId: 1, createdAt: -1 }); -export default mongoose.model("NetworkCheck", NetworkCheckSchema); \ No newline at end of file +export default mongoose.model("NetworkCheck", NetworkCheckSchema); diff --git a/server/db/mongo/modules/networkCheckModule.js b/server/db/mongo/modules/networkCheckModule.js index 9aa860302..14c5824b8 100644 --- a/server/db/mongo/modules/networkCheckModule.js +++ b/server/db/mongo/modules/networkCheckModule.js @@ -4,46 +4,43 @@ import logger from "../../../utils/logger.js"; const SERVICE_NAME = "networkCheckModule"; const createNetworkCheck = async (networkCheckData) => { - try { - const { monitorId, status } = networkCheckData; - const n = (await NetworkCheck.countDocuments({ monitorId })) + 1; + try { + const { monitorId, status } = networkCheckData; + const n = (await NetworkCheck.countDocuments({ monitorId })) + 1; - const networkCheck = await new NetworkCheck({ - ...networkCheckData, - n, - }); + const networkCheck = await new NetworkCheck({ + ...networkCheckData, + n, + }); - await networkCheck.save(); - return networkCheck; - } catch (error) { - logger.error({ - message: error.message, - service: SERVICE_NAME, - method: "createNetworkCheck", - stack: error.stack, - }); - throw error; - } + await networkCheck.save(); + return networkCheck; + } catch (error) { + logger.error({ + message: error.message, + service: SERVICE_NAME, + method: "createNetworkCheck", + stack: error.stack, + }); + throw error; + } }; const getNetworkChecksByMonitorId = async (monitorId, limit = 100) => { - try { - const networkChecks = await NetworkCheck.find({ monitorId }) - .sort({ createdAt: -1 }) - .limit(limit); - return networkChecks; - } catch (error) { - logger.error({ - message: error.message, - service: SERVICE_NAME, - method: "getNetworkChecksByMonitorId", - stack: error.stack, - }); - throw error; - } + try { + const networkChecks = await NetworkCheck.find({ monitorId }) + .sort({ createdAt: -1 }) + .limit(limit); + return networkChecks; + } catch (error) { + logger.error({ + message: error.message, + service: SERVICE_NAME, + method: "getNetworkChecksByMonitorId", + stack: error.stack, + }); + throw error; + } }; -export { - createNetworkCheck, - getNetworkChecksByMonitorId, -}; \ No newline at end of file +export { createNetworkCheck, getNetworkChecksByMonitorId }; From a1c11fbb3cccaf8dc91ffde0e81a2555fdb71374 Mon Sep 17 00:00:00 2001 From: Owaise Imdad Date: Thu, 19 Jun 2025 22:23:10 +0530 Subject: [PATCH 08/74] Added a new method for file download and correct csv is getting added. --- server/controllers/monitorController.js | 3 +-- server/middleware/responseHandler.js | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/server/controllers/monitorController.js b/server/controllers/monitorController.js index 612fc328f..024a18a27 100755 --- a/server/controllers/monitorController.js +++ b/server/controllers/monitorController.js @@ -727,8 +727,7 @@ class MonitorController { const csv = pkg.unparse(csvData); - return res.success({ - msg: this.stringService.monitorsExported, + return res.file({ data: csv, headers: { "Content-Type": "text/csv", diff --git a/server/middleware/responseHandler.js b/server/middleware/responseHandler.js index e051e3fa5..5169e7160 100755 --- a/server/middleware/responseHandler.js +++ b/server/middleware/responseHandler.js @@ -45,6 +45,21 @@ const responseHandler = (req, res, next) => { data, }); }; + + /** + * Sends a raw file response (for CSV, PDF, etc.) + * @param {Object} options + * @param {Buffer|string} options.data - The file content + * @param {Object} options.headers - Headers to set (e.g. Content-Type, Content-Disposition) + * @param {number} [options.status=200] - HTTP status code + */ + res.file = ({ data, headers = {}, status = 200 }) => { + Object.entries(headers).forEach(([key, value]) => { + res.setHeader(key, value); + }); + return res.status(status).send(data); + }; + next(); }; From 9f88fbbcf20fa7f09941515fb5b186c74adad601 Mon Sep 17 00:00:00 2001 From: Owaise Imdad Date: Thu, 19 Jun 2025 22:32:53 +0530 Subject: [PATCH 09/74] Formatting issues. --- client/src/Components/MonitorActions/index.jsx | 2 +- client/src/locales/en.json | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/client/src/Components/MonitorActions/index.jsx b/client/src/Components/MonitorActions/index.jsx index d28822227..ac5d89a47 100644 --- a/client/src/Components/MonitorActions/index.jsx +++ b/client/src/Components/MonitorActions/index.jsx @@ -21,7 +21,7 @@ const MonitorActions = ({ isLoading }) => { const { t } = useTranslation(); const [exportMonitors, isExporting] = useExportMonitors(); - const options = [t('monitorActions.import'), t('monitorActions.export')]; + const options = [t("monitorActions.import"), t("monitorActions.export")]; const handleClick = async () => { if (selectedIndex === 0) { diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 2fc2f1bb3..a22e8123d 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -745,9 +745,7 @@ "settingsEmailSecure": "Secure - Use SSL", "settingsEmailPool": "Pool - Enable connection pooling", "sendTestNotifications": "Send test notifications", - "selectAll": "Select all" -} - }, + "selectAll": "Select all", "export": { "title": "Export Monitors", "success": "Monitors exported successfully!", From 223203737ff4313b80dcb6088079f2817649a2a4 Mon Sep 17 00:00:00 2001 From: Owaise Imdad Date: Thu, 19 Jun 2025 22:42:07 +0530 Subject: [PATCH 10/74] Logging error only once as middleware takes care of it. --- server/db/mongo/modules/networkCheckModule.js | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/server/db/mongo/modules/networkCheckModule.js b/server/db/mongo/modules/networkCheckModule.js index 14c5824b8..b515ba5af 100644 --- a/server/db/mongo/modules/networkCheckModule.js +++ b/server/db/mongo/modules/networkCheckModule.js @@ -16,12 +16,8 @@ const createNetworkCheck = async (networkCheckData) => { await networkCheck.save(); return networkCheck; } catch (error) { - logger.error({ - message: error.message, - service: SERVICE_NAME, - method: "createNetworkCheck", - stack: error.stack, - }); + error.service = SERVICE_NAME; + error.method = "createNetworkCheck"; throw error; } }; @@ -33,12 +29,8 @@ const getNetworkChecksByMonitorId = async (monitorId, limit = 100) => { .limit(limit); return networkChecks; } catch (error) { - logger.error({ - message: error.message, - service: SERVICE_NAME, - method: "getNetworkChecksByMonitorId", - stack: error.stack, - }); + error.service = SERVICE_NAME; + error.method = "getNetworkChecksByMonitorId"; throw error; } }; From 4cf38048988ee2665a1dff945b26a02d1b5a6ffb Mon Sep 17 00:00:00 2001 From: Owaise Imdad Date: Thu, 19 Jun 2025 22:45:04 +0530 Subject: [PATCH 11/74] Formatting issues. --- client/src/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/locales/en.json b/client/src/locales/en.json index ff878c3da..fd89d31f5 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -746,4 +746,4 @@ "settingsEmailPool": "Pool - Enable connection pooling", "sendTestNotifications": "Send test notifications", "selectAll": "Select all" -} \ No newline at end of file +} From 1187f9d4cc9628354648b8dd983e7aa1f24cb66c Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 20 Jun 2025 10:08:13 +0800 Subject: [PATCH 12/74] add fix button loading state --- client/src/Hooks/useNotifications.js | 2 +- client/src/Pages/Notifications/create/index.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/Hooks/useNotifications.js b/client/src/Hooks/useNotifications.js index 69e03fd5e..cc6c3e875 100644 --- a/client/src/Hooks/useNotifications.js +++ b/client/src/Hooks/useNotifications.js @@ -161,7 +161,7 @@ const useTestNotification = () => { } catch (error) { setError(error); createToast({ - body: t("notifications.test.failed"), + body: error?.response?.data?.msg || t("notifications.test.failed"), }); } finally { setIsLoading(false); diff --git a/client/src/Pages/Notifications/create/index.jsx b/client/src/Pages/Notifications/create/index.jsx index fba30185f..0fb6cce08 100644 --- a/client/src/Pages/Notifications/create/index.jsx +++ b/client/src/Pages/Notifications/create/index.jsx @@ -204,7 +204,7 @@ const CreateNotifications = () => { spacing={theme.spacing(2)} > + + + + ); +}; + +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 17/74] 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 18/74] 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 19/74] 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 20/74] 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 21/74] 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 22/74] 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 23/74] 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 24/74] 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 9700551f41edf53d2d3d86ded9cce5ce5bbae265 Mon Sep 17 00:00:00 2001 From: Owaise Imdad Date: Sat, 21 Jun 2025 00:18:09 +0530 Subject: [PATCH 25/74] Removal of n count of document. --- server/db/mongo/modules/networkCheckModule.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/server/db/mongo/modules/networkCheckModule.js b/server/db/mongo/modules/networkCheckModule.js index b515ba5af..608113a73 100644 --- a/server/db/mongo/modules/networkCheckModule.js +++ b/server/db/mongo/modules/networkCheckModule.js @@ -5,14 +5,7 @@ const SERVICE_NAME = "networkCheckModule"; const createNetworkCheck = async (networkCheckData) => { try { - const { monitorId, status } = networkCheckData; - const n = (await NetworkCheck.countDocuments({ monitorId })) + 1; - - const networkCheck = await new NetworkCheck({ - ...networkCheckData, - n, - }); - + const networkCheck = await new NetworkCheck(networkCheckData); await networkCheck.save(); return networkCheck; } catch (error) { From ba00e9afffe2660da19d3b0c1a299597983e7fb0 Mon Sep 17 00:00:00 2001 From: Owaise Imdad Date: Sat, 21 Jun 2025 00:25:30 +0530 Subject: [PATCH 26/74] Added JSDocs in module file. --- server/db/mongo/modules/networkCheckModule.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/server/db/mongo/modules/networkCheckModule.js b/server/db/mongo/modules/networkCheckModule.js index 608113a73..8d44af96f 100644 --- a/server/db/mongo/modules/networkCheckModule.js +++ b/server/db/mongo/modules/networkCheckModule.js @@ -1,8 +1,15 @@ import NetworkCheck from "../../models/NetworkCheck.js"; -import logger from "../../../utils/logger.js"; const SERVICE_NAME = "networkCheckModule"; +/** + * Creates and saves a new network check document to the database. + * @async + * @param {object} networkCheckData - The data for the new network check. This should conform to the NetworkCheckSchema. + * @param {string} networkCheckData.monitorId - The ID of the monitor associated with this check. + * @returns {Promise} A promise that resolves to the newly created network check document. + * @throws {Error} Throws an error if the database operation fails. + */ const createNetworkCheck = async (networkCheckData) => { try { const networkCheck = await new NetworkCheck(networkCheckData); From ecb1ca4517badd21970b11579e95978d80e231a7 Mon Sep 17 00:00:00 2001 From: Owaise Imdad Date: Sat, 21 Jun 2025 00:25:57 +0530 Subject: [PATCH 27/74] Missed to add a line in jsdoc. --- server/db/mongo/modules/networkCheckModule.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/server/db/mongo/modules/networkCheckModule.js b/server/db/mongo/modules/networkCheckModule.js index 8d44af96f..53c55c7e3 100644 --- a/server/db/mongo/modules/networkCheckModule.js +++ b/server/db/mongo/modules/networkCheckModule.js @@ -22,6 +22,14 @@ const createNetworkCheck = async (networkCheckData) => { } }; +/** + * Retrieves a list of network checks for a specific monitor, sorted by most recent. + * @async + * @param {string} monitorId - The ID of the monitor to retrieve checks for. + * @param {number} [limit=100] - The maximum number of checks to return. Defaults to 100. + * @returns {Promise>} A promise that resolves to an array of network check documents. + * @throws {Error} Throws an error if the database operation fails. + */ const getNetworkChecksByMonitorId = async (monitorId, limit = 100) => { try { const networkChecks = await NetworkCheck.find({ monitorId }) From 452d1a2ad5168a8b0022cd3e2e184ec35ba95bfa Mon Sep 17 00:00:00 2001 From: mohadeseh safari Date: Fri, 20 Jun 2025 19:58:11 -0400 Subject: [PATCH 28/74] Fix missing password error translation for auth.common.fields.password.errors.incorrect --- client/src/locales/en.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/client/src/locales/en.json b/client/src/locales/en.json index a7d9fe069..c374b48e8 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -507,6 +507,13 @@ }, "auth": { "common": { + "fields": { + "password": { + "errors": { + "incorrect": "The password you provided does not match our records" + } + } + }, "navigation": { "continue": "Continue", "back": "Back" From 105353c9f6e609530399a36cf7aa07ccc02746f8 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Sat, 21 Jun 2025 16:15:34 +0800 Subject: [PATCH 29/74] 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 30/74] 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)); From f853a034910fddfc4c2266672208ab3c7ff23d08 Mon Sep 17 00:00:00 2001 From: Sky-walkerX Date: Sun, 22 Jun 2025 17:17:26 +0530 Subject: [PATCH 31/74] refactor(settings): email settings toggle in one row --- client/src/Pages/Settings/SettingsEmail.jsx | 58 ++++++++++----------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/client/src/Pages/Settings/SettingsEmail.jsx b/client/src/Pages/Settings/SettingsEmail.jsx index 4bd6ff3ad..061cf2602 100644 --- a/client/src/Pages/Settings/SettingsEmail.jsx +++ b/client/src/Pages/Settings/SettingsEmail.jsx @@ -212,37 +212,35 @@ const SettingsEmail = ({ gap: theme.spacing(4), }} > - {t("settingsEmailSecure")} - - {t("settingsEmailPool")} - - {t("settingsEmailIgnoreTLS")} - - {t("settingsEmailRequireTLS")} - - {t("settingsEmailRejectUnauthorized")} - + {[ + ["settingsEmailSecure", "systemEmailSecure", systemEmailSecure], + ["settingsEmailPool", "systemEmailPool", systemEmailPool], + ["settingsEmailIgnoreTLS", "systemEmailIgnoreTLS", systemEmailIgnoreTLS], + ["settingsEmailRequireTLS", "systemEmailRequireTLS", systemEmailRequireTLS], + [ + "settingsEmailRejectUnauthorized", + "systemEmailRejectUnauthorized", + systemEmailRejectUnauthorized, + ], + ].map(([labelKey, name, value]) => ( + + {t(labelKey)} + + + ))} + {systemEmailHost && systemEmailPort && From 9972c3cf9a31b266e238f84020c24285a70b863f Mon Sep 17 00:00:00 2001 From: Sky-walkerX Date: Sun, 22 Jun 2025 17:35:45 +0530 Subject: [PATCH 32/74] refactor(settings): changed wordings of email settings --- client/src/locales/en.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/src/locales/en.json b/client/src/locales/en.json index d3a274a32..b29c290a9 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -749,11 +749,11 @@ }, "advancedMatching": "Advanced matching", "settingsEmailTLSServername": "TLS Servername - Optional Hostname for TLS Validation when host is an IP", - "settingsEmailIgnoreTLS": "Ignore TLS - Disable STARTTLS", - "settingsEmailRequireTLS": "Require TLS - Force STARTTLS", - "settingsEmailRejectUnauthorized": "Reject Unauthorized", - "settingsEmailSecure": "Secure - Use SSL", - "settingsEmailPool": "Pool - Enable connection pooling", + "settingsEmailIgnoreTLS": "Disable STARTTLS: Don't use TLS even if the server supports it", + "settingsEmailRequireTLS": "Force STARTTLS: Require TLS upgrade, fail if not supported", + "settingsEmailRejectUnauthorized": "Reject invalid certificates: Reject connections with self-signed or untrusted certificates", + "settingsEmailSecure": "Use SSL (recommended): Encrypt the connection using SSL/TLS", + "settingsEmailPool": "Enable connection pooling: Reuse existing connections to improve performance", "sendTestNotifications": "Send test notifications", "selectAll": "Select all", "queuePage": { From 3a6d3071be4ca68fd225142ff362e721f91c7011 Mon Sep 17 00:00:00 2001 From: Sky-walkerX Date: Sun, 22 Jun 2025 17:39:42 +0530 Subject: [PATCH 33/74] chore: format --- client/src/locales/en.json | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/client/src/locales/en.json b/client/src/locales/en.json index b29c290a9..14317e689 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -785,15 +785,16 @@ "failCountHeader": "Fail count", "failedAtHeader": "Last failed at", "failReasonHeader": "Fail reason" + }, + "export": { + "title": "Export Monitors", + "success": "Monitors exported successfully!", + "failed": "Failed to export monitors" + }, + "monitorActions": { + "title": "Export/Import", + "import": "Import Monitors", + "export": "Export Monitors" } - "export": { - "title": "Export Monitors", - "success": "Monitors exported successfully!", - "failed": "Failed to export monitors" - }, - "monitorActions": { - "title": "Export/Import", - "import": "Import Monitors", - "export": "Export Monitors" } } From 50e9a6b967ce3f32c4c390a58d1bcf45f8fb22c6 Mon Sep 17 00:00:00 2001 From: Sky-walkerX Date: Sun, 22 Jun 2025 19:40:42 +0530 Subject: [PATCH 34/74] chore: formatted NetworkService --- client/src/Utils/NetworkService.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/Utils/NetworkService.js b/client/src/Utils/NetworkService.js index d9a3853e1..651f72fe0 100644 --- a/client/src/Utils/NetworkService.js +++ b/client/src/Utils/NetworkService.js @@ -1033,8 +1033,8 @@ class NetworkService { async flushQueue() { return this.axiosInstance.post(`/queue/flush`); - - + } + async exportMonitors() { const response = await this.axiosInstance.get("/monitors/export", { responseType: "blob", From 31321749b20a963d1d2c2e4d5da4d9972e3a6884 Mon Sep 17 00:00:00 2001 From: Jeffer Marcelino Date: Sun, 22 Jun 2025 17:11:01 +0200 Subject: [PATCH 35/74] fix: syntax errors in NetworkService.js and en.json causing build failure --- client/src/Utils/NetworkService.js | 2 +- client/src/locales/en.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/Utils/NetworkService.js b/client/src/Utils/NetworkService.js index d9a3853e1..e7021d585 100644 --- a/client/src/Utils/NetworkService.js +++ b/client/src/Utils/NetworkService.js @@ -1033,7 +1033,7 @@ class NetworkService { async flushQueue() { return this.axiosInstance.post(`/queue/flush`); - + } async exportMonitors() { const response = await this.axiosInstance.get("/monitors/export", { diff --git a/client/src/locales/en.json b/client/src/locales/en.json index d3a274a32..81a664290 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -786,6 +786,7 @@ "failedAtHeader": "Last failed at", "failReasonHeader": "Fail reason" } + }, "export": { "title": "Export Monitors", "success": "Monitors exported successfully!", From 7c5516cd7505dc9045d9cad92a8246d4de25fc19 Mon Sep 17 00:00:00 2001 From: Jeffer Marcelino Date: Sun, 22 Jun 2025 18:26:20 +0200 Subject: [PATCH 36/74] feat(api): add backend support for toggling admin login link visibility on status page --- server/db/models/StatusPage.js | 4 ++++ server/db/mongo/modules/statusPageModule.js | 1 + server/validation/joi.js | 1 + 3 files changed, 6 insertions(+) diff --git a/server/db/models/StatusPage.js b/server/db/models/StatusPage.js index e4c81707f..dd64c184a 100755 --- a/server/db/models/StatusPage.js +++ b/server/db/models/StatusPage.js @@ -70,6 +70,10 @@ const StatusPageSchema = mongoose.Schema( type: Boolean, default: true, }, + showAdminLoginLink: { + type: Boolean, + default: false, + } }, { timestamps: true } ); diff --git a/server/db/mongo/modules/statusPageModule.js b/server/db/mongo/modules/statusPageModule.js index d6331fcf8..e94ffbd55 100755 --- a/server/db/mongo/modules/statusPageModule.js +++ b/server/db/mongo/modules/statusPageModule.js @@ -153,6 +153,7 @@ const getStatusPage = async (url) => { showCharts: 1, showUptimePercentage: 1, timezone: 1, + showAdminLoginLink: 1, url: 1, }, monitors: { diff --git a/server/validation/joi.js b/server/validation/joi.js index 448a844f1..e5c1d6e43 100755 --- a/server/validation/joi.js +++ b/server/validation/joi.js @@ -463,6 +463,7 @@ const createStatusPageBodyValidation = joi.object({ isPublished: joi.boolean(), showCharts: joi.boolean().optional(), showUptimePercentage: joi.boolean(), + showAdminLoginLink: joi.boolean().optional(), }); const imageValidation = joi From e6fd57d0bb4a1649bdd41415d231d7834a57a354 Mon Sep 17 00:00:00 2001 From: Jeffer Marcelino Date: Sun, 22 Jun 2025 18:27:56 +0200 Subject: [PATCH 37/74] feat: optionally hide the "Administrator? Login Here" on status page --- client/src/Pages/StatusPage/Status/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/Pages/StatusPage/Status/index.jsx b/client/src/Pages/StatusPage/Status/index.jsx index 727daeeb1..45822f6c6 100644 --- a/client/src/Pages/StatusPage/Status/index.jsx +++ b/client/src/Pages/StatusPage/Status/index.jsx @@ -48,7 +48,7 @@ const PublicStatus = () => { let link = undefined; const isPublic = location.pathname.startsWith("/status/uptime/public"); // Public status page - if (isPublic) { + if (isPublic && statusPage && statusPage.showAdminLoginLink === true) { sx = { paddingTop: theme.spacing(20), paddingLeft: "20vw", From 4a36bd3157c24ef48cb375457db96187938fa1ad Mon Sep 17 00:00:00 2001 From: Jeffer Marcelino Date: Sun, 22 Jun 2025 18:28:17 +0200 Subject: [PATCH 38/74] =?UTF-8?q?feat:=20add=20toggle=20to=20show/hide=20?= =?UTF-8?q?=E2=80=9CAdministrator=3F=20Login=20Here=E2=80=9D=20link=20in?= =?UTF-8?q?=20status=20page=20settings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Pages/StatusPage/Create/Components/Tabs/Content.jsx | 7 +++++++ client/src/Pages/StatusPage/Create/index.jsx | 4 +++- client/src/Utils/NetworkService.js | 3 +++ client/src/Validation/validation.js | 1 + client/src/locales/en.json | 1 + client/src/locales/pt-BR.json | 1 + 6 files changed, 16 insertions(+), 1 deletion(-) diff --git a/client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx b/client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx index 7423b05bd..327a2fb0a 100644 --- a/client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx +++ b/client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx @@ -90,6 +90,13 @@ const Content = ({ isChecked={form.showUptimePercentage} onChange={handleFormChange} /> + diff --git a/client/src/Pages/StatusPage/Create/index.jsx b/client/src/Pages/StatusPage/Create/index.jsx index 50535cd90..63c248786 100644 --- a/client/src/Pages/StatusPage/Create/index.jsx +++ b/client/src/Pages/StatusPage/Create/index.jsx @@ -20,7 +20,7 @@ const TAB_LIST = ["General settings", "Contents"]; const ERROR_TAB_MAPPING = [ ["companyName", "url", "timezone", "color", "isPublished", "logo"], - ["monitors", "showUptimePercentage", "showCharts"], + ["monitors", "showUptimePercentage", "showCharts", "showAdminLoginLink"], ]; const CreateStatusPage = () => { @@ -39,6 +39,7 @@ const CreateStatusPage = () => { monitors: [], showCharts: true, showUptimePercentage: true, + showAdminLoginLink: false, }); const [errors, setErrors] = useState({}); const [selectedMonitors, setSelectedMonitors] = useState([]); @@ -195,6 +196,7 @@ const CreateStatusPage = () => { logo: newLogo, showCharts: statusPage?.showCharts ?? true, showUptimePercentage: statusPage?.showUptimePercentage ?? true, + showAdminLoginLink: statusPage?.showAdminLoginLink ?? false, }; }); setSelectedMonitors(statusPageMonitors); diff --git a/client/src/Utils/NetworkService.js b/client/src/Utils/NetworkService.js index e7021d585..20281abf5 100644 --- a/client/src/Utils/NetworkService.js +++ b/client/src/Utils/NetworkService.js @@ -871,6 +871,9 @@ class NetworkService { if (form.showUptimePercentage !== undefined) { fd.append("showUptimePercentage", String(form.showUptimePercentage)); } + if (form.showAdminLoginLink !== undefined) { + fd.append("showAdminLoginLink", String(form.showAdminLoginLink)); + } form.monitors && form.monitors.forEach((monitorId) => { fd.append("monitors[]", monitorId); diff --git a/client/src/Validation/validation.js b/client/src/Validation/validation.js index 4e6a806c8..d86bbd556 100644 --- a/client/src/Validation/validation.js +++ b/client/src/Validation/validation.js @@ -277,6 +277,7 @@ const statusPageValidation = joi.object({ logo: logoImageValidation, showUptimePercentage: joi.boolean(), showCharts: joi.boolean(), + showAdminLoginLink: joi.boolean(), }); const settingsValidation = joi.object({ diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 81a664290..818258905 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -319,6 +319,7 @@ "statusPageCreateTabsContentFeaturesDescription": "Show more details on the status page", "showCharts": "Show charts", "showUptimePercentage": "Show uptime percentage", + "showAdminLoginLink": "Show \"Administrator? Login Here\" link on the status page", "removeLogo": "Remove Logo", "statusPageStatus": "A public status page is not set up.", "statusPageStatusContactAdmin": "Please contact to your administrator", diff --git a/client/src/locales/pt-BR.json b/client/src/locales/pt-BR.json index a783a820c..7368c360c 100644 --- a/client/src/locales/pt-BR.json +++ b/client/src/locales/pt-BR.json @@ -312,6 +312,7 @@ "statusPageCreateTabsContentFeaturesDescription": "Mostrar mais detalhes na página de status", "showCharts": "Mostrar gráficos", "showUptimePercentage": "Mostrar porcentagem de Uptime", + "showAdminLoginLink": "Mostrar o link \"Administrador? Efetue login aqui\" na página de status", "removeLogo": "Remover logo", "statusPageStatus": "Uma página de status pública não está configurada.", "statusPageStatusContactAdmin": "Entre em contato com seu administrador", From 465bdd29457159c650b94a3cb459f81a14acea3b Mon Sep 17 00:00:00 2001 From: Jeffer Marcelino Date: Sun, 22 Jun 2025 18:33:58 +0200 Subject: [PATCH 39/74] chore: format --- client/src/Utils/NetworkService.js | 2 +- server/db/models/StatusPage.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/Utils/NetworkService.js b/client/src/Utils/NetworkService.js index 20281abf5..653ed5c5b 100644 --- a/client/src/Utils/NetworkService.js +++ b/client/src/Utils/NetworkService.js @@ -1037,7 +1037,7 @@ class NetworkService { async flushQueue() { return this.axiosInstance.post(`/queue/flush`); } - + async exportMonitors() { const response = await this.axiosInstance.get("/monitors/export", { responseType: "blob", diff --git a/server/db/models/StatusPage.js b/server/db/models/StatusPage.js index dd64c184a..92cbab1cd 100755 --- a/server/db/models/StatusPage.js +++ b/server/db/models/StatusPage.js @@ -73,7 +73,7 @@ const StatusPageSchema = mongoose.Schema( showAdminLoginLink: { type: Boolean, default: false, - } + }, }, { timestamps: true } ); From 5fcf8ce7ba240fa9f7aa5a62bb57a87b8f176bc3 Mon Sep 17 00:00:00 2001 From: Jeffer Marcelino Date: Mon, 23 Jun 2025 02:58:51 +0200 Subject: [PATCH 40/74] chore: format --- client/src/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 17907f189..0fe4ceb3d 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -797,5 +797,5 @@ "import": "Import Monitors", "export": "Export Monitors" } - }, + } } From ddf9673a35b734fb7a839b9de8afed3675555366 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 23 Jun 2025 09:24:33 +0800 Subject: [PATCH 41/74] remove redis from production docker copose --- docker/prod/docker-compose.yaml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/docker/prod/docker-compose.yaml b/docker/prod/docker-compose.yaml index 9cc4a82cb..d9bc1109a 100755 --- a/docker/prod/docker-compose.yaml +++ b/docker/prod/docker-compose.yaml @@ -29,21 +29,9 @@ services: env_file: - server.env depends_on: - - redis - mongodb volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - redis: - image: ghcr.io/bluewave-labs/checkmate:redis-demo - restart: always - volumes: - - ./redis/data:/data - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 30s - timeout: 10s - retries: 5 - start_period: 5s mongodb: image: ghcr.io/bluewave-labs/checkmate:mongo-demo restart: always From 3faa6194f39e2091bd7a622bb66d20acbc3ccd7c Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 23 Jun 2025 10:02:06 +0800 Subject: [PATCH 42/74] display transport settings --- client/src/Components/TextLink/index.jsx | 4 +- client/src/Pages/Settings/SettingsEmail.jsx | 44 ++++++++++++++++++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/client/src/Components/TextLink/index.jsx b/client/src/Components/TextLink/index.jsx index a10839e84..83ae251a5 100644 --- a/client/src/Components/TextLink/index.jsx +++ b/client/src/Components/TextLink/index.jsx @@ -5,7 +5,7 @@ import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; import { Link as RouterLink } from "react-router-dom"; -const TextLink = ({ text, linkText, href }) => { +const TextLink = ({ text, linkText, href, target = "_self" }) => { const theme = useTheme(); return ( @@ -18,6 +18,7 @@ const TextLink = ({ text, linkText, href }) => { color="accent" to={href} component={RouterLink} + target={target} > {linkText} @@ -29,6 +30,7 @@ TextLink.propTypes = { text: PropTypes.string, linkText: PropTypes.string, href: PropTypes.string, + target: PropTypes.string, }; export default TextLink; diff --git a/client/src/Pages/Settings/SettingsEmail.jsx b/client/src/Pages/Settings/SettingsEmail.jsx index 061cf2602..07ee050bb 100644 --- a/client/src/Pages/Settings/SettingsEmail.jsx +++ b/client/src/Pages/Settings/SettingsEmail.jsx @@ -4,6 +4,8 @@ import ConfigBox from "../../Components/ConfigBox"; import TextInput from "../../Components/Inputs/TextInput"; import Button from "@mui/material/Button"; import Stack from "@mui/material/Stack"; +import { Switch } from "@mui/material"; +import TextLink from "../../Components/TextLink"; // Utils import { useTheme } from "@emotion/react"; import { PropTypes } from "prop-types"; @@ -12,7 +14,6 @@ import { useTranslation } from "react-i18next"; import { PasswordEndAdornment } from "../../Components/Inputs/TextInput/Adornments"; import { useSendTestEmail } from "../../Hooks/useSendTestEmail"; import { createToast } from "../../Utils/toastUtils"; -import { Switch } from "@mui/material"; const SettingsEmail = ({ isAdmin, @@ -239,6 +240,47 @@ const SettingsEmail = ({ /> ))} + + + + + {JSON.stringify( + { + host: systemEmailHost, + port: systemEmailPort, + secure: systemEmailSecure, + auth: { + user: systemEmailUser || systemEmailAddress, + pass: "", + }, + name: systemEmailConnectionHost || "localhost", + pool: systemEmailPool, + user: systemEmailUser, + tls: { + rejectUnauthorized: systemEmailRejectUnauthorized, + ignoreTLS: systemEmailIgnoreTLS, + requireTLS: systemEmailRequireTLS, + servername: systemEmailTLSServername, + }, + }, + null, + 2 + )} + + From 766c073ec4014ab1f361fb74d8bb0db74d2262c2 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 23 Jun 2025 10:05:06 +0800 Subject: [PATCH 43/74] localisation --- client/src/Pages/Settings/SettingsEmail.jsx | 4 ++-- client/src/locales/en.json | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/client/src/Pages/Settings/SettingsEmail.jsx b/client/src/Pages/Settings/SettingsEmail.jsx index 07ee050bb..c63b9edf5 100644 --- a/client/src/Pages/Settings/SettingsEmail.jsx +++ b/client/src/Pages/Settings/SettingsEmail.jsx @@ -242,8 +242,8 @@ const SettingsEmail = ({ ))} diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 14317e689..f677c346c 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -450,6 +450,8 @@ "settingsEmailPassword": "Email password - Password for authentication", "settingsEmailUser": "Email user - Username for authentication, overrides email address if specified", "settingsEmailFieldResetLabel": "Password is set. Click Reset to change it.", + "settingsEmailTransportLinkDescription": "This builds an SMTP transport for NodeMailer", + "settingsEmailTransportLinkText": "See specifications here", "state": "State", "statusBreadCrumbsStatusPages": "Status Pages", "statusBreadCrumbsDetails": "Details", From 34485a0cae285d7c3f83dddc92c878f1365fb3d2 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 23 Jun 2025 11:07:12 +0800 Subject: [PATCH 44/74] move queue to logs --- client/src/Components/Sidebar/index.jsx | 3 +- client/src/Components/StatusBoxes/index.jsx | 2 +- .../Queue/components/FailedJobTable/index.jsx | 4 +- .../Queue/components/JobTable/index.jsx | 6 +-- .../Logs/Queue/components/Metrics/index.jsx | 45 +++++++++++++++++++ .../Queue/components/MetricsTable/index.jsx | 2 +- client/src/Pages/{ => Logs}/Queue/index.jsx | 9 ++-- client/src/Pages/Logs/index.jsx | 41 +++++++++++++++++ client/src/Pages/Settings/SettingsDev.jsx | 37 --------------- client/src/Pages/Settings/index.jsx | 6 +-- client/src/Routes/index.jsx | 7 +-- client/src/Utils/Theme/globalTheme.js | 3 ++ client/src/assets/icons/logs.svg | 5 +++ client/src/locales/en.json | 11 +++-- 14 files changed, 119 insertions(+), 62 deletions(-) rename client/src/Pages/{ => Logs}/Queue/components/FailedJobTable/index.jsx (93%) rename client/src/Pages/{ => Logs}/Queue/components/JobTable/index.jsx (93%) create mode 100644 client/src/Pages/Logs/Queue/components/Metrics/index.jsx rename client/src/Pages/{ => Logs}/Queue/components/MetricsTable/index.jsx (95%) rename client/src/Pages/{ => Logs}/Queue/index.jsx (82%) create mode 100644 client/src/Pages/Logs/index.jsx delete mode 100644 client/src/Pages/Settings/SettingsDev.jsx create mode 100644 client/src/assets/icons/logs.svg diff --git a/client/src/Components/Sidebar/index.jsx b/client/src/Components/Sidebar/index.jsx index 06a140c54..e9f0d3ad7 100644 --- a/client/src/Components/Sidebar/index.jsx +++ b/client/src/Components/Sidebar/index.jsx @@ -8,7 +8,6 @@ import { ListItemButton, ListItemIcon, ListItemText, - ListSubheader, Menu, MenuItem, Stack, @@ -39,6 +38,7 @@ import Docs from "../../assets/icons/docs.svg?react"; import StatusPages from "../../assets/icons/status-pages.svg?react"; import Discussions from "../../assets/icons/discussions.svg?react"; import Notifications from "../../assets/icons/notifications.svg?react"; +import Logs from "../../assets/icons/logs.svg?react"; import "./index.css"; @@ -64,6 +64,7 @@ const getMenu = (t) => [ { name: t("menu.statusPages"), path: "status", icon: }, { name: t("menu.maintenance"), path: "maintenance", icon: }, + { name: t("menu.logs"), path: "logs", icon: }, { name: t("menu.settings"), diff --git a/client/src/Components/StatusBoxes/index.jsx b/client/src/Components/StatusBoxes/index.jsx index 40ea2d182..d97bb6211 100644 --- a/client/src/Components/StatusBoxes/index.jsx +++ b/client/src/Components/StatusBoxes/index.jsx @@ -4,7 +4,7 @@ import SkeletonLayout from "./skeleton"; // Utils import { useTheme } from "@mui/material/styles"; import PropTypes from "prop-types"; -const StatusBoxes = ({ shouldRender, flexWrap = "nowrap", children }) => { +const StatusBoxes = ({ shouldRender = true, flexWrap = "nowrap", children }) => { const theme = useTheme(); if (!shouldRender) { return ( diff --git a/client/src/Pages/Queue/components/FailedJobTable/index.jsx b/client/src/Pages/Logs/Queue/components/FailedJobTable/index.jsx similarity index 93% rename from client/src/Pages/Queue/components/FailedJobTable/index.jsx rename to client/src/Pages/Logs/Queue/components/FailedJobTable/index.jsx index d7284f9da..94fc2f5c3 100644 --- a/client/src/Pages/Queue/components/FailedJobTable/index.jsx +++ b/client/src/Pages/Logs/Queue/components/FailedJobTable/index.jsx @@ -1,10 +1,10 @@ import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import DataTable from "../../../../Components/Table"; +import DataTable from "../../../../../Components/Table"; import { useNavigate } from "react-router-dom"; import { useTheme } from "@emotion/react"; -import { TypeToPathMap } from "../../../../Utils/monitorUtils"; +import { TypeToPathMap } from "../../../../../Utils/monitorUtils"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/Queue/components/JobTable/index.jsx b/client/src/Pages/Logs/Queue/components/JobTable/index.jsx similarity index 93% rename from client/src/Pages/Queue/components/JobTable/index.jsx rename to client/src/Pages/Logs/Queue/components/JobTable/index.jsx index f4aa0768c..e27dd103f 100644 --- a/client/src/Pages/Queue/components/JobTable/index.jsx +++ b/client/src/Pages/Logs/Queue/components/JobTable/index.jsx @@ -1,13 +1,13 @@ import Stack from "@mui/material/Stack"; -import DataTable from "../../../../Components/Table"; +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 { TypeToPathMap } from "../../../../../Utils/monitorUtils"; import { useTranslation } from "react-i18next"; -import { createHeaderFactory } from "../../../../Components/Table/TableUtils"; +import { createHeaderFactory } from "../../../../../Components/Table/TableUtils"; const JobTable = ({ jobs = [] }) => { const theme = useTheme(); diff --git a/client/src/Pages/Logs/Queue/components/Metrics/index.jsx b/client/src/Pages/Logs/Queue/components/Metrics/index.jsx new file mode 100644 index 000000000..52117cd3b --- /dev/null +++ b/client/src/Pages/Logs/Queue/components/Metrics/index.jsx @@ -0,0 +1,45 @@ +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import StatBox from "../../../../../Components/StatBox"; +import StatusBoxes from "../../../../../Components/StatusBoxes"; + +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] }; + }); + + console.log(JSON.stringify(data, null, 2)); + + return ( + + {t("queuePage.metricsTable.title")} + + {data.map((metric) => { + return ( + + ); + })} + + + ); +}; +export default Metrics; diff --git a/client/src/Pages/Queue/components/MetricsTable/index.jsx b/client/src/Pages/Logs/Queue/components/MetricsTable/index.jsx similarity index 95% rename from client/src/Pages/Queue/components/MetricsTable/index.jsx rename to client/src/Pages/Logs/Queue/components/MetricsTable/index.jsx index 818b4e543..204b90035 100644 --- a/client/src/Pages/Queue/components/MetricsTable/index.jsx +++ b/client/src/Pages/Logs/Queue/components/MetricsTable/index.jsx @@ -1,6 +1,6 @@ import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import DataTable from "../../../../Components/Table"; +import DataTable from "../../../../../Components/Table"; // Utils import { useTranslation } from "react-i18next"; diff --git a/client/src/Pages/Queue/index.jsx b/client/src/Pages/Logs/Queue/index.jsx similarity index 82% rename from client/src/Pages/Queue/index.jsx rename to client/src/Pages/Logs/Queue/index.jsx index eb732560c..729f87fe3 100644 --- a/client/src/Pages/Queue/index.jsx +++ b/client/src/Pages/Logs/Queue/index.jsx @@ -1,15 +1,14 @@ // Components import Stack from "@mui/material/Stack"; -import Breadcrumbs from "../../Components/Breadcrumbs"; import JobTable from "./components/JobTable"; -import MetricsTable from "./components/MetricsTable"; +import Metrics from "./components/Metrics"; 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 { useFetchQueueData, useFlushQueue } from "../../../Hooks/queueHooks"; import { useTranslation } from "react-i18next"; import { useTheme } from "@emotion/react"; @@ -23,15 +22,13 @@ const QueueDetails = () => { 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 ( - + - { + 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 ( + + + + + + + {value === 0 && } + {value === 1 &&
Not yet
} +
+ ); +}; + +export default Logs; diff --git a/client/src/Pages/Settings/SettingsDev.jsx b/client/src/Pages/Settings/SettingsDev.jsx deleted file mode 100644 index 721a17d5c..000000000 --- a/client/src/Pages/Settings/SettingsDev.jsx +++ /dev/null @@ -1,37 +0,0 @@ -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 ef57a16c7..0cd954e52 100644 --- a/client/src/Pages/Settings/index.jsx +++ b/client/src/Pages/Settings/index.jsx @@ -8,7 +8,6 @@ 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"; @@ -209,10 +208,7 @@ const Settings = () => { emailPasswordHasBeenReset={emailPasswordHasBeenReset} setEmailPasswordHasBeenReset={setEmailPasswordHasBeenReset} /> - + { const AdminCheckedRegister = withAdminCheck(AuthRegister); @@ -187,9 +187,10 @@ const Routes = () => { path="account/team" element={} /> + } + path="logs" + element={} /> diff --git a/client/src/Utils/Theme/globalTheme.js b/client/src/Utils/Theme/globalTheme.js index 58d9943be..ae014432d 100644 --- a/client/src/Utils/Theme/globalTheme.js +++ b/client/src/Utils/Theme/globalTheme.js @@ -407,6 +407,7 @@ const baseTheme = (palette) => ({ }), }, }, + MuiTab: { styleOverrides: { root: ({ theme }) => ({ @@ -475,6 +476,8 @@ const baseTheme = (palette) => ({ MuiTabs: { styleOverrides: { root: ({ theme }) => ({ + height: "34px", + minHeight: "34px", display: "inline-flex", borderRadius: 0, "& .MuiTabs-indicator": { diff --git a/client/src/assets/icons/logs.svg b/client/src/assets/icons/logs.svg new file mode 100644 index 000000000..e1adcb390 --- /dev/null +++ b/client/src/assets/icons/logs.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 14317e689..0856592fc 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -30,9 +30,6 @@ "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", @@ -431,6 +428,7 @@ "statusPages": "Status pages", "maintenance": "Maintenance", "integrations": "Integrations", + "logs": "Logs", "settings": "Settings", "support": "Support", "discussions": "Discussions", @@ -756,6 +754,13 @@ "settingsEmailPool": "Enable connection pooling: Reuse existing connections to improve performance", "sendTestNotifications": "Send test notifications", "selectAll": "Select all", + "logsPage": { + "title": "Logs", + "tabs": { + "queue": "Job queue", + "logs": "Logs" + } + }, "queuePage": { "title": "Queue", "refreshButton": "Refresh", From c6075817670ad5b07d750f0311e1c405d36cf090 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 23 Jun 2025 03:11:03 +0000 Subject: [PATCH 45/74] feat: translations updated from POEditor --- client/src/locales/ar.json | 14 +- client/src/locales/cs.json | 14 +- client/src/locales/de.json | 14 +- client/src/locales/en.json | 84 +++-------- client/src/locales/es.json | 14 +- client/src/locales/fi.json | 14 +- client/src/locales/fr.json | 14 +- client/src/locales/pt-BR.json | 14 +- client/src/locales/ru.json | 14 +- client/src/locales/tr.json | 254 ++++++++++++++++++---------------- client/src/locales/zh-TW.json | 14 +- 11 files changed, 250 insertions(+), 214 deletions(-) diff --git a/client/src/locales/ar.json b/client/src/locales/ar.json index fd4d925a9..06789abb0 100644 --- a/client/src/locales/ar.json +++ b/client/src/locales/ar.json @@ -583,6 +583,13 @@ }, "errors": { "validation": "" + }, + "fields": { + "password": { + "errors": { + "incorrect": "" + } + } } }, "login": { @@ -709,13 +716,13 @@ "integrationKeyLabel": "", "integrationKeyPlaceholder": "" }, - "genericWebhookSettings": { + "discordSettings": { "title": "", "description": "", "webhookLabel": "", "webhookPlaceholder": "" }, - "discordSettings": { + "webhookSettings": { "title": "", "description": "", "webhookLabel": "", @@ -740,5 +747,6 @@ "settingsEmailRejectUnauthorized": "", "settingsEmailSecure": "", "settingsEmailPool": "", - "sendTestNotifications": "" + "sendTestNotifications": "", + "selectAll": "" } diff --git a/client/src/locales/cs.json b/client/src/locales/cs.json index 66b2993d2..110d9a508 100644 --- a/client/src/locales/cs.json +++ b/client/src/locales/cs.json @@ -583,6 +583,13 @@ }, "errors": { "validation": "Nastala chyba při ověřování dat." + }, + "fields": { + "password": { + "errors": { + "incorrect": "" + } + } } }, "login": { @@ -709,13 +716,13 @@ "integrationKeyLabel": "", "integrationKeyPlaceholder": "" }, - "genericWebhookSettings": { + "discordSettings": { "title": "", "description": "", "webhookLabel": "", "webhookPlaceholder": "" }, - "discordSettings": { + "webhookSettings": { "title": "", "description": "", "webhookLabel": "", @@ -740,5 +747,6 @@ "settingsEmailRejectUnauthorized": "", "settingsEmailSecure": "", "settingsEmailPool": "", - "sendTestNotifications": "" + "sendTestNotifications": "", + "selectAll": "" } diff --git a/client/src/locales/de.json b/client/src/locales/de.json index 6d5c5a4d8..5561188a0 100644 --- a/client/src/locales/de.json +++ b/client/src/locales/de.json @@ -583,6 +583,13 @@ }, "errors": { "validation": "" + }, + "fields": { + "password": { + "errors": { + "incorrect": "" + } + } } }, "login": { @@ -709,13 +716,13 @@ "integrationKeyLabel": "", "integrationKeyPlaceholder": "" }, - "genericWebhookSettings": { + "discordSettings": { "title": "", "description": "", "webhookLabel": "", "webhookPlaceholder": "" }, - "discordSettings": { + "webhookSettings": { "title": "", "description": "", "webhookLabel": "", @@ -740,5 +747,6 @@ "settingsEmailRejectUnauthorized": "", "settingsEmailSecure": "", "settingsEmailPool": "", - "sendTestNotifications": "" + "sendTestNotifications": "", + "selectAll": "" } diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 14317e689..b7db06202 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -30,9 +30,6 @@ "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", @@ -510,13 +507,6 @@ }, "auth": { "common": { - "fields": { - "password": { - "errors": { - "incorrect": "The password you provided does not match our records" - } - } - }, "navigation": { "continue": "Continue", "back": "Back" @@ -597,6 +587,13 @@ }, "errors": { "validation": "Error validating data." + }, + "fields": { + "password": { + "errors": { + "incorrect": "The password you provided does not match our records" + } + } } }, "login": { @@ -717,23 +714,23 @@ "webhookLabel": "Slack webhook URL", "webhookPlaceholder": "https://hooks.slack.com/services/..." }, - "pagerDutySettings": { + "pagerdutySettings": { "title": "PagerDuty", "description": "Configure your PagerDuty integration here", "integrationKeyLabel": "Integration key", "integrationKeyPlaceholder": "1234567890" }, - "webhookSettings": { - "title": "Webhook", - "description": "Configure your webhook here", - "webhookLabel": "Webhook URL", - "webhookPlaceholder": "https://your-server.com/webhook" - }, "discordSettings": { "title": "Discord", "description": "Configure your Discord webhook here", "webhookLabel": "Discord Webhook URL", "webhookPlaceholder": "https://your-server.com/webhook" + }, + "webhookSettings": { + "title": "Webhook", + "description": "Configure your webhook here", + "webhookLabel": "Webhook URL", + "webhookPlaceholder": "https://your-server.com/webhook" } }, "notificationConfig": { @@ -749,52 +746,11 @@ }, "advancedMatching": "Advanced matching", "settingsEmailTLSServername": "TLS Servername - Optional Hostname for TLS Validation when host is an IP", - "settingsEmailIgnoreTLS": "Disable STARTTLS: Don't use TLS even if the server supports it", - "settingsEmailRequireTLS": "Force STARTTLS: Require TLS upgrade, fail if not supported", - "settingsEmailRejectUnauthorized": "Reject invalid certificates: Reject connections with self-signed or untrusted certificates", - "settingsEmailSecure": "Use SSL (recommended): Encrypt the connection using SSL/TLS", - "settingsEmailPool": "Enable connection pooling: Reuse existing connections to improve performance", + "settingsEmailIgnoreTLS": "Ignore TLS - Disable STARTTLS", + "settingsEmailRequireTLS": "Require TLS - Force STARTTLS", + "settingsEmailRejectUnauthorized": "Reject Unauthorized", + "settingsEmailSecure": "Secure - Use SSL", + "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!", - "failed": "Failed to export monitors" - }, - "monitorActions": { - "title": "Export/Import", - "import": "Import Monitors", - "export": "Export Monitors" - } - } + "selectAll": "Select all" } diff --git a/client/src/locales/es.json b/client/src/locales/es.json index fb8207e48..b23a57ed3 100644 --- a/client/src/locales/es.json +++ b/client/src/locales/es.json @@ -583,6 +583,13 @@ }, "errors": { "validation": "" + }, + "fields": { + "password": { + "errors": { + "incorrect": "" + } + } } }, "login": { @@ -709,13 +716,13 @@ "integrationKeyLabel": "", "integrationKeyPlaceholder": "" }, - "genericWebhookSettings": { + "discordSettings": { "title": "", "description": "", "webhookLabel": "", "webhookPlaceholder": "" }, - "discordSettings": { + "webhookSettings": { "title": "", "description": "", "webhookLabel": "", @@ -740,5 +747,6 @@ "settingsEmailRejectUnauthorized": "", "settingsEmailSecure": "", "settingsEmailPool": "", - "sendTestNotifications": "" + "sendTestNotifications": "", + "selectAll": "" } diff --git a/client/src/locales/fi.json b/client/src/locales/fi.json index f085b5857..44b56516b 100644 --- a/client/src/locales/fi.json +++ b/client/src/locales/fi.json @@ -583,6 +583,13 @@ }, "errors": { "validation": "" + }, + "fields": { + "password": { + "errors": { + "incorrect": "" + } + } } }, "login": { @@ -709,13 +716,13 @@ "integrationKeyLabel": "", "integrationKeyPlaceholder": "" }, - "genericWebhookSettings": { + "discordSettings": { "title": "", "description": "", "webhookLabel": "", "webhookPlaceholder": "" }, - "discordSettings": { + "webhookSettings": { "title": "", "description": "", "webhookLabel": "", @@ -740,5 +747,6 @@ "settingsEmailRejectUnauthorized": "", "settingsEmailSecure": "", "settingsEmailPool": "", - "sendTestNotifications": "" + "sendTestNotifications": "", + "selectAll": "" } diff --git a/client/src/locales/fr.json b/client/src/locales/fr.json index 48834602e..0f218d1c6 100644 --- a/client/src/locales/fr.json +++ b/client/src/locales/fr.json @@ -583,6 +583,13 @@ }, "errors": { "validation": "" + }, + "fields": { + "password": { + "errors": { + "incorrect": "" + } + } } }, "login": { @@ -709,13 +716,13 @@ "integrationKeyLabel": "", "integrationKeyPlaceholder": "" }, - "genericWebhookSettings": { + "discordSettings": { "title": "", "description": "", "webhookLabel": "", "webhookPlaceholder": "" }, - "discordSettings": { + "webhookSettings": { "title": "", "description": "", "webhookLabel": "", @@ -740,5 +747,6 @@ "settingsEmailRejectUnauthorized": "", "settingsEmailSecure": "", "settingsEmailPool": "", - "sendTestNotifications": "" + "sendTestNotifications": "", + "selectAll": "" } diff --git a/client/src/locales/pt-BR.json b/client/src/locales/pt-BR.json index a783a820c..74a0b654f 100644 --- a/client/src/locales/pt-BR.json +++ b/client/src/locales/pt-BR.json @@ -583,6 +583,13 @@ }, "errors": { "validation": "" + }, + "fields": { + "password": { + "errors": { + "incorrect": "" + } + } } }, "login": { @@ -709,13 +716,13 @@ "integrationKeyLabel": "", "integrationKeyPlaceholder": "" }, - "genericWebhookSettings": { + "discordSettings": { "title": "", "description": "", "webhookLabel": "", "webhookPlaceholder": "" }, - "discordSettings": { + "webhookSettings": { "title": "", "description": "", "webhookLabel": "", @@ -740,5 +747,6 @@ "settingsEmailRejectUnauthorized": "", "settingsEmailSecure": "", "settingsEmailPool": "", - "sendTestNotifications": "" + "sendTestNotifications": "", + "selectAll": "" } diff --git a/client/src/locales/ru.json b/client/src/locales/ru.json index 539bb2d51..65a4fe4a0 100644 --- a/client/src/locales/ru.json +++ b/client/src/locales/ru.json @@ -583,6 +583,13 @@ }, "errors": { "validation": "" + }, + "fields": { + "password": { + "errors": { + "incorrect": "" + } + } } }, "login": { @@ -709,13 +716,13 @@ "integrationKeyLabel": "", "integrationKeyPlaceholder": "" }, - "genericWebhookSettings": { + "discordSettings": { "title": "", "description": "", "webhookLabel": "", "webhookPlaceholder": "" }, - "discordSettings": { + "webhookSettings": { "title": "", "description": "", "webhookLabel": "", @@ -740,5 +747,6 @@ "settingsEmailRejectUnauthorized": "", "settingsEmailSecure": "", "settingsEmailPool": "", - "sendTestNotifications": "" + "sendTestNotifications": "", + "selectAll": "" } diff --git a/client/src/locales/tr.json b/client/src/locales/tr.json index 1d86de06c..ed03d7e6c 100644 --- a/client/src/locales/tr.json +++ b/client/src/locales/tr.json @@ -117,11 +117,11 @@ "title": "", "checks": [""] }, - "createButton": "", - "createTitle": "", + "createButton": "Bildirim kanalı oluştur", + "createTitle": "Bildirim kanalı", "create": { "success": "", - "failed": "" + "failed": "Bildirim kanalı oluşturulamadı" }, "fetch": { "success": "", @@ -136,7 +136,7 @@ "failed": "" }, "test": { - "success": "", + "success": "Test bildirimi başarıyla gönderildi", "failed": "" } }, @@ -373,33 +373,33 @@ "pageSpeedApiKeyFieldDescription": "Sayfa hızı izlemeyi etkinleştirmek için Google PageSpeed API anahtarınızı girin. Anahtarı güncellemek için Sıfırla'ya tıklayın.", "pageSpeedApiKeyFieldResetLabel": "API anahtarı ayarlandı. Değiştirmek için Sıfırla'ya tıklayın.", "reset": "Sıfırla", - "ignoreTLSError": "", - "tlsErrorIgnored": "", + "ignoreTLSError": "TLS/SSL hatalarını gözardı et", + "tlsErrorIgnored": "TLS/SSL hataları gözardı ediliyor", "ignoreTLSErrorDescription": "", - "createNew": "", + "createNew": "Yeni oluştur", "greeting": { - "prepend": "", + "prepend": "Merhaba", "append": "", "overview": "" }, "roles": { - "superAdmin": "", - "admin": "", - "teamMember": "", - "demoUser": "" + "superAdmin": "Süper yönetici", + "admin": "Yönetici", + "teamMember": "Ekip elemanı", + "demoUser": "Demo kullanıcısı" }, "teamPanel": { - "teamMembers": "", + "teamMembers": "Ekip elemanları", "filter": { - "all": "", - "member": "" + "all": "Tümü", + "member": "Eleman" }, - "inviteTeamMember": "", - "inviteNewTeamMember": "", + "inviteTeamMember": "Ekip elemanı davet et", + "inviteNewTeamMember": "Yeni ekip elemanı davet et", "inviteDescription": "", - "email": "", - "selectRole": "", - "inviteLink": "", + "email": "Eposta", + "selectRole": "Rolü seçin", + "inviteLink": "Davet linki", "cancel": "", "noMembers": "", "getToken": "", @@ -422,21 +422,21 @@ "infrastructure": "", "incidents": "", "statusPages": "", - "maintenance": "", - "integrations": "", - "settings": "", - "support": "", - "discussions": "", - "docs": "", - "changelog": "", - "profile": "", - "password": "", - "team": "", + "maintenance": "Bakım", + "integrations": "Entegrasyonlar", + "settings": "Ayarlar", + "support": "Destek", + "discussions": "Soru-cevap", + "docs": "Dokümanlar", + "changelog": "Değişiklik günlüğü", + "profile": "Profil", + "password": "Parola", + "team": "Ekip", "logOut": "Çıkış yap", - "notifications": "" + "notifications": "Bildirimler" }, - "settingsEmail": "", - "settingsEmailDescription": "", + "settingsEmail": "Eposta", + "settingsEmailDescription": "Sistem için eposta ayarlarını düzenleyin. Bu işlem eposta üzerinden bildirim ve alarm almak için gereklidir.", "settingsEmailHost": "", "settingsEmailPort": "", "settingsEmailAddress": "", @@ -490,73 +490,73 @@ "port": "" }, "common": { - "appName": "", + "appName": "Checkmate", "monitoringAgentName": "", "buttons": { "toggleTheme": "" }, "toasts": { "networkError": "", - "checkConnection": "", - "unknownError": "" + "checkConnection": "Lütfen bağlantınızı kontrol edin", + "unknownError": "Bilinmeyen hata" } }, "auth": { "common": { "navigation": { - "continue": "", - "back": "" + "continue": "Devam et", + "back": "Geri" }, "inputs": { "email": { - "label": "", + "label": "Eposta", "placeholder": "", "errors": { - "empty": "", + "empty": "Devam etmek için eposta adresinizi girin", "invalid": "" } }, "password": { - "label": "", + "label": "Parola", "rules": { "length": { "beginning": "", - "highlighted": "" + "highlighted": "8 karakter uzunluğunda" }, "special": { - "beginning": "", - "highlighted": "" + "beginning": "En az içermelidir: ", + "highlighted": "bir özel karakter" }, "number": { - "beginning": "", - "highlighted": "" + "beginning": "En az içermelidir: ", + "highlighted": "bir sayı" }, "uppercase": { - "beginning": "", - "highlighted": "" + "beginning": "En az içermelidir: ", + "highlighted": "Bir büyük harf" }, "lowercase": { - "beginning": "", - "highlighted": "" + "beginning": "En az içermelidir: ", + "highlighted": "Bir küçük harf" }, "match": { "beginning": "", - "highlighted": "" + "highlighted": "eşleşmelidir" } }, "errors": { - "empty": "", - "length": "", - "uppercase": "", - "lowercase": "", - "number": "", - "special": "", - "incorrect": "" + "empty": "Lütfen parolanızı girin", + "length": "Parolanız en az 8 karakter uzunluğunda olmalıdır", + "uppercase": "Parola en az 1 büyük harf içermelidir", + "lowercase": "Parola en az 1 küçük harf içermelidir", + "number": "Parola en az 1 sayı içermelidir", + "special": "Parola en az 1 özel karakter içermelidir", + "incorrect": "Parolanız sistemdeki ile uyuşmuyor" } }, "passwordConfirm": { - "label": "", - "placeholder": "", + "label": "Parolayı onayla", + "placeholder": "Onaylamak için parolayı yeniden gir", "errors": { "empty": "", "different": "" @@ -572,78 +572,85 @@ } }, "lastName": { - "label": "", - "placeholder": "", + "label": "Soyadı", + "placeholder": "Ellis", "errors": { - "empty": "", - "length": "", + "empty": "Lütfen soyadınızı girin", + "length": "Soyadı en çok 50 karakter olmalıdır", "pattern": "" } } }, "errors": { "validation": "" + }, + "fields": { + "password": { + "errors": { + "incorrect": "" + } + } } }, "login": { "heading": "", "subheadings": { - "stepOne": "", - "stepTwo": "" + "stepOne": "Eposta adresinizi girin", + "stepTwo": "Parolanızı girin" }, "links": { - "forgotPassword": "", - "register": "", - "forgotPasswordLink": "", - "registerLink": "" + "forgotPassword": "Parolanızı mı unuttunuz?", + "register": "Hesabınız yok mu?", + "forgotPasswordLink": "Parolayı sıfırla", + "registerLink": "Buradan kayıt olun" }, "toasts": { - "success": "", - "incorrectPassword": "" + "success": "Tekrar hoşgeldiniz! Başarıyla sisteme giriş yaptınız.", + "incorrectPassword": "Hatalı parola" }, "errors": { "password": { - "incorrect": "" + "incorrect": "Giriş yaptığınız parola sistemdekiyle uyuşmuyor" } } }, "registration": { "heading": { - "superAdmin": "", - "user": "" + "superAdmin": "Süper yönetici oluştur", + "user": "Kayıt ol" }, "subheadings": { - "stepOne": "", - "stepTwo": "", - "stepThree": "" + "stepOne": "Kişisel bilgilerinizi girin", + "stepTwo": "Eposta adresinizi girin", + "stepThree": "Parolanızı oluturun" }, "description": { - "superAdmin": "", + "superAdmin": "Başlamak için süper yönetici hesabı oluşturun", "user": "" }, "gettingStartedButton": { - "superAdmin": "", + "superAdmin": "Süper yönetici hesabı oluşturun", "user": "" }, "termsAndPolicies": "", "links": { - "login": "" + "login": "Bir hesabınız mı var? Giriş yapın" }, "toasts": { - "success": "" + "success": "Hoşgeldiniz! Hesabınız başarıyla oluşturuldu." } }, "forgotPassword": { - "heading": "", + "heading": "Parolanızı mı kaybettiniz?", "subheadings": { - "stepOne": "", - "stepTwo": "", + "stepOne": "Endişelenmeyin, sıfırlamak için gerekli bilgileri göndereceğiz.", + "stepTwo": "Parola sıfırlama için gerekli bilgileri hesabına ilettik.", "stepThree": "", "stepFour": "" }, "buttons": { - "openEmail": "", - "resetPassword": "" + "openEmail": "Eposta uygulamasını aç", + "resetPassword": "Parolayı sıfırla" }, "imageAlts": { "passwordKey": "", @@ -670,75 +677,76 @@ "reconnected": "", "stillUnreachable": "" }, - "alertBox": "", + "alertBox": "Sunucu bağlantı hatası", "description": "", "retryButton": { - "default": "", - "processing": "" + "default": "Bağlantıyı tekrarla", + "processing": "Bağlanıyor..." } } }, "createNotifications": { - "title": "", + "title": "Bildirim kanalları oluştur", "nameSettings": { - "title": "", - "description": "", - "nameLabel": "", - "namePlaceholder": "" + "title": "İsim", + "description": "Entegrasyon için açıklama", + "nameLabel": "İsim", + "namePlaceholder": "örn. Slack bildirimleri" }, "typeSettings": { - "title": "", + "title": "Tür", "description": "", - "typeLabel": "" + "typeLabel": "Tür" }, "emailSettings": { - "title": "", - "description": "", - "emailLabel": "", - "emailPlaceholder": "" + "title": "Eposta", + "description": "Hedef eposta adresi", + "emailLabel": "Eposta adresi", + "emailPlaceholder": "örn. john@example.com" }, "slackSettings": { - "title": "", - "description": "", - "webhookLabel": "", - "webhookPlaceholder": "" + "title": "Slack", + "description": "Slack webhook'u burada tanımlayın", + "webhookLabel": "Slack webhook adresi", + "webhookPlaceholder": "https://hooks.slack.com/services/...\n" }, "pagerdutySettings": { - "title": "", - "description": "", - "integrationKeyLabel": "", - "integrationKeyPlaceholder": "" - }, - "genericWebhookSettings": { - "title": "", - "description": "", - "webhookLabel": "", - "webhookPlaceholder": "" + "title": "PagerDuty", + "description": "PagerDuty entegrasyonunu burada yapın", + "integrationKeyLabel": "Entegrasyon anahtarı", + "integrationKeyPlaceholder": "1234567890" }, "discordSettings": { - "title": "", + "title": "Discord", + "description": "Discord webhook'u buradan ayarlayın", + "webhookLabel": "Discord Webhook adresi", + "webhookPlaceholder": "https://sunucu-adı.com/webhook\n" + }, + "webhookSettings": { + "title": "Webhook", "description": "", "webhookLabel": "", - "webhookPlaceholder": "" + "webhookPlaceholder": "https://sunucu-adı.com/webhook\n" } }, "notificationConfig": { - "title": "", + "title": "Bildirimler", "description": "" }, "monitorStatus": { "checkingEvery": "", "withCaptureAgent": "", - "up": "", - "down": "", - "paused": "" + "up": "ayakta", + "down": "çalışmıyor", + "paused": "beklemede" }, "advancedMatching": "", "settingsEmailTLSServername": "", "settingsEmailIgnoreTLS": "", "settingsEmailRequireTLS": "", "settingsEmailRejectUnauthorized": "", - "settingsEmailSecure": "", + "settingsEmailSecure": "Güvenli - SSL kullan", "settingsEmailPool": "", - "sendTestNotifications": "" + "sendTestNotifications": "Bildirimleri dene", + "selectAll": "Tümünü seç" } diff --git a/client/src/locales/zh-TW.json b/client/src/locales/zh-TW.json index 6e864b7ee..5f9409f6a 100644 --- a/client/src/locales/zh-TW.json +++ b/client/src/locales/zh-TW.json @@ -583,6 +583,13 @@ }, "errors": { "validation": "" + }, + "fields": { + "password": { + "errors": { + "incorrect": "" + } + } } }, "login": { @@ -709,13 +716,13 @@ "integrationKeyLabel": "", "integrationKeyPlaceholder": "" }, - "genericWebhookSettings": { + "discordSettings": { "title": "", "description": "", "webhookLabel": "", "webhookPlaceholder": "" }, - "discordSettings": { + "webhookSettings": { "title": "", "description": "", "webhookLabel": "", @@ -740,5 +747,6 @@ "settingsEmailRejectUnauthorized": "", "settingsEmailSecure": "", "settingsEmailPool": "", - "sendTestNotifications": "" + "sendTestNotifications": "", + "selectAll": "" } From 0b6febbb820c76505e4a188574f93434234a1d01 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 23 Jun 2025 11:25:42 +0800 Subject: [PATCH 46/74] add log cache, routes, and controller --- server/controllers/logController.js | 23 ++++++++++++ server/index.js | 7 ++++ server/routes/logRoutes.js | 18 ++++++++++ server/utils/logger.js | 55 +++++++++++++++++++++++------ 4 files changed, 93 insertions(+), 10 deletions(-) create mode 100644 server/controllers/logController.js create mode 100755 server/routes/logRoutes.js diff --git a/server/controllers/logController.js b/server/controllers/logController.js new file mode 100644 index 000000000..00cf7bbcb --- /dev/null +++ b/server/controllers/logController.js @@ -0,0 +1,23 @@ +import { handleError } from "./controllerUtils.js"; + +const SERVICE_NAME = "JobQueueController"; + +class LogController { + constructor(logger) { + this.logger = logger; + } + + getLogs = async (req, res, next) => { + try { + const logs = await this.logger.getLogs(); + res.success({ + msg: "Logs fetched successfully", + data: logs, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getMetrics")); + return; + } + }; +} +export default LogController; diff --git a/server/index.js b/server/index.js index 99be6de7a..4b74ef46c 100755 --- a/server/index.js +++ b/server/index.js @@ -36,6 +36,9 @@ import StatusPageController from "./controllers/statusPageController.js"; import QueueRoutes from "./routes/queueRoute.js"; import QueueController from "./controllers/queueController.js"; +import LogRoutes from "./routes/logRoutes.js"; +import LogController from "./controllers/logController.js"; + import NotificationRoutes from "./routes/notificationRoute.js"; import NotificationController from "./controllers/notificationController.js"; @@ -294,6 +297,8 @@ const startApp = async () => { ServiceRegistry.get(StringService.SERVICE_NAME) ); + const logController = new LogController(logger); + const statusPageController = new StatusPageController( ServiceRegistry.get(MongoDB.SERVICE_NAME), ServiceRegistry.get(StringService.SERVICE_NAME) @@ -320,6 +325,7 @@ const startApp = async () => { maintenanceWindowController ); const queueRoutes = new QueueRoutes(queueController); + const logRoutes = new LogRoutes(logController); const statusPageRoutes = new StatusPageRoutes(statusPageController); const notificationRoutes = new NotificationRoutes(notificationController); @@ -372,6 +378,7 @@ const startApp = async () => { app.use("/api/v1/checks", verifyJWT, checkRoutes.getRouter()); app.use("/api/v1/maintenance-window", verifyJWT, maintenanceWindowRoutes.getRouter()); app.use("/api/v1/queue", verifyJWT, queueRoutes.getRouter()); + app.use("/api/v1/logs", verifyJWT, logRoutes.getRouter()); app.use("/api/v1/status-page", statusPageRoutes.getRouter()); app.use("/api/v1/notifications", verifyJWT, notificationRoutes.getRouter()); app.use("/api/v1/diagnostic", verifyJWT, diagnosticRoutes.getRouter()); diff --git a/server/routes/logRoutes.js b/server/routes/logRoutes.js new file mode 100755 index 000000000..867826dde --- /dev/null +++ b/server/routes/logRoutes.js @@ -0,0 +1,18 @@ +import { Router } from "express"; +import { isAllowed } from "../middleware/isAllowed.js"; +class LogRoutes { + constructor(logController) { + this.router = Router(); + this.logController = logController; + this.initRoutes(); + } + initRoutes() { + this.router.get("/", isAllowed(["admin", "superadmin"]), this.logController.getLogs); + } + + getRouter() { + return this.router; + } +} + +export default LogRoutes; diff --git a/server/utils/logger.js b/server/utils/logger.js index 155adc957..f19694987 100755 --- a/server/utils/logger.js +++ b/server/utils/logger.js @@ -4,6 +4,8 @@ dotenv.config(); class Logger { constructor() { + this.logCache = []; + this.maxCacheSize = 500; const consoleFormat = format.printf( ({ level, message, service, method, details, timestamp, stack }) => { if (message instanceof Object) { @@ -72,11 +74,17 @@ class Logger { * @param {Object} config.details - Additional details. */ info(config) { - this.logger.info(config.message, { + const logEntry = { + level: "info", + message: config.message, service: config.service, method: config.method, details: config.details, - }); + timestamp: new Date().toISOString(), + }; + + this.cacheLog(logEntry); + this.logger.info(config.message, logEntry); } /** @@ -88,11 +96,17 @@ class Logger { * @param {Object} config.details - Additional details. */ warn(config) { - this.logger.warn(config.message, { + const logEntry = { + level: "warn", + message: config.message, service: config.service, method: config.method, details: config.details, - }); + timestamp: new Date().toISOString(), + }; + + this.cacheLog(logEntry); + this.logger.warn(config.message, logEntry); } /** @@ -104,12 +118,17 @@ class Logger { * @param {Object} config.details - Additional details. */ error(config) { - this.logger.error(config.message, { + const logEntry = { + level: "error", + message: config.message, service: config.service, method: config.method, details: config.details, - stack: config.stack, - }); + timestamp: new Date().toISOString(), + }; + + this.cacheLog(logEntry); + this.logger.error(config.message, logEntry); } /** * Logs a debug message. @@ -120,12 +139,28 @@ class Logger { * @param {Object} config.details - Additional details. */ debug(config) { - this.logger.debug(config.message, { + const logEntry = { + level: "debug", + message: config.message, service: config.service, method: config.method, details: config.details, - stack: config.stack, - }); + timestamp: new Date().toISOString(), + }; + + this.cacheLog(logEntry); + this.logger.debug(config.message, logEntry); + } + + cacheLog(entry) { + this.logCache.push(entry); + if (this.logCache.length > this.maxCacheSize) { + this.logCache.shift(); + } + } + + getLogs() { + return this.logCache; } } From 365ae081237cc4c7aca94124c3029fdf0db712c8 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 23 Jun 2025 12:09:30 +0800 Subject: [PATCH 47/74] FE log component implementation --- client/package-lock.json | 399 +++++++++++------- client/package.json | 2 +- client/src/Hooks/logHooks.js | 35 ++ client/src/Pages/Logs/Logs/index.jsx | 93 ++++ .../Logs/Queue/components/Metrics/index.jsx | 2 - client/src/Pages/Logs/index.jsx | 5 +- client/src/Utils/NetworkService.js | 8 + client/src/assets/icons/logs.svg | 2 +- client/src/locales/en.json | 15 +- 9 files changed, 401 insertions(+), 160 deletions(-) create mode 100644 client/src/Hooks/logHooks.js create mode 100644 client/src/Pages/Logs/Logs/index.jsx diff --git a/client/package-lock.json b/client/package-lock.json index 4050adfbb..f58f56f44 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -45,7 +45,7 @@ "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-react-refresh": "^0.4.6", "prettier": "^3.3.3", - "vite": "^5.4.19" + "vite": "6.3.5" }, "optionalDependencies": { "@rollup/rollup-linux-arm64-musl": "4.41.0" @@ -492,9 +492,9 @@ "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", "cpu": [ "ppc64" ], @@ -504,13 +504,13 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", "cpu": [ "arm" ], @@ -520,13 +520,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", "cpu": [ "arm64" ], @@ -536,13 +536,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", "cpu": [ "x64" ], @@ -552,13 +552,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", + "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", "cpu": [ "arm64" ], @@ -568,13 +568,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", + "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", "cpu": [ "x64" ], @@ -584,13 +584,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", + "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", "cpu": [ "arm64" ], @@ -600,13 +600,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", + "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", "cpu": [ "x64" ], @@ -616,13 +616,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", "cpu": [ "arm" ], @@ -632,13 +632,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", + "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", "cpu": [ "arm64" ], @@ -648,13 +648,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", + "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", "cpu": [ "ia32" ], @@ -664,13 +664,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", "cpu": [ "loong64" ], @@ -680,13 +680,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", + "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", "cpu": [ "mips64el" ], @@ -696,13 +696,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", + "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", "cpu": [ "ppc64" ], @@ -712,13 +712,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", + "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", "cpu": [ "riscv64" ], @@ -728,13 +728,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", "cpu": [ "s390x" ], @@ -744,13 +744,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", + "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", "cpu": [ "x64" ], @@ -760,13 +760,29 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", + "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", + "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", "cpu": [ "x64" ], @@ -776,13 +792,29 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", + "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", + "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", "cpu": [ "x64" ], @@ -792,13 +824,13 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", + "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", "cpu": [ "x64" ], @@ -808,13 +840,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", + "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", "cpu": [ "arm64" ], @@ -824,13 +856,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", "cpu": [ "ia32" ], @@ -840,13 +872,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", "cpu": [ "x64" ], @@ -856,7 +888,7 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { @@ -3040,9 +3072,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -3172,23 +3204,6 @@ ], "license": "CC-BY-4.0" }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -3259,6 +3274,15 @@ "node": ">=10" } }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3820,41 +3844,43 @@ } }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", + "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", "hasInstallScript": true, "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@esbuild/aix-ppc64": "0.25.5", + "@esbuild/android-arm": "0.25.5", + "@esbuild/android-arm64": "0.25.5", + "@esbuild/android-x64": "0.25.5", + "@esbuild/darwin-arm64": "0.25.5", + "@esbuild/darwin-x64": "0.25.5", + "@esbuild/freebsd-arm64": "0.25.5", + "@esbuild/freebsd-x64": "0.25.5", + "@esbuild/linux-arm": "0.25.5", + "@esbuild/linux-arm64": "0.25.5", + "@esbuild/linux-ia32": "0.25.5", + "@esbuild/linux-loong64": "0.25.5", + "@esbuild/linux-mips64el": "0.25.5", + "@esbuild/linux-ppc64": "0.25.5", + "@esbuild/linux-riscv64": "0.25.5", + "@esbuild/linux-s390x": "0.25.5", + "@esbuild/linux-x64": "0.25.5", + "@esbuild/netbsd-arm64": "0.25.5", + "@esbuild/netbsd-x64": "0.25.5", + "@esbuild/openbsd-arm64": "0.25.5", + "@esbuild/openbsd-x64": "0.25.5", + "@esbuild/sunos-x64": "0.25.5", + "@esbuild/win32-arm64": "0.25.5", + "@esbuild/win32-ia32": "0.25.5", + "@esbuild/win32-x64": "0.25.5" } }, "node_modules/escalade": { @@ -4052,6 +4078,23 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/eslint/node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -4184,6 +4227,20 @@ "reusify": "^1.0.4" } }, + "node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -6713,6 +6770,22 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "license": "MIT" }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -6923,20 +6996,23 @@ } }, "node_modules/vite": { - "version": "5.4.19", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", - "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -6945,19 +7021,25 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", - "terser": "^5.4.0" + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -6978,6 +7060,12 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, @@ -7133,12 +7221,17 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, "engines": { - "node": ">= 6" + "node": ">= 14.6" } }, "node_modules/yocto-queue": { diff --git a/client/package.json b/client/package.json index 83dfe8407..611ba5093 100644 --- a/client/package.json +++ b/client/package.json @@ -58,7 +58,7 @@ "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-react-refresh": "^0.4.6", "prettier": "^3.3.3", - "vite": "^5.4.19" + "vite": "6.3.5" }, "optionalDependencies": { "@rollup/rollup-linux-arm64-musl": "4.41.0" diff --git a/client/src/Hooks/logHooks.js b/client/src/Hooks/logHooks.js new file mode 100644 index 000000000..000e4d0f0 --- /dev/null +++ b/client/src/Hooks/logHooks.js @@ -0,0 +1,35 @@ +import { useState, useEffect } from "react"; +import { networkService } from "../main"; +import { createToast } from "../Utils/toastUtils"; +import { useTranslation } from "react-i18next"; + +const useFetchLogs = () => { + const { t } = useTranslation(); + const [logs, setLogs] = useState(undefined); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(undefined); + + useEffect(() => { + const fetchLogs = async () => { + try { + setIsLoading(true); + const response = await networkService.getLogs(); + setLogs(response.data.data); + createToast({ + body: t("logsPage.toast.fetchLogsSuccess"), + }); + } catch (error) { + setError(error); + createToast({ + message: error.message, + }); + } finally { + setIsLoading(false); + } + }; + fetchLogs(); + }, [t]); + return [logs, isLoading, error]; +}; + +export { useFetchLogs }; diff --git a/client/src/Pages/Logs/Logs/index.jsx b/client/src/Pages/Logs/Logs/index.jsx new file mode 100644 index 000000000..d5b0a8469 --- /dev/null +++ b/client/src/Pages/Logs/Logs/index.jsx @@ -0,0 +1,93 @@ +import Stack from "@mui/material/Stack"; +import Box from "@mui/material/Box"; +import Select from "../../../Components/Inputs/Select"; +import Typography from "@mui/material/Typography"; + +import { useFetchLogs } from "../../../Hooks/logHooks"; +import { useTheme } from "@emotion/react"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; + +const formatLog = (theme, log, idx) => { + 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[log.level] || theme.palette.primary.contrastText; + + return ( + + [{log.timestamp}]{" "} + {log.level.toUpperCase()} + {": "} + {`(${log.service})`} + {`(${log.method})`} + {": "} + {log.message} +
+
+ ); +}; + +const Logs = () => { + // Local state + const [logLevel, setLogLevel] = useState("all"); + + // Hooks + const theme = useTheme(); + const { t } = useTranslation(); + const [logs, isLoading, error] = useFetchLogs(); + // Setup + 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") }, + ]; + return ( + + + {t("logsPage.logLevelSelect.title")} +