Merge pull request #1693 from bluewave-labs/feat/fe/status-page-config
feat: fe/status page config
This commit is contained in:
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);
|
||||
|
||||
@@ -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,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 = () => {
|
||||
|
||||
@@ -120,6 +120,10 @@ const Routes = () => {
|
||||
path="status/create"
|
||||
element={<CreateStatus />}
|
||||
/>
|
||||
<Route
|
||||
path="status/configure"
|
||||
element={<CreateStatus />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="integrations"
|
||||
|
||||
@@ -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}`,
|
||||
},
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 };
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
|
||||
Reference in new issue
Block a user