Merge pull request #1693 from bluewave-labs/feat/fe/status-page-config

feat: fe/status page config
This commit is contained in:
Alexander Holliday authored and GitHub committed 2025-02-05 12:09:58 -08:00
commit 89be714811
12 files changed
+134 -16

No files matched your search

@@ -79,6 +79,7 @@ const Search = ({
disableClearable
options={options}
getOptionLabel={(option) => option[filteredBy]}
isOptionEqualToValue={(option, value) => option._id === value._id} // Compare by unique identifier
renderInput={(params) => (
<Stack>
<Typography
@@ -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);
+44 -3
View File
@@ -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 (
<GenericFallback>
@@ -162,6 +202,7 @@ const CreateStatusPage = () => {
if (isLoading) return <SkeletonLayout />;
// Load fields
return (
<Stack gap={theme.spacing(10)}>
<Tabs
@@ -37,7 +37,7 @@ const Controls = ({ deleteStatusPage, isDeleting }) => {
<Button
variant="contained"
color="secondary"
onClick={() => navigate(`/status/create`)}
onClick={() => navigate(`/status/configure`)}
sx={{
px: theme.spacing(5),
"& svg": {
@@ -28,7 +28,7 @@ const getMonitorWithPercentage = (monitor, theme) => {
};
};
const useStatusPageFetch = () => {
const useStatusPageFetch = (isCreate = false) => {
const [isLoading, setIsLoading] = useState(true);
const [networkError, setNetworkError] = useState(false);
const [statusPage, setStatusPage] = useState(undefined);
@@ -59,9 +59,13 @@ const useStatusPageFetch = () => {
setIsLoading(false);
}
}, [authToken, theme]);
useEffect(() => {
if (isCreate === true) {
return;
}
fetchStatusPage();
}, [fetchStatusPage]);
}, [isCreate, fetchStatusPage]);
return [statusPage, monitors, isLoading, networkError, fetchStatusPage];
};
+1 -3
View File
@@ -1,5 +1,5 @@
// Components
import { Typography, Stack, Box } from "@mui/material";
import { Typography, Stack } from "@mui/material";
import GenericFallback from "../../../Components/GenericFallback";
import Fallback from "../../../Components/Fallback";
import AdminLink from "./Components/AdminLink";
@@ -8,12 +8,10 @@ import SkeletonLayout from "./Components/Skeleton";
import StatusBar from "./Components/StatusBar";
import MonitorsList from "./Components/MonitorsList";
// Utils
import { useState, useEffect } from "react";
import { useStatusPageFetch } from "./Hooks/useStatusPageFetch";
import { useTheme } from "@emotion/react";
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
import { useLocation } from "react-router-dom";
import { useNavigate } from "react-router-dom";
import { useStatusPageDelete } from "./Hooks/useStatusPageDelete";
const PublicStatus = () => {
+4
View File
@@ -120,6 +120,10 @@ const Routes = () => {
path="status/create"
element={<CreateStatus />}
/>
<Route
path="status/configure"
element={<CreateStatus />}
/>
<Route
path="integrations"
+9 -2
View File
@@ -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}`,
},
+26 -1
View File
@@ -4,7 +4,7 @@ import {
getStatusPageParamValidation,
imageValidation,
} from "../validation/joi.js";
import { successMessages } from "../utils/messages.js";
import { successMessages, errorMessages } from "../utils/messages.js";
const SERVICE_NAME = "statusPageController";
@@ -33,6 +33,31 @@ 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);
if (statusPage === null) {
const error = new Error(errorMessages.STATUS_PAGE_NOT_FOUND);
error.status = 404;
throw error;
}
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();
+33 -2
View File
@@ -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 };
+6
View File
@@ -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);
}
+1
View File
@@ -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",