Merge remote-tracking branch 'upstream/develop' into feat/be/webhook-integrations

This commit is contained in:
Skorpios committed 2025-02-10 18:26:58 -08:00
commit ccd6c58e04
28 files changed
+406 -104

No files matched your search

-1
View File
@@ -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
@@ -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"]}
@@ -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);
@@ -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);
+16 -5
View File
@@ -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) {
+25 -26
View File
@@ -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
View File
@@ -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 />}
/>
+20 -4
View File
@@ -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);
+1
View File
@@ -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()
+3 -3
View File
@@ -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.
+16 -1
View File
@@ -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);
+18
View File
@@ -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,
+33 -8
View File
@@ -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,
+2
View File
@@ -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"),
+1
View File
@@ -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",
+8
View File
@@ -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,
};