From 76b21f1d8c722e7a2b71a78ac8f6de396e6b3cf3 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 5 Feb 2025 10:55:08 -0800 Subject: [PATCH 1/8] add route, controller and DB methods for updating status page --- Server/controllers/statusPageController.js | 20 ++++++++++++ Server/db/mongo/modules/statusPageModule.js | 35 +++++++++++++++++++-- Server/routes/statusPageRoute.js | 6 ++++ Server/utils/messages.js | 1 + 4 files changed, 60 insertions(+), 2 deletions(-) diff --git a/Server/controllers/statusPageController.js b/Server/controllers/statusPageController.js index 5f927f118..bbcbec10a 100644 --- a/Server/controllers/statusPageController.js +++ b/Server/controllers/statusPageController.js @@ -33,6 +33,26 @@ class StatusPageController { } }; + updateStatusPage = async (req, res, next) => { + try { + await createStatusPageBodyValidation.validateAsync(req.body); + await imageValidation.validateAsync(req.file); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } + + try { + const statusPage = await this.db.updateStatusPage(req.body, req.file); + return res.success({ + msg: successMessages.STATUS_PAGE_UPDATE, + data: statusPage, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "updateStatusPage")); + } + }; + getStatusPage = async (req, res, next) => { try { const statusPage = await this.db.getStatusPage(); diff --git a/Server/db/mongo/modules/statusPageModule.js b/Server/db/mongo/modules/statusPageModule.js index 61a1877d7..2f31d2adb 100644 --- a/Server/db/mongo/modules/statusPageModule.js +++ b/Server/db/mongo/modules/statusPageModule.js @@ -27,6 +27,26 @@ const createStatusPage = async (statusPageData, image) => { } }; +const updateStatusPage = async (statusPageData, image) => { + try { + if (image) { + statusPageData.logo = { + data: image.buffer, + contentType: image.mimetype, + }; + } + const statusPage = await StatusPage.findOneAndUpdate({}, statusPageData, { + new: true, + }); + + return statusPage; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "updateStatusPage"; + throw error; + } +}; + const getStatusPage = async () => { try { const statusPageQuery = await StatusPage.aggregate([ @@ -79,7 +99,18 @@ const getStatusPage = async () => { }, { $project: { - statusPage: 1, + statusPage: { + _id: 1, + color: 1, + companyName: 1, + isPublished: 1, + logo: 1, + originalMonitors: 1, + showCharts: 1, + showUptimePercentage: 1, + timezone: 1, + url: 1, + }, monitors: { $sortArray: { input: "$monitors", @@ -122,4 +153,4 @@ const deleteStatusPage = async () => { } }; -export { createStatusPage, getStatusPage, deleteStatusPage }; +export { createStatusPage, updateStatusPage, getStatusPage, deleteStatusPage }; diff --git a/Server/routes/statusPageRoute.js b/Server/routes/statusPageRoute.js index 2217bc9f6..37ae313ee 100644 --- a/Server/routes/statusPageRoute.js +++ b/Server/routes/statusPageRoute.js @@ -18,6 +18,12 @@ class StatusPageRoutes { verifyJWT, this.statusPageController.createStatusPage ); + this.router.put( + "/", + upload.single("logo"), + verifyJWT, + this.statusPageController.updateStatusPage + ); this.router.delete("/", verifyJWT, this.statusPageController.deleteStatusPage); } diff --git a/Server/utils/messages.js b/Server/utils/messages.js index 932585bc7..ffcfc3f1f 100644 --- a/Server/utils/messages.js +++ b/Server/utils/messages.js @@ -137,6 +137,7 @@ const successMessages = { STATUS_PAGE_BY_URL: "Got status page by url successfully", STATUS_PAGE_CREATE: "Status page created successfully", STATUS_PAGE_DELETE: "Status page deleted successfully", + STATUS_PAGE_UPDATE: "Status page updated successfully", // Docker DOCKER_SUCCESS: "Docker container status fetched successfully", From d29bdd1545baac685b7453f6c9b84fb8c81d4737 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 5 Feb 2025 10:55:58 -0800 Subject: [PATCH 2/8] modify network request to allow updating --- Client/src/Utils/NetworkService.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js index 56b8b85d1..85e82554e 100644 --- a/Client/src/Utils/NetworkService.js +++ b/Client/src/Utils/NetworkService.js @@ -873,7 +873,7 @@ class NetworkService { } async createStatusPage(config) { - const { authToken, user, form } = config; + const { authToken, user, form, isCreate } = config; const fd = new FormData(); fd.append("isPublished", form.isPublished); fd.append("companyName", form.companyName); @@ -895,8 +895,15 @@ class NetworkService { URL.revokeObjectURL(form.logo.src); } } + if (isCreate) { + return this.axiosInstance.post(`/status-page`, fd, { + headers: { + Authorization: `Bearer ${authToken}`, + }, + }); + } - return this.axiosInstance.post(`/status-page`, fd, { + return this.axiosInstance.put(`/status-page`, fd, { headers: { Authorization: `Bearer ${authToken}`, }, From 7d31b4556b9ca83a05b4b8e2d594ea7c3a81253c Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 5 Feb 2025 10:56:21 -0800 Subject: [PATCH 3/8] add config route --- Client/src/Routes/index.jsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Client/src/Routes/index.jsx b/Client/src/Routes/index.jsx index 570691726..12fad43ae 100644 --- a/Client/src/Routes/index.jsx +++ b/Client/src/Routes/index.jsx @@ -120,6 +120,10 @@ const Routes = () => { path="status/create" element={} /> + } + /> Date: Wed, 5 Feb 2025 10:56:50 -0800 Subject: [PATCH 4/8] explicitly match IDs --- Client/src/Components/Inputs/Search/index.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Client/src/Components/Inputs/Search/index.jsx b/Client/src/Components/Inputs/Search/index.jsx index 56b329500..3459c6eac 100644 --- a/Client/src/Components/Inputs/Search/index.jsx +++ b/Client/src/Components/Inputs/Search/index.jsx @@ -60,6 +60,7 @@ const Search = ({ onBlur, }) => { const theme = useTheme(); + console.log({ options }); return ( option[filteredBy]} + isOptionEqualToValue={(option, value) => option._id === value._id} // Compare by unique identifier renderInput={(params) => ( Date: Wed, 5 Feb 2025 10:57:15 -0800 Subject: [PATCH 5/8] add conditon to hook for create/update --- .../src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx b/Client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx index 24d841cc9..92e2acf96 100644 --- a/Client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx +++ b/Client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx @@ -3,7 +3,7 @@ import { networkService } from "../../../../main"; import { useSelector } from "react-redux"; import { createToast } from "../../../../Utils/toastUtils"; -const useCreateStatusPage = () => { +const useCreateStatusPage = (isCreate) => { const { authToken, user } = useSelector((state) => state.auth); const [isLoading, setIsLoading] = useState(false); @@ -11,7 +11,7 @@ const useCreateStatusPage = () => { const createStatusPage = async ({ form }) => { setIsLoading(true); try { - await networkService.createStatusPage({ authToken, user, form }); + await networkService.createStatusPage({ authToken, user, form, isCreate }); return true; } catch (error) { setNetworkError(true); From e9c7f1d7be9ae32fce6b94a567fde9c7cadba360 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 5 Feb 2025 10:57:28 -0800 Subject: [PATCH 6/8] implement config --- Client/src/Pages/StatusPage/Create/index.jsx | 47 +++++++++++++++++-- .../Components/ControlsHeader/index.jsx | 2 +- .../Status/Hooks/useStatusPageFetch.jsx | 8 +++- Client/src/Pages/StatusPage/Status/index.jsx | 4 +- 4 files changed, 52 insertions(+), 9 deletions(-) diff --git a/Client/src/Pages/StatusPage/Create/index.jsx b/Client/src/Pages/StatusPage/Create/index.jsx index f3b30069b..58269e3b8 100644 --- a/Client/src/Pages/StatusPage/Create/index.jsx +++ b/Client/src/Pages/StatusPage/Create/index.jsx @@ -5,13 +5,16 @@ import GenericFallback from "../../../Components/GenericFallback"; import SkeletonLayout from "./Components/Skeleton"; //Utils import { useTheme } from "@emotion/react"; -import { useState, useRef, useCallback } from "react"; +import { useState, useEffect, useRef, useCallback } from "react"; import { statusPageValidation } from "../../../Validation/validation"; import { buildErrors } from "../../../Validation/error"; import { useMonitorsFetch } from "./Hooks/useMonitorsFetch"; import { useCreateStatusPage } from "./Hooks/useCreateStatusPage"; import { createToast } from "../../../Utils/toastUtils"; import { useNavigate } from "react-router-dom"; +import { useLocation } from "react-router-dom"; +import { useStatusPageFetch } from "../Status/Hooks/useStatusPageFetch"; + //Constants const TAB_LIST = ["General settings", "Contents"]; @@ -40,12 +43,18 @@ const CreateStatusPage = () => { // Refs const intervalRef = useRef(null); + // Setup + const location = useLocation(); + const isCreate = location.pathname === "/status/create"; + //Utils const theme = useTheme(); const [monitors, isLoading, networkError] = useMonitorsFetch(); - const [createStatusPage, createSatusIsLoading, createStatusPageNetworkError] = - useCreateStatusPage(); + const [createStatusPage, createStatusIsLoading, createStatusPageNetworkError] = + useCreateStatusPage(isCreate); const navigate = useNavigate(); + const [statusPage, statusPageMonitors, statusPageIsLoading, statusPageNetworkError] = + useStatusPageFetch(isCreate); // Handlers const handleFormChange = (e) => { @@ -145,6 +154,37 @@ const CreateStatusPage = () => { setTab(errorTabs[0]); }; + // If we are configuring, populate fields + useEffect(() => { + if (isCreate) return; + if (typeof statusPage === "undefined") { + return; + } + + let newLogo = undefined; + if (statusPage.logo) { + newLogo = { + src: `data:${statusPage.logo.contentType};base64,${statusPage.logo.data}`, + name: "logo", + type: statusPage.logo.contentType, + size: null, + }; + } + + setForm((prev) => { + return { + ...prev, + companyName: statusPage?.companyName, + isPublished: statusPage?.isPublished, + timezone: statusPage?.timezone, + monitors: statusPageMonitors.map((monitor) => monitor._id), + color: statusPage?.color, + logo: newLogo, + }; + }); + setSelectedMonitors(statusPageMonitors); + }, [isCreate, statusPage, statusPageMonitors]); + if (networkError === true) { return ( @@ -162,6 +202,7 @@ const CreateStatusPage = () => { if (isLoading) return ; + // Load fields return ( {