From deca3b93f0acfb705a8a46750b840a43f1717e8b Mon Sep 17 00:00:00 2001 From: "Gorkem Cetin (BWL)" <167266851+gorkem-bwl@users.noreply.github.com> Date: Mon, 10 Feb 2025 01:28:25 -0500 Subject: [PATCH 01/21] Update documentation URL --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3dd59ef8f..ebb45a149 100644 --- a/README.md +++ b/README.md @@ -32,11 +32,11 @@ See [Checkmate](https://checkmate-demo.bluewavelabs.ca/) in action. The username ## 🔗 User's guide -Usage instructions can be found [here](https://bluewavelabs.gitbook.io/checkmate). It's still WIP and some of the information there might be outdated. Rest assured, we are doing our best! :) +Usage instructions can be found [here](https://docs.checkmate.so/). It's still WIP and some of the information there might be outdated. Rest assured, we are doing our best! :) ## 🛠️ Installation -See installation instructions in [Checkmate documentation portal](https://bluewavelabs.gitbook.io/checkmate/quickstart). Alternatively, you can also use [Coolify](https://coolify.io/) for a one click Docker deployment. If you would like to monitor your server infrastructure, you'll need [Capture agent](https://github.com/bluewave-labs/capture). Capture repository also contains the installation instructions. +See installation instructions in [Checkmate documentation portal](https://docs.checkmate.so/quickstart). Alternatively, you can also use [Coolify](https://coolify.io/) for a one click Docker deployment. If you would like to monitor your server infrastructure, you'll need [Capture agent](https://github.com/bluewave-labs/capture). Capture repository also contains the installation instructions. ## 💚 Questions & ideas @@ -103,7 +103,7 @@ Here's how you can contribute: 0. Star this repo :) 1. Check [Contributor's guideline](https://github.com/bluewave-labs/bluewave-uptime/blob/master/CONTRIBUTING.md). First timers are encouraged to check `good-first-issue` tag. -2. Optionally, read [project structure](https://bluewavelabs.gitbook.io/checkmate/developers-guide/general-project-structure) and [high level overview](https://bluewavelabs.gitbook.io/checkmate/developers-guide/high-level-overview). +2. Optionally, read [project structure](https://docs.checkmate.so/developers-guide/general-project-structure) and [high level overview](https://bluewavelabs.gitbook.io/checkmate/developers-guide/high-level-overview). 3. Have a look at our Figma designs [here](https://www.figma.com/design/RPSfaw66HjzSwzntKcgDUV/Uptime-Genie?node-id=0-1&t=WqOFv9jqNTFGItpL-1) if you are going to use one of our designs. We encourage you to copy to your own Figma page, then work on it as it is read-only. 4. Open an issue if you believe you've encountered a bug. 5. Check for good-first-issue's if you are a newcomer. From 99079cc2bbc714f9bd72c5bc22f1171f1a462d02 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 10 Feb 2025 10:20:21 -0800 Subject: [PATCH 02/21] add userId and teamId to status pages --- Server/db/models/StatusPage.js | 12 ++++++++++++ Server/validation/joi.js | 2 ++ 2 files changed, 14 insertions(+) diff --git a/Server/db/models/StatusPage.js b/Server/db/models/StatusPage.js index 2f9898300..0ca8a1266 100644 --- a/Server/db/models/StatusPage.js +++ b/Server/db/models/StatusPage.js @@ -2,6 +2,18 @@ import mongoose from "mongoose"; const StatusPageSchema = mongoose.Schema( { + userId: { + type: mongoose.Schema.Types.ObjectId, + ref: "User", + immutable: true, + required: true, + }, + teamId: { + type: mongoose.Schema.Types.ObjectId, + ref: "Team", + immutable: true, + required: true, + }, companyName: { type: String, required: true, diff --git a/Server/validation/joi.js b/Server/validation/joi.js index 349ac9fcb..fe0220853 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -419,6 +419,8 @@ const getStatusPageParamValidation = joi.object({ }); const createStatusPageBodyValidation = joi.object({ + userId: joi.string().required(), + teamId: joi.string().required(), companyName: joi.string().required(), url: joi.string().required(), timezone: joi.string().optional(), From f78343d03f76576fe752d65f0fc2b4a6cf05b142 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 10 Feb 2025 10:21:56 -0800 Subject: [PATCH 03/21] add teamId and userId to network request --- Client/src/Utils/NetworkService.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js index 4f20f77f0..4b0ad55cc 100644 --- a/Client/src/Utils/NetworkService.js +++ b/Client/src/Utils/NetworkService.js @@ -1,5 +1,5 @@ import axios from "axios"; -import i18next from 'i18next'; +import i18next from "i18next"; const BASE_URL = import.meta.env.VITE_APP_API_BASE_URL; const FALLBACK_BASE_URL = "http://localhost:5000/api/v1"; import { clearAuthState } from "../Features/Auth/authSlice"; @@ -26,7 +26,7 @@ class NetworkService { }); this.axiosInstance.interceptors.request.use( (config) => { - const currentLanguage = i18next.language || 'en'; + const currentLanguage = i18next.language || "en"; config.headers = { ...config.headers, @@ -1003,6 +1003,8 @@ class NetworkService { async createStatusPage(config) { const { authToken, user, form, isCreate } = config; const fd = new FormData(); + fd.append("teamId", user.teamId); + fd.append("userId", user._id); form.isPublished && fd.append("isPublished", form.isPublished); form.companyName && fd.append("companyName", form.companyName); form.url && fd.append("url", form.url); From 48cfeb1558c22080c34871a7570e9d4192482978 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 10 Feb 2025 13:41:21 -0800 Subject: [PATCH 04/21] add route, controller, and db method for getting all status pages by ID --- Server/controllers/statusPageController.js | 17 ++++++++- Server/db/models/StatusPage.js | 6 +++ Server/db/mongo/modules/statusPageModule.js | 41 +++++++++++++++++---- Server/routes/statusPageRoute.js | 2 + Server/utils/messages.js | 1 + Server/validation/joi.js | 6 +++ 6 files changed, 64 insertions(+), 9 deletions(-) diff --git a/Server/controllers/statusPageController.js b/Server/controllers/statusPageController.js index de2e786a6..69609825c 100644 --- a/Server/controllers/statusPageController.js +++ b/Server/controllers/statusPageController.js @@ -2,6 +2,7 @@ import { handleError, handleValidationError } from "./controllerUtils.js"; import { createStatusPageBodyValidation, getStatusPageParamValidation, + getStatusPageQueryValidation, imageValidation, } from "../validation/joi.js"; import { successMessages, errorMessages } from "../utils/messages.js"; @@ -73,13 +74,14 @@ class StatusPageController { getStatusPageByUrl = async (req, res, next) => { try { await getStatusPageParamValidation.validateAsync(req.params); + await getStatusPageQueryValidation.validateAsync(req.query); } catch (error) { next(handleValidationError(error, SERVICE_NAME)); return; } try { - const statusPage = await this.db.getStatusPageByUrl(req.params.url); + const statusPage = await this.db.getStatusPageByUrl(req.params.url, req.query.type); return res.success({ msg: successMessages.STATUS_PAGE_BY_URL, data: statusPage, @@ -89,6 +91,19 @@ class StatusPageController { } }; + getStatusPagesByTeamId = async (req, res, next) => { + try { + const teamId = req.params.teamId; + const statusPages = await this.db.getStatusPagesByTeamId(teamId); + return res.success({ + msg: successMessages.STATUS_PAGE_BY_TEAM_ID, + data: statusPages, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getStatusPageByUrl")); + } + }; + deleteStatusPage = async (req, res, next) => { try { await this.db.deleteStatusPage(req.params.url); diff --git a/Server/db/models/StatusPage.js b/Server/db/models/StatusPage.js index 0ca8a1266..bb1392d05 100644 --- a/Server/db/models/StatusPage.js +++ b/Server/db/models/StatusPage.js @@ -14,6 +14,12 @@ const StatusPageSchema = mongoose.Schema( immutable: true, required: true, }, + type: { + type: String, + required: true, + default: "uptime", + enum: ["uptime", "distributed"], + }, companyName: { type: String, required: true, diff --git a/Server/db/mongo/modules/statusPageModule.js b/Server/db/mongo/modules/statusPageModule.js index a08e6a0b4..8687f06ed 100644 --- a/Server/db/mongo/modules/statusPageModule.js +++ b/Server/db/mongo/modules/statusPageModule.js @@ -35,9 +35,13 @@ const updateStatusPage = async (statusPageData, image) => { contentType: image.mimetype, }; } - const statusPage = await StatusPage.findOneAndUpdate({}, statusPageData, { - new: true, - }); + const statusPage = await StatusPage.findOneAndUpdate( + { url: statusPageData.url }, + statusPageData, + { + new: true, + } + ); return statusPage; } catch (error) { @@ -47,10 +51,14 @@ const updateStatusPage = async (statusPageData, image) => { } }; -const getStatusPageByUrl = async (url) => { +const getStatusPageByUrl = async (url, type) => { try { - const statusPage = await StatusPage.aggregate([{ $match: { url } }]); - return statusPage[0]; + if (type === "distributed") { + const statusPage = await StatusPage.aggregate([{ $match: { url } }]); + return statusPage[0]; + } else { + return getStatusPage(url); + } } catch (error) { error.service = SERVICE_NAME; error.method = "getStatusPageByUrl"; @@ -58,10 +66,26 @@ const getStatusPageByUrl = async (url) => { } }; -const getStatusPage = async () => { +const getStatusPagesByTeamId = async (teamId) => { + try { + const statusPages = await StatusPage.find({ teamId }); + if (statusPages.length === 0) { + const error = new Error(errorMessages.STATUS_PAGE_NOT_FOUND); + error.status = 404; + throw error; + } + return statusPages; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getStatusPagesByTeamId"; + throw error; + } +}; + +const getStatusPage = async (url) => { try { const statusPageQuery = await StatusPage.aggregate([ - { $match: { url: "/status/public" } }, + { $match: { url: url } }, { $set: { originalMonitors: "$monitors", @@ -167,6 +191,7 @@ const deleteStatusPage = async (url) => { export { createStatusPage, updateStatusPage, + getStatusPagesByTeamId, getStatusPage, getStatusPageByUrl, deleteStatusPage, diff --git a/Server/routes/statusPageRoute.js b/Server/routes/statusPageRoute.js index 020935834..71d4bc920 100644 --- a/Server/routes/statusPageRoute.js +++ b/Server/routes/statusPageRoute.js @@ -13,6 +13,8 @@ class StatusPageRoutes { initRoutes() { this.router.get("/", this.statusPageController.getStatusPage); this.router.get("/:url", this.statusPageController.getStatusPageByUrl); + this.router.get("/team/:teamId", this.statusPageController.getStatusPagesByTeamId); + this.router.post( "/", upload.single("logo"), diff --git a/Server/utils/messages.js b/Server/utils/messages.js index c8ff88f5a..38b1bf9d1 100644 --- a/Server/utils/messages.js +++ b/Server/utils/messages.js @@ -139,6 +139,7 @@ const successMessages = { STATUS_PAGE_CREATE: "Status page created successfully", STATUS_PAGE_DELETE: "Status page deleted successfully", STATUS_PAGE_UPDATE: "Status page updated successfully", + STATUS_PAGE_BY_TEAM_ID: "Got status pages by team id successfully", // Docker DOCKER_SUCCESS: "Docker container status fetched successfully", diff --git a/Server/validation/joi.js b/Server/validation/joi.js index fe0220853..42c41d8a5 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -418,9 +418,14 @@ const getStatusPageParamValidation = joi.object({ url: joi.string().required(), }); +const getStatusPageQueryValidation = joi.object({ + type: joi.string().valid("uptime", "distributed").required(), +}); + const createStatusPageBodyValidation = joi.object({ userId: joi.string().required(), teamId: joi.string().required(), + type: joi.string().valid("uptime", "distributed").required(), companyName: joi.string().required(), url: joi.string().required(), timezone: joi.string().optional(), @@ -521,5 +526,6 @@ export { updateAppSettingsBodyValidation, createStatusPageBodyValidation, getStatusPageParamValidation, + getStatusPageQueryValidation, imageValidation, }; From 8d93a7e63bee9dc3c91ad5422e7c9d675b86a731 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 10 Feb 2025 13:41:47 -0800 Subject: [PATCH 05/21] remove unused classname --- Client/src/Components/Fallback/index.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/Client/src/Components/Fallback/index.jsx b/Client/src/Components/Fallback/index.jsx index 36c0d3eb1..3e30cd374 100644 --- a/Client/src/Components/Fallback/index.jsx +++ b/Client/src/Components/Fallback/index.jsx @@ -27,7 +27,6 @@ const Fallback = ({ title, checks, link = "/", isAdmin, vowelStart = false }) => return ( Date: Mon, 10 Feb 2025 13:43:09 -0800 Subject: [PATCH 06/21] Add type to status page request, add get status pages by team request --- Client/src/Utils/NetworkService.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js index 4b0ad55cc..ebb726a2c 100644 --- a/Client/src/Utils/NetworkService.js +++ b/Client/src/Utils/NetworkService.js @@ -991,8 +991,21 @@ class NetworkService { } async getStatusPageByUrl(config) { - const { authToken, url } = config; - return this.axiosInstance.get(`/status-page/${url}`, { + const { authToken, url, type } = config; + const params = new URLSearchParams(); + params.append("type", type); + + return this.axiosInstance.get(`/status-page/${url}?${params.toString()}`, { + headers: { + Authorization: `Bearer ${authToken}`, + "Content-Type": "application/json", + }, + }); + } + + async getStatusPagesByTeamId(config) { + const { authToken, teamId } = config; + return this.axiosInstance.get(`/status-page/team/${teamId}`, { headers: { Authorization: `Bearer ${authToken}`, "Content-Type": "application/json", @@ -1005,6 +1018,7 @@ class NetworkService { const fd = new FormData(); fd.append("teamId", user.teamId); fd.append("userId", user._id); + fd.append("type", form.type); form.isPublished && fd.append("isPublished", form.isPublished); form.companyName && fd.append("companyName", form.companyName); form.url && fd.append("url", form.url); From e0e73ce520f1b4d6c866f9354340894103a9cedf Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 10 Feb 2025 13:43:24 -0800 Subject: [PATCH 07/21] Add type to validation --- Client/src/Validation/validation.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Client/src/Validation/validation.js b/Client/src/Validation/validation.js index 6a2a56b94..0335b520d 100644 --- a/Client/src/Validation/validation.js +++ b/Client/src/Validation/validation.js @@ -206,6 +206,7 @@ const logoImageValidation = joi .optional(); // Make entire object optional const statusPageValidation = joi.object({ + type: joi.string().valid("uptime", "distributed").required(), isPublished: joi.bool(), companyName: joi .string() From d0bb648492dfc16fa2975c02cdc64cfc30194dc4 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 10 Feb 2025 13:44:43 -0800 Subject: [PATCH 08/21] move DU status --- .../Components/VisuallyHiddenInput/index.jsx | 0 .../Create}/index.jsx | 52 ++++++++++++++++--- .../Status/Components/Skeleton/index.jsx | 0 .../Status/Hooks/useStatusPageFetchByUrl.jsx | 6 ++- .../Status/index.jsx | 20 +++---- 5 files changed, 61 insertions(+), 17 deletions(-) rename Client/src/Pages/{DistributedUptime/CreateStatus => DistributedUptimeStatus/Create}/Components/VisuallyHiddenInput/index.jsx (100%) rename Client/src/Pages/{DistributedUptime/CreateStatus => DistributedUptimeStatus/Create}/index.jsx (79%) rename Client/src/Pages/{DistributedUptime => DistributedUptimeStatus}/Status/Components/Skeleton/index.jsx (100%) rename Client/src/Pages/{DistributedUptime => DistributedUptimeStatus}/Status/Hooks/useStatusPageFetchByUrl.jsx (90%) rename Client/src/Pages/{DistributedUptime => DistributedUptimeStatus}/Status/index.jsx (85%) diff --git a/Client/src/Pages/DistributedUptime/CreateStatus/Components/VisuallyHiddenInput/index.jsx b/Client/src/Pages/DistributedUptimeStatus/Create/Components/VisuallyHiddenInput/index.jsx similarity index 100% rename from Client/src/Pages/DistributedUptime/CreateStatus/Components/VisuallyHiddenInput/index.jsx rename to Client/src/Pages/DistributedUptimeStatus/Create/Components/VisuallyHiddenInput/index.jsx diff --git a/Client/src/Pages/DistributedUptime/CreateStatus/index.jsx b/Client/src/Pages/DistributedUptimeStatus/Create/index.jsx similarity index 79% rename from Client/src/Pages/DistributedUptime/CreateStatus/index.jsx rename to Client/src/Pages/DistributedUptimeStatus/Create/index.jsx index d244bfe38..91570c151 100644 --- a/Client/src/Pages/DistributedUptime/CreateStatus/index.jsx +++ b/Client/src/Pages/DistributedUptimeStatus/Create/index.jsx @@ -9,8 +9,9 @@ import LogoPlaceholder from "../../../assets/Images/logo_placeholder.svg"; import Breadcrumbs from "../../../Components/Breadcrumbs"; // Utils import { useTheme } from "@emotion/react"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { useParams } from "react-router-dom"; +import { useStatusPageFetch } from "../../StatusPage/Status/Hooks/useStatusPageFetch"; import { useCreateStatusPage } from "../../StatusPage/Create/Hooks/useCreateStatusPage"; import { useLocation } from "react-router-dom"; import { statusPageValidation } from "../../../Validation/validation"; @@ -20,20 +21,25 @@ import { useNavigate } from "react-router-dom"; const CreateStatus = () => { const theme = useTheme(); - const { monitorId } = useParams(); + const { monitorId, url } = useParams(); const navigate = useNavigate(); const location = useLocation(); - const isCreate = location.pathname.startsWith("/distributed-uptime/status/create"); + const isCreate = location.pathname.startsWith("/status/distributed/create"); const [createStatusPage, isLoading, networkError] = useCreateStatusPage(isCreate); + + const [statusPage, statusPageMonitors, statusPageIsLoading, statusPageNetworkError] = + useStatusPageFetch(isCreate, url); + const BREADCRUMBS = [ { name: "distributed uptime", path: "/distributed-uptime" }, { name: "details", path: `/distributed-uptime/${monitorId}` }, - { name: "create status page", path: `` }, + { name: isCreate ? "create status page" : "edit status page", path: `` }, ]; // Local state const [form, setForm] = useState({ + type: "distributed", isPublished: false, - url: Math.floor(Math.random() * 1000000).toFixed(0), + url: url ?? Math.floor(Math.random() * 1000000).toFixed(0), logo: undefined, companyName: "", monitors: [monitorId], @@ -87,8 +93,9 @@ const CreateStatus = () => { if (typeof error === "undefined") { const success = await createStatusPage({ form: formToSubmit }); if (success) { - createToast({ body: "Status page created successfully" }); - navigate(`/distributed-uptime/status/${form.url}`); + const verb = isCreate ? "created" : "updated"; + createToast({ body: `Status page ${verb} successfully` }); + navigate(`/status/distributed/${form.url}`); } return; } @@ -99,6 +106,36 @@ const CreateStatus = () => { setErrors((prev) => ({ ...prev, ...newErrors })); }; + // 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, + }; + }); + }, [isCreate, statusPage, statusPageMonitors]); + return ( @@ -159,6 +196,7 @@ const CreateStatus = () => { name="url" type="url" label="Your status page address" + disabled={!isCreate} value={form.url} onChange={handleFormChange} helperText={errors["url"]} diff --git a/Client/src/Pages/DistributedUptime/Status/Components/Skeleton/index.jsx b/Client/src/Pages/DistributedUptimeStatus/Status/Components/Skeleton/index.jsx similarity index 100% rename from Client/src/Pages/DistributedUptime/Status/Components/Skeleton/index.jsx rename to Client/src/Pages/DistributedUptimeStatus/Status/Components/Skeleton/index.jsx diff --git a/Client/src/Pages/DistributedUptime/Status/Hooks/useStatusPageFetchByUrl.jsx b/Client/src/Pages/DistributedUptimeStatus/Status/Hooks/useStatusPageFetchByUrl.jsx similarity index 90% rename from Client/src/Pages/DistributedUptime/Status/Hooks/useStatusPageFetchByUrl.jsx rename to Client/src/Pages/DistributedUptimeStatus/Status/Hooks/useStatusPageFetchByUrl.jsx index 583fed69d..0ca6346a0 100644 --- a/Client/src/Pages/DistributedUptime/Status/Hooks/useStatusPageFetchByUrl.jsx +++ b/Client/src/Pages/DistributedUptimeStatus/Status/Hooks/useStatusPageFetchByUrl.jsx @@ -13,7 +13,11 @@ const useStatusPageFetchByUrl = ({ url }) => { useEffect(() => { const fetchStatusPageByUrl = async () => { try { - const response = await networkService.getStatusPageByUrl({ authToken, url }); + const response = await networkService.getStatusPageByUrl({ + authToken, + url, + type: "distributed", + }); if (!response?.data?.data) return; const statusPage = response.data.data; setStatusPage(statusPage); diff --git a/Client/src/Pages/DistributedUptime/Status/index.jsx b/Client/src/Pages/DistributedUptimeStatus/Status/index.jsx similarity index 85% rename from Client/src/Pages/DistributedUptime/Status/index.jsx rename to Client/src/Pages/DistributedUptimeStatus/Status/index.jsx index d91761e06..89f97a4d0 100644 --- a/Client/src/Pages/DistributedUptime/Status/index.jsx +++ b/Client/src/Pages/DistributedUptimeStatus/Status/index.jsx @@ -1,12 +1,12 @@ //Components -import DistributedUptimeMap from "../Details/Components/DistributedUptimeMap"; +import DistributedUptimeMap from "../../DistributedUptime/Details/Components/DistributedUptimeMap"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import { Stack, Typography } from "@mui/material"; -import DeviceTicker from "../Details/Components/DeviceTicker"; -import DistributedUptimeResponseChart from "../Details/Components/DistributedUptimeResponseChart"; -import NextExpectedCheck from "../Details/Components/NextExpectedCheck"; -import Footer from "../Details/Components/Footer"; -import StatBoxes from "../Details/Components/StatBoxes"; +import DeviceTicker from "../../DistributedUptime/Details/Components/DeviceTicker"; +import DistributedUptimeResponseChart from "../../DistributedUptime/Details/Components/DistributedUptimeResponseChart"; +import NextExpectedCheck from "../../DistributedUptime/Details/Components/NextExpectedCheck"; +import Footer from "../../DistributedUptime/Details/Components/Footer"; +import StatBoxes from "../../DistributedUptime/Details/Components/StatBoxes"; import ControlsHeader from "../../StatusPage/Status/Components/ControlsHeader"; import MonitorTimeFrameHeader from "../../../Components/MonitorTimeFrameHeader"; import GenericFallback from "../../../Components/GenericFallback"; @@ -16,7 +16,7 @@ import SkeletonLayout from "./Components/Skeleton"; import { useTheme } from "@mui/material/styles"; import { useState } from "react"; import { useParams } from "react-router-dom"; -import { useSubscribeToDetails } from "../Details/Hooks/useSubscribeToDetails"; +import { useSubscribeToDetails } from "../../DistributedUptime/Details/Hooks/useSubscribeToDetails"; import { useStatusPageFetchByUrl } from "./Hooks/useStatusPageFetchByUrl"; import { useStatusPageDelete } from "../../StatusPage/Status/Hooks/useStatusPageDelete"; import { useNavigate } from "react-router-dom"; @@ -24,7 +24,7 @@ import { useLocation } from "react-router-dom"; const DistributedUptimeStatus = () => { const { url } = useParams(); const location = useLocation(); - const isPublic = location.pathname.startsWith("/distributed-uptime/status/public"); + const isPublic = location.pathname.startsWith("/status/distributed/public"); // Local State const [dateRange, setDateRange] = useState("day"); @@ -84,7 +84,7 @@ const DistributedUptimeStatus = () => { } // Done loading, a status page exists but is not public - if (!statusPageIsLoading && statusPage.isPublished === false) { + if (!statusPageIsLoading && isPublic && statusPage.isPublished === false) { return ( @@ -141,6 +141,8 @@ const DistributedUptimeStatus = () => { isDeleting={isDeleting} isDeleteOpen={isDeleteOpen} setIsDeleteOpen={setIsDeleteOpen} + url={url} + type="distributed" /> Date: Mon, 10 Feb 2025 13:45:01 -0800 Subject: [PATCH 09/21] Add basic status pages page --- .../StatusPages/Hooks/useStatusPagesFetch.jsx | 30 ++++++++ .../Pages/StatusPage/StatusPages/index.jsx | 77 +++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 Client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx create mode 100644 Client/src/Pages/StatusPage/StatusPages/index.jsx diff --git a/Client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx b/Client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx new file mode 100644 index 000000000..df5950930 --- /dev/null +++ b/Client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx @@ -0,0 +1,30 @@ +import { useState, useEffect } from "react"; +import { networkService } from "../../../../main"; +import { useSelector } from "react-redux"; +import { createToast } from "../../../../Utils/toastUtils"; + +const useStatusPagesFetch = () => { + const { authToken, user } = useSelector((state) => state.auth); + + const [isLoading, setIsLoading] = useState(true); + const [networkError, setNetworkError] = useState(false); + const [statusPages, setStatusPages] = useState(undefined); + + useEffect(() => { + try { + networkService + .getStatusPagesByTeamId({ authToken, teamId: user.teamId }) + .then((res) => { + setStatusPages(res.data.data); + }); + } catch (error) { + setNetworkError(true); + createToast(error.message, "error"); + } finally { + setIsLoading(false); + } + }, [authToken, user]); + return [isLoading, networkError, statusPages]; +}; + +export { useStatusPagesFetch }; diff --git a/Client/src/Pages/StatusPage/StatusPages/index.jsx b/Client/src/Pages/StatusPage/StatusPages/index.jsx new file mode 100644 index 000000000..0f9ba0aa5 --- /dev/null +++ b/Client/src/Pages/StatusPage/StatusPages/index.jsx @@ -0,0 +1,77 @@ +// Components +import { Stack, Typography } from "@mui/material"; +import Breadcrumbs from "../../../Components/Breadcrumbs"; +import Fallback from "../../../Components/Fallback"; +import GenericFallback from "../../../Components/GenericFallback"; +// Utils +import { useTheme } from "@emotion/react"; +import { useStatusPagesFetch } from "./Hooks/useStatusPagesFetch"; +import { useIsAdmin } from "../../../Hooks/useIsAdmin"; +import { useNavigate } from "react-router"; +const BREADCRUMBS = [{ name: `Status Pages`, path: "/distributed-uptime/status-pages" }]; + +const StatusPages = () => { + // Utils + const theme = useTheme(); + const isAdmin = useIsAdmin(); + const [isLoading, networkError, statusPages] = useStatusPagesFetch(); + const navigate = useNavigate(); + + // Handlers + const handleStatusPageClick = (statusPage) => { + if (statusPage.type === "distributed") { + navigate(`/status/distributed/${statusPage.url}`); + } else if (statusPage.type === "uptime") { + navigate(`/status/uptime/${statusPage.url}`); + } + }; + + if (!isLoading && typeof statusPages === "undefined") { + return ( + + ); + } + + if (networkError === true) { + return ( + + + Network error + + Please check your connection + + ); + } + return ( + + + {statusPages?.map((statusPage) => { + return ( + handleStatusPageClick(statusPage)} + sx={{ cursor: "pointer" }} + > + Company Name: {statusPage.companyName} + Status page URL: {statusPage.url} + Type: {statusPage.type} + + ); + })} + + ); +}; + +export default StatusPages; From 01d1f8e173035669e169f782fe5b84a1089fd269 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 10 Feb 2025 13:45:23 -0800 Subject: [PATCH 10/21] update routes --- Client/src/Routes/index.jsx | 64 ++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/Client/src/Routes/index.jsx b/Client/src/Routes/index.jsx index 80e91d995..c36a008a7 100644 --- a/Client/src/Routes/index.jsx +++ b/Client/src/Routes/index.jsx @@ -31,14 +31,17 @@ import InfrastructureDetails from "../Pages/Infrastructure/Details"; import DistributedUptimeMonitors from "../Pages/DistributedUptime/Monitors"; import CreateDistributedUptime from "../Pages/DistributedUptime/Create"; import DistributedUptimeDetails from "../Pages/DistributedUptime/Details"; -import DistributedUptimeStatus from "../Pages/DistributedUptime/Status"; -import CreateDistributedUptimeStatus from "../Pages/DistributedUptime/CreateStatus"; + +// Distributed Uptime Status +import CreateDistributedUptimeStatus from "../Pages/DistributedUptimeStatus/Create"; +import DistributedUptimeStatus from "../Pages/DistributedUptimeStatus/Status"; // Incidents import Incidents from "../Pages/Incidents"; // Status pages import CreateStatus from "../Pages/StatusPage/Create"; +import StatusPages from "../Pages/StatusPage/StatusPages"; import Status from "../Pages/StatusPage/Status"; import Integrations from "../Pages/Integrations"; @@ -112,22 +115,6 @@ const Routes = () => { } /> - - - - } - /> - - - - } - /> { } + /> + + } /> + + + } + /> + + } /> + + + + } + /> + + } /> + + + + } + /> + } @@ -239,11 +259,11 @@ const Routes = () => { element={} /> } /> } /> From 55321ffc3cdbfe6b36ca07db45f4fab3aa0ad563 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 10 Feb 2025 13:46:26 -0800 Subject: [PATCH 11/21] use url in hook --- .../StatusPage/Create/Hooks/useCreateStatusPage.jsx | 4 ++-- .../StatusPage/Status/Hooks/useStatusPageDelete.jsx | 2 +- .../StatusPage/Status/Hooks/useStatusPageFetch.jsx | 11 +++++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx b/Client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx index 92e2acf96..4972672f3 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 = (isCreate) => { +const useCreateStatusPage = (isCreate, url) => { const { authToken, user } = useSelector((state) => state.auth); const [isLoading, setIsLoading] = useState(false); @@ -11,7 +11,7 @@ const useCreateStatusPage = (isCreate) => { const createStatusPage = async ({ form }) => { setIsLoading(true); try { - await networkService.createStatusPage({ authToken, user, form, isCreate }); + await networkService.createStatusPage({ authToken, user, form, isCreate, url }); return true; } catch (error) { setNetworkError(true); diff --git a/Client/src/Pages/StatusPage/Status/Hooks/useStatusPageDelete.jsx b/Client/src/Pages/StatusPage/Status/Hooks/useStatusPageDelete.jsx index f9699020c..6f8368f37 100644 --- a/Client/src/Pages/StatusPage/Status/Hooks/useStatusPageDelete.jsx +++ b/Client/src/Pages/StatusPage/Status/Hooks/useStatusPageDelete.jsx @@ -4,7 +4,7 @@ import { networkService } from "../../../../main"; import { createToast } from "../../../../Utils/toastUtils"; import { useNavigate } from "react-router-dom"; -const useStatusPageDelete = (fetchStatusPage, url = "/status/public") => { +const useStatusPageDelete = (fetchStatusPage, url) => { const [isLoading, setIsLoading] = useState(false); const navigate = useNavigate(); const { authToken } = useSelector((state) => state.auth); diff --git a/Client/src/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx b/Client/src/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx index 013e32dd9..317dfe7b6 100644 --- a/Client/src/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx +++ b/Client/src/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx @@ -5,7 +5,7 @@ import { createToast } from "../../../../Utils/toastUtils"; import { useTheme } from "@emotion/react"; import { useMonitorUtils } from "../../../../Hooks/useMonitorUtils"; -const useStatusPageFetch = (isCreate = false) => { +const useStatusPageFetch = (isCreate = false, url) => { const [isLoading, setIsLoading] = useState(true); const [networkError, setNetworkError] = useState(false); const [statusPage, setStatusPage] = useState(undefined); @@ -15,10 +15,13 @@ const useStatusPageFetch = (isCreate = false) => { const { getMonitorWithPercentage } = useMonitorUtils(); const fetchStatusPage = useCallback(async () => { try { - const response = await networkService.getStatusPage({ authToken }); + const response = await networkService.getStatusPageByUrl({ + authToken, + url, + type: "uptime", + }); if (!response?.data?.data) return; const { statusPage, monitors } = response.data.data; - setStatusPage(statusPage); const monitorsWithPercentage = monitors.map((monitor) => @@ -37,7 +40,7 @@ const useStatusPageFetch = (isCreate = false) => { } finally { setIsLoading(false); } - }, [authToken, theme, getMonitorWithPercentage]); + }, [authToken, theme, getMonitorWithPercentage, url]); useEffect(() => { if (isCreate === true) { From b99bb99e2a797d3ea53a8434c57476bb457770b7 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 10 Feb 2025 13:46:48 -0800 Subject: [PATCH 12/21] update path --- Client/src/Pages/DistributedUptime/Details/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/src/Pages/DistributedUptime/Details/index.jsx b/Client/src/Pages/DistributedUptime/Details/index.jsx index 27a596af4..9915e1811 100644 --- a/Client/src/Pages/DistributedUptime/Details/index.jsx +++ b/Client/src/Pages/DistributedUptime/Details/index.jsx @@ -74,7 +74,7 @@ const DistributedUptimeDetails = () => { Date: Mon, 10 Feb 2025 13:47:13 -0800 Subject: [PATCH 13/21] allow editing in create component --- Client/src/Pages/DistributedUptime/Create/index.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Client/src/Pages/DistributedUptime/Create/index.jsx b/Client/src/Pages/DistributedUptime/Create/index.jsx index 2df0a4482..a0efca428 100644 --- a/Client/src/Pages/DistributedUptime/Create/index.jsx +++ b/Client/src/Pages/DistributedUptime/Create/index.jsx @@ -18,6 +18,7 @@ import { useNavigate } from "react-router-dom"; import { useSelector, useDispatch } from "react-redux"; import { monitorValidation } from "../../../Validation/validation"; import { createUptimeMonitor } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice"; +import { useLocation } from "react-router-dom"; // Constants const BREADCRUMBS = [ @@ -34,6 +35,9 @@ const SELECT_VALUES = [ ]; const CreateDistributedUptime = () => { + const location = useLocation(); + const isCreate = location.pathname.startsWith("/distributed-uptime/create"); + // Redux state const { user, authToken } = useSelector((state) => state.auth); const isLoading = useSelector((state) => state.uptimeMonitors.isLoading); @@ -103,7 +107,6 @@ const CreateDistributedUptime = () => { { [name]: value }, { abortEarly: false } ); - console.log(name); setErrors((prev) => ({ ...prev, ...(error ? { [name]: error.details[0].message } : { [name]: undefined }), From 9a67348a8388fdde01d242e48cb2947483f10992 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 10 Feb 2025 13:48:10 -0800 Subject: [PATCH 14/21] Update status page to use URL, no longer shows create fallback --- .../Components/ControlsHeader/index.jsx | 28 ++++++++++--- Client/src/Pages/StatusPage/Status/index.jsx | 42 +++++++++---------- 2 files changed, 43 insertions(+), 27 deletions(-) diff --git a/Client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx b/Client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx index 113f0bafc..59807bf17 100644 --- a/Client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx +++ b/Client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx @@ -9,17 +9,17 @@ import { useNavigate } from "react-router-dom"; import { useLocation } from "react-router-dom"; import PropTypes from "prop-types"; -const Controls = ({ isDeleteOpen, setIsDeleteOpen, isDeleting }) => { +const Controls = ({ isDeleteOpen, setIsDeleteOpen, isDeleting, url, type }) => { const theme = useTheme(); const location = useLocation(); const currentPath = location.pathname; const navigate = useNavigate(); - if (currentPath === "/status/public") { + if (currentPath.startsWith("/status/uptime/public")) { return null; } - if (currentPath.startsWith("/distributed-uptime/status/public")) { + if (currentPath.startsWith("/status/distributed/public")) { return null; } @@ -42,7 +42,13 @@ const Controls = ({ isDeleteOpen, setIsDeleteOpen, isDeleting }) => {