Merge remote-tracking branch 'upstream/develop' into feat/be/webhook-integrations
This commit is contained in:
commit
ccd6c58e04
28 files changed
+406
-104
No files matched your search
@@ -27,7 +27,6 @@ const Fallback = ({ title, checks, link = "/", isAdmin, vowelStart = false }) =>
|
||||
|
||||
return (
|
||||
<Box
|
||||
className="page-speed"
|
||||
position="relative"
|
||||
border={1}
|
||||
borderColor={theme.palette.primary.lowContrast}
|
||||
|
||||
@@ -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 }),
|
||||
|
||||
@@ -74,7 +74,7 @@ const DistributedUptimeDetails = () => {
|
||||
<MonitorCreateHeader
|
||||
label="Create status page"
|
||||
isAdmin={isAdmin}
|
||||
path={`/distributed-uptime/status/create/${monitorId}`}
|
||||
path={`/status/distributed/create/${monitorId}`}
|
||||
/>
|
||||
<MonitorHeader monitor={monitor} />
|
||||
<StatBoxes
|
||||
|
||||
File renamed without changes.
+45
-7
@@ -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 (
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
@@ -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"]}
|
||||
File renamed without changes.
+5
-1
@@ -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);
|
||||
+11
-9
@@ -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 (
|
||||
<Stack sx={sx}>
|
||||
<GenericFallback>
|
||||
@@ -141,6 +141,8 @@ const DistributedUptimeStatus = () => {
|
||||
isDeleting={isDeleting}
|
||||
isDeleteOpen={isDeleteOpen}
|
||||
setIsDeleteOpen={setIsDeleteOpen}
|
||||
url={url}
|
||||
type="distributed"
|
||||
/>
|
||||
<StatBoxes
|
||||
monitor={monitor}
|
||||
@@ -15,6 +15,7 @@ import timezones from "../../../../../Utils/timezones.json";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const TabSettings = ({
|
||||
isCreate,
|
||||
tabValue,
|
||||
form,
|
||||
handleFormChange,
|
||||
@@ -68,8 +69,8 @@ const TabSettings = ({
|
||||
id="url"
|
||||
name="url"
|
||||
type="url"
|
||||
disabled={!isCreate}
|
||||
label="Your status page address"
|
||||
disabled
|
||||
value={form.url}
|
||||
onChange={handleFormChange}
|
||||
/>
|
||||
@@ -128,6 +129,7 @@ const TabSettings = ({
|
||||
};
|
||||
|
||||
TabSettings.propTypes = {
|
||||
isCreate: PropTypes.bool,
|
||||
tabValue: PropTypes.string,
|
||||
form: PropTypes.object,
|
||||
handleFormChange: PropTypes.func,
|
||||
|
||||
@@ -8,6 +8,7 @@ import Content from "./Content";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const Tabs = ({
|
||||
isCreate,
|
||||
form,
|
||||
errors,
|
||||
monitors,
|
||||
@@ -47,6 +48,7 @@ const Tabs = ({
|
||||
progress={progress}
|
||||
removeLogo={removeLogo}
|
||||
errors={errors}
|
||||
isCreate={isCreate}
|
||||
/>
|
||||
) : (
|
||||
<Content
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -14,6 +14,7 @@ import { createToast } from "../../../Utils/toastUtils";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { useStatusPageFetch } from "../Status/Hooks/useStatusPageFetch";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
//Constants
|
||||
const TAB_LIST = ["General settings", "Contents"];
|
||||
@@ -24,16 +25,18 @@ const ERROR_TAB_MAPPING = [
|
||||
];
|
||||
|
||||
const CreateStatusPage = () => {
|
||||
const { url } = useParams();
|
||||
//Local state
|
||||
const [tab, setTab] = useState(0);
|
||||
const [progress, setProgress] = useState({ value: 0, isLoading: false });
|
||||
const [form, setForm] = useState({
|
||||
isPublished: false,
|
||||
companyName: "",
|
||||
url: "/status/public",
|
||||
url: url ?? Math.floor(Math.random() * 1000000).toFixed(0),
|
||||
logo: undefined,
|
||||
timezone: "America/Toronto",
|
||||
color: "#4169E1",
|
||||
type: "uptime",
|
||||
monitors: [],
|
||||
showCharts: true,
|
||||
showUptimePercentage: true,
|
||||
@@ -45,7 +48,7 @@ const CreateStatusPage = () => {
|
||||
|
||||
// Setup
|
||||
const location = useLocation();
|
||||
const isCreate = location.pathname === "/status/create";
|
||||
const isCreate = location.pathname === "/status/uptime/create";
|
||||
|
||||
//Utils
|
||||
const theme = useTheme();
|
||||
@@ -53,8 +56,9 @@ const CreateStatusPage = () => {
|
||||
const [createStatusPage, createStatusIsLoading, createStatusPageNetworkError] =
|
||||
useCreateStatusPage(isCreate);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [statusPage, statusPageMonitors, statusPageIsLoading, statusPageNetworkError] =
|
||||
useStatusPageFetch(isCreate);
|
||||
useStatusPageFetch(isCreate, url);
|
||||
|
||||
// Handlers
|
||||
const handleFormChange = (e) => {
|
||||
@@ -131,7 +135,7 @@ const CreateStatusPage = () => {
|
||||
const success = await createStatusPage({ form });
|
||||
if (success) {
|
||||
createToast({ body: "Status page created successfully" });
|
||||
navigate("/status");
|
||||
navigate(`/status/uptime/${form.url}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -141,7 +145,6 @@ const CreateStatusPage = () => {
|
||||
newErrors[err.path[0]] = err.message;
|
||||
});
|
||||
setErrors((prev) => ({ ...prev, ...newErrors }));
|
||||
|
||||
const errorTabs = Object.keys(newErrors).map((err) => {
|
||||
return ERROR_TAB_MAPPING.findIndex((tab) => tab.includes(err));
|
||||
});
|
||||
@@ -150,6 +153,13 @@ const CreateStatusPage = () => {
|
||||
if (errorTabs.some((errorTab) => errorTab === tab)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we get -1, there's an unknown error
|
||||
if (errorTabs[0] === -1) {
|
||||
createToast({ body: "Unknown error" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise go to tab with error
|
||||
setTab(errorTabs[0]);
|
||||
};
|
||||
@@ -218,6 +228,7 @@ const CreateStatusPage = () => {
|
||||
tab={tab}
|
||||
setTab={setTab}
|
||||
TAB_LIST={TAB_LIST}
|
||||
isCreate={isCreate}
|
||||
/>
|
||||
<Stack
|
||||
direction="row"
|
||||
|
||||
@@ -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 }) => {
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={() => navigate(`/status/configure`)}
|
||||
onClick={() => {
|
||||
if (type === "uptime") {
|
||||
navigate(`/status/uptime/configure/${url}`);
|
||||
} else {
|
||||
navigate(`/status/distributed/configure/${url}`);
|
||||
}
|
||||
}}
|
||||
sx={{
|
||||
px: theme.spacing(5),
|
||||
"& svg": {
|
||||
@@ -61,12 +67,21 @@ const Controls = ({ isDeleteOpen, setIsDeleteOpen, isDeleting }) => {
|
||||
};
|
||||
|
||||
Controls.propTypes = {
|
||||
type: PropTypes.string,
|
||||
isDeleting: PropTypes.bool,
|
||||
url: PropTypes.string,
|
||||
isDeleteOpen: PropTypes.bool.isRequired,
|
||||
setIsDeleteOpen: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const ControlsHeader = ({ statusPage, isDeleting, isDeleteOpen, setIsDeleteOpen }) => {
|
||||
const ControlsHeader = ({
|
||||
statusPage,
|
||||
isDeleting,
|
||||
isDeleteOpen,
|
||||
setIsDeleteOpen,
|
||||
url,
|
||||
type = "uptime",
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
@@ -95,16 +110,20 @@ const ControlsHeader = ({ statusPage, isDeleting, isDeleteOpen, setIsDeleteOpen
|
||||
isDeleting={isDeleting}
|
||||
isDeleteOpen={isDeleteOpen}
|
||||
setIsDeleteOpen={setIsDeleteOpen}
|
||||
url={url}
|
||||
type={type}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
ControlsHeader.propTypes = {
|
||||
url: PropTypes.string,
|
||||
statusPage: PropTypes.object,
|
||||
isDeleting: PropTypes.bool,
|
||||
isDeleteOpen: PropTypes.bool.isRequired,
|
||||
setIsDeleteOpen: PropTypes.func.isRequired,
|
||||
type: PropTypes.string,
|
||||
};
|
||||
|
||||
export default ControlsHeader;
|
||||
@@ -2,11 +2,9 @@ import { useSelector } from "react-redux";
|
||||
import { useState } from "react";
|
||||
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);
|
||||
const deleteStatusPage = async () => {
|
||||
try {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Components
|
||||
import { Typography, Stack } from "@mui/material";
|
||||
import GenericFallback from "../../../Components/GenericFallback";
|
||||
import Fallback from "../../../Components/Fallback";
|
||||
import AdminLink from "./Components/AdminLink";
|
||||
import ControlsHeader from "./Components/ControlsHeader";
|
||||
import SkeletonLayout from "./Components/Skeleton";
|
||||
@@ -16,25 +15,30 @@ import { useIsAdmin } from "../../../Hooks/useIsAdmin";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { useStatusPageDelete } from "./Hooks/useStatusPageDelete";
|
||||
import { useState } from "react";
|
||||
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
const PublicStatus = () => {
|
||||
const { url } = useParams();
|
||||
|
||||
// Local state
|
||||
const [isDeleteOpen, setIsDeleteOpen] = useState(false);
|
||||
// Utils
|
||||
const theme = useTheme();
|
||||
const isAdmin = useIsAdmin();
|
||||
const [statusPage, monitors, isLoading, networkError, fetchStatusPage] =
|
||||
useStatusPageFetch();
|
||||
const [deleteStatusPage, isDeleting] = useStatusPageDelete(fetchStatusPage);
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [statusPage, monitors, isLoading, networkError, fetchStatusPage] =
|
||||
useStatusPageFetch(false, url);
|
||||
const [deleteStatusPage, isDeleting] = useStatusPageDelete(fetchStatusPage, url);
|
||||
|
||||
// Setup
|
||||
const currentPath = location.pathname;
|
||||
let sx = { paddingLeft: theme.spacing(20), paddingRight: theme.spacing(20) };
|
||||
let link = undefined;
|
||||
|
||||
const isPublic = location.pathname.startsWith("/status/uptime/public");
|
||||
// Public status page
|
||||
if (currentPath === "/status/public") {
|
||||
if (isPublic) {
|
||||
sx = {
|
||||
paddingTop: theme.spacing(20),
|
||||
paddingLeft: "20vw",
|
||||
@@ -65,11 +69,7 @@ const PublicStatus = () => {
|
||||
}
|
||||
|
||||
// Public status page fallback
|
||||
if (
|
||||
!isLoading &&
|
||||
typeof statusPage === "undefined" &&
|
||||
currentPath === "/status/public"
|
||||
) {
|
||||
if (!isLoading && typeof statusPage === "undefined" && isPublic) {
|
||||
return (
|
||||
<Stack sx={sx}>
|
||||
<GenericFallback>
|
||||
@@ -87,11 +87,7 @@ const PublicStatus = () => {
|
||||
}
|
||||
|
||||
// Finished loading, but status page is not public
|
||||
if (
|
||||
!isLoading &&
|
||||
currentPath === "/status/public" &&
|
||||
statusPage.isPublished === false
|
||||
) {
|
||||
if (!isLoading && isPublic && statusPage.isPublished === false) {
|
||||
return (
|
||||
<Stack sx={sx}>
|
||||
<GenericFallback>
|
||||
@@ -111,15 +107,16 @@ const PublicStatus = () => {
|
||||
// Status page doesn't exist
|
||||
if (!isLoading && typeof statusPage === "undefined") {
|
||||
return (
|
||||
<Fallback
|
||||
title="status page"
|
||||
checks={[
|
||||
"Display a list of monitors to track",
|
||||
"Share your monitors with the public",
|
||||
]}
|
||||
link="/status/create"
|
||||
isAdmin={isAdmin}
|
||||
/>
|
||||
<GenericFallback>
|
||||
<Typography
|
||||
variant="h1"
|
||||
marginY={theme.spacing(4)}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
There's no status page here.
|
||||
</Typography>
|
||||
<Typography>Please contact to your administrator</Typography>
|
||||
</GenericFallback>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -134,6 +131,7 @@ const PublicStatus = () => {
|
||||
isDeleting={isDeleting}
|
||||
isDeleteOpen={isDeleteOpen}
|
||||
setIsDeleteOpen={setIsDeleteOpen}
|
||||
url={url}
|
||||
/>
|
||||
<Typography variant="h2">Service status</Typography>
|
||||
<StatusBar monitors={monitors} />
|
||||
@@ -144,6 +142,7 @@ const PublicStatus = () => {
|
||||
onConfirm={() => {
|
||||
deleteStatusPage();
|
||||
setIsDeleteOpen(false);
|
||||
navigate("/status");
|
||||
}}
|
||||
onCancel={() => {
|
||||
setIsDeleteOpen(false);
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
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(() => {
|
||||
const fetchStatusPages = async () => {
|
||||
try {
|
||||
const res = await networkService.getStatusPagesByTeamId({
|
||||
authToken,
|
||||
teamId: user.teamId,
|
||||
});
|
||||
setStatusPages(res?.data?.data);
|
||||
} catch (error) {
|
||||
setNetworkError(true);
|
||||
createToast(error.message, "error");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
fetchStatusPages();
|
||||
}, [authToken, user]);
|
||||
return [isLoading, networkError, statusPages];
|
||||
};
|
||||
|
||||
export { useStatusPagesFetch };
|
||||
@@ -0,0 +1,83 @@
|
||||
// Components
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
||||
import Fallback from "../../../Components/Fallback";
|
||||
import MonitorCreateHeader from "../../../Components/MonitorCreateHeader";
|
||||
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: "" }];
|
||||
|
||||
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 (
|
||||
<Fallback
|
||||
title="status page"
|
||||
checks={[
|
||||
"Display a list of monitors to track",
|
||||
"Share your monitors with the public",
|
||||
]}
|
||||
link="/status/uptime/create"
|
||||
isAdmin={isAdmin}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (networkError === true) {
|
||||
return (
|
||||
<GenericFallback>
|
||||
<Typography
|
||||
variant="h1"
|
||||
marginY={theme.spacing(4)}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
Network error
|
||||
</Typography>
|
||||
<Typography>Please check your connection</Typography>
|
||||
</GenericFallback>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<MonitorCreateHeader
|
||||
label="Create status page"
|
||||
isAdmin={isAdmin}
|
||||
path="/status/uptime/create"
|
||||
/>
|
||||
{statusPages?.map((statusPage) => {
|
||||
return (
|
||||
<Stack
|
||||
key={statusPage._id}
|
||||
onClick={() => handleStatusPageClick(statusPage)}
|
||||
sx={{ cursor: "pointer" }}
|
||||
>
|
||||
<Typography variant="h2">Company Name: {statusPage.companyName}</Typography>
|
||||
<Typography variant="h2">Status page URL: {statusPage.url}</Typography>
|
||||
<Typography variant="h2">Type: {statusPage.type}</Typography>
|
||||
</Stack>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatusPages;
|
||||
+42
-22
@@ -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 = () => {
|
||||
</ProtectedDistributedUptimeRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/distributed-uptime/status/create/:monitorId"
|
||||
element={
|
||||
<ProtectedDistributedUptimeRoute>
|
||||
<CreateDistributedUptimeStatus />
|
||||
</ProtectedDistributedUptimeRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/distributed-uptime/status/:url"
|
||||
element={
|
||||
<ProtectedDistributedUptimeRoute>
|
||||
<DistributedUptimeStatus />
|
||||
</ProtectedDistributedUptimeRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="pagespeed"
|
||||
@@ -164,18 +151,51 @@ const Routes = () => {
|
||||
|
||||
<Route
|
||||
path="status"
|
||||
element={<StatusPages />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="status/uptime/:url"
|
||||
element={<Status />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="status/create"
|
||||
path="/status/distributed/:url"
|
||||
element={
|
||||
<ProtectedDistributedUptimeRoute>
|
||||
<DistributedUptimeStatus />
|
||||
</ProtectedDistributedUptimeRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="status/uptime/create"
|
||||
element={<CreateStatus />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="status/configure"
|
||||
path="/status/distributed/create/:monitorId"
|
||||
element={
|
||||
<ProtectedDistributedUptimeRoute>
|
||||
<CreateDistributedUptimeStatus />
|
||||
</ProtectedDistributedUptimeRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="status/uptime/configure/:url"
|
||||
element={<CreateStatus />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/status/distributed/configure/:url"
|
||||
element={
|
||||
<ProtectedDistributedUptimeRoute>
|
||||
<CreateDistributedUptimeStatus />
|
||||
</ProtectedDistributedUptimeRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="integrations"
|
||||
element={<Integrations />}
|
||||
@@ -239,11 +259,11 @@ const Routes = () => {
|
||||
element={<AuthNewPasswordConfirmed />}
|
||||
/>
|
||||
<Route
|
||||
path="/status/public"
|
||||
path="/status/uptime/public/:url"
|
||||
element={<Status />}
|
||||
/>
|
||||
<Route
|
||||
path="/distributed-uptime/status/public/:url"
|
||||
path="/status/distributed/public/:url"
|
||||
element={<DistributedUptimeStatus />}
|
||||
/>
|
||||
|
||||
|
||||
@@ -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,
|
||||
@@ -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",
|
||||
@@ -1003,6 +1016,9 @@ 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);
|
||||
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);
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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, "getStatusPageByTeamId"));
|
||||
}
|
||||
};
|
||||
|
||||
deleteStatusPage = async (req, res, next) => {
|
||||
try {
|
||||
await this.db.deleteStatusPage(req.params.url);
|
||||
|
||||
@@ -2,6 +2,24 @@ 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,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: "uptime",
|
||||
enum: ["uptime", "distributed"],
|
||||
},
|
||||
companyName: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -12,7 +12,9 @@ class StatusPageRoutes {
|
||||
|
||||
initRoutes() {
|
||||
this.router.get("/", this.statusPageController.getStatusPage);
|
||||
this.router.get("/team/:teamId", this.statusPageController.getStatusPagesByTeamId);
|
||||
this.router.get("/:url", this.statusPageController.getStatusPageByUrl);
|
||||
|
||||
this.router.post(
|
||||
"/",
|
||||
upload.single("logo"),
|
||||
|
||||
@@ -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",
|
||||
|
||||
|
||||
@@ -418,7 +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(),
|
||||
@@ -519,5 +526,6 @@ export {
|
||||
updateAppSettingsBodyValidation,
|
||||
createStatusPageBodyValidation,
|
||||
getStatusPageParamValidation,
|
||||
getStatusPageQueryValidation,
|
||||
imageValidation,
|
||||
};
|
||||
Reference in new issue
Block a user