diff --git a/.github/workflows/sync-upstream-frontend.yml b/.github/workflows/sync-upstream-frontend.yml
deleted file mode 100644
index 26a7ee723..000000000
--- a/.github/workflows/sync-upstream-frontend.yml
+++ /dev/null
@@ -1,60 +0,0 @@
-name: Sync Upstream (Frontend)
-
-on:
- workflow_dispatch:
-
-jobs:
- sync-upstream:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout private repository
- uses: actions/checkout@v3
- with:
- fetch-depth: 0
-
- - name: Get Current Date and Day
- id: date
- run: echo "BRANCH_NAME=shadow/$(date +%A-%Y-%m-%d)" >> $GITHUB_ENV
-
- - name: Add Upstream Remote and Fetch Changes
- run: |
- git remote add upstream https://github.com/bluewave-labs/checkmate.git || true
- git fetch upstream develop --prune
- if ! git show-ref --quiet refs/remotes/upstream/develop; then
- echo "ERROR: upstream/develop not found"
- exit 1
- fi
-
- - name: Ensure Local Develop is Up-to-Date
- run: |
- git checkout develop
- git pull origin develop --prune
-
- - name: Create Shadow Branch
- run: |
- git checkout -b ${{ env.BRANCH_NAME }} develop
-
- - name: Identify New Commits
- id: check_commits
- run: |
- NEW_COMMITS=$(git log develop..upstream/develop --pretty=format:"%H" | wc -l)
- echo "New commits: $NEW_COMMITS"
- if [ "$NEW_COMMITS" -eq 0 ]; then
- echo "No new commits. Exiting."
- exit 0
- fi
-
- - name: Cherry-pick New Commits
- run: |
- git cherry-pick develop..upstream/develop || (git cherry-pick --abort && exit 1)
- git push origin ${{ env.BRANCH_NAME }}
-
- - name: Create Pull Request
- uses: peter-evans/create-pull-request@v5
- with:
- title: "Sync Upstream (Frontend) - ${{ env.BRANCH_NAME }}"
- body: "Automated upstream sync for frontend repository. Please review and merge."
- branch: ${{ env.BRANCH_NAME }}
- base: develop
- labels: "sync, upstream"
- reviewers: "ridvankaratas, ajhollid"
diff --git a/src/Components/Charts/DePINStatusPageBarChart/index.jsx b/src/Components/Charts/DePINStatusPageBarChart/index.jsx
new file mode 100644
index 000000000..65c6278f1
--- /dev/null
+++ b/src/Components/Charts/DePINStatusPageBarChart/index.jsx
@@ -0,0 +1,181 @@
+// Components
+import { Stack, Box, Tooltip, Typography } from "@mui/material";
+
+// Utils
+import { useTheme } from "@emotion/react";
+import { useState, useEffect } from "react";
+import PropTypes from "prop-types";
+import { formatDateWithTz } from "../../../Utils/timeUtils";
+import { useSelector } from "react-redux";
+
+const PlaceholderCheck = ({ daysToShow }) => {
+ const theme = useTheme();
+
+ return (
+
+ );
+};
+
+PlaceholderCheck.propTypes = {
+ daysToShow: PropTypes.number,
+};
+
+const Check = ({ check, daysToShow }) => {
+ const [animate, setAnimate] = useState(false);
+
+ const theme = useTheme();
+ useEffect(() => {
+ setAnimate(true);
+ }, []);
+ const uiTimezone = useSelector((state) => state.ui.timezone);
+
+ return (
+
+
+ {formatDateWithTz(check._id, "ddd, MMMM D, YYYY", uiTimezone)}
+
+
+
+
+ Uptime percentage
+
+
+ {check.upPercentage.toFixed(2)}
+
+ {" "}
+ %
+
+
+
+
+ >
+ }
+ placement="top"
+ key={`check-${check?._id}`}
+ slotProps={{
+ popper: {
+ className: "bar-tooltip",
+ modifiers: [
+ {
+ name: "offset",
+ options: {
+ offset: [0, -10],
+ },
+ },
+ ],
+ sx: {
+ "& .MuiTooltip-tooltip": {
+ backgroundColor: theme.palette.secondary.main,
+ border: 1,
+ borderColor: theme.palette.primary.lowContrast,
+ borderRadius: theme.shape.borderRadius,
+ boxShadow: theme.shape.boxShadow,
+ px: theme.spacing(4),
+ py: theme.spacing(3),
+ },
+ "& .MuiTooltip-tooltip p": {
+ /* TODO Font size should point to theme */
+ fontSize: 12,
+ color: theme.palette.secondary.contrastText,
+ fontWeight: 500,
+ },
+ "& .MuiTooltip-tooltip span": {
+ /* TODO Font size should point to theme */
+ fontSize: 11,
+ color: theme.palette.secondary.contrastText,
+ fontWeight: 600,
+ },
+ },
+ },
+ }}
+ >
+
+
+
+
+ );
+};
+
+Check.propTypes = {
+ check: PropTypes.object,
+ daysToShow: PropTypes.number,
+};
+
+const DePINStatusPageBarChart = ({ checks = [], daysToShow = 30 }) => {
+ if (checks.length !== daysToShow) {
+ const placeholders = Array(daysToShow - checks.length).fill("placeholder");
+ checks = [...checks, ...placeholders];
+ }
+ return (
+
+ {checks.map((check) => {
+ if (check === "placeholder") {
+ return (
+
+ );
+ }
+ return (
+
+ );
+ })}
+
+ );
+};
+
+DePINStatusPageBarChart.propTypes = {
+ checks: PropTypes.array,
+ daysToShow: PropTypes.number,
+};
+
+export default DePINStatusPageBarChart;
diff --git a/src/Pages/DistributedUptimeStatus/Create/Hooks/useDUStatusPageFetch.jsx b/src/Pages/DistributedUptimeStatus/Create/Hooks/useDUStatusPageFetch.jsx
new file mode 100644
index 000000000..eecb161a3
--- /dev/null
+++ b/src/Pages/DistributedUptimeStatus/Create/Hooks/useDUStatusPageFetch.jsx
@@ -0,0 +1,46 @@
+import { useEffect, useState, useCallback } from "react";
+import { networkService } from "../../../../main";
+import { useSelector } from "react-redux";
+import { createToast } from "../../../../Utils/toastUtils";
+
+const useDUStatusPageFetch = (isCreate = false, url) => {
+ const [isLoading, setIsLoading] = useState(true);
+ const [networkError, setNetworkError] = useState(false);
+ const [statusPage, setStatusPage] = useState(undefined);
+ const { authToken } = useSelector((state) => state.auth);
+ const fetchStatusPage = useCallback(async () => {
+ try {
+ const response = await networkService.getStatusPageByUrl({
+ authToken,
+ url,
+ type: "distributed",
+ });
+
+ if (!response?.data?.data) return;
+
+ const statusPage = response.data.data;
+ setStatusPage(statusPage);
+ } catch (error) {
+ // If there is a 404, status page is not found
+ if (error?.response?.status === 404) {
+ setStatusPage(undefined);
+ return;
+ }
+ createToast({ body: error.message });
+ setNetworkError(true);
+ } finally {
+ setIsLoading(false);
+ }
+ }, [authToken, url]);
+
+ useEffect(() => {
+ if (isCreate === true) {
+ return;
+ }
+ fetchStatusPage();
+ }, [isCreate, fetchStatusPage]);
+
+ return [statusPage, isLoading, networkError, fetchStatusPage];
+};
+
+export { useDUStatusPageFetch };
diff --git a/src/Pages/DistributedUptimeStatus/Create/index.jsx b/src/Pages/DistributedUptimeStatus/Create/index.jsx
index 3aa16a074..09673b53e 100644
--- a/src/Pages/DistributedUptimeStatus/Create/index.jsx
+++ b/src/Pages/DistributedUptimeStatus/Create/index.jsx
@@ -7,26 +7,34 @@ import VisuallyHiddenInput from "./Components/VisuallyHiddenInput";
import Image from "../../../Components/Image";
import LogoPlaceholder from "../../../assets/Images/logo_placeholder.svg";
import Breadcrumbs from "../../../Components/Breadcrumbs";
+import Search from "../../../Components/Inputs/Search";
+import MonitorList from "../../StatusPage/Create/Components/MonitorList";
// Utils
import { useTheme } from "@emotion/react";
import { useState, useEffect } from "react";
import { useParams } from "react-router-dom";
-import { useStatusPageFetch } from "../../StatusPage/Status/Hooks/useStatusPageFetch";
+import { useDUStatusPageFetch } from "./Hooks/useDUStatusPageFetch";
import { useCreateStatusPage } from "../../StatusPage/Create/Hooks/useCreateStatusPage";
import { statusPageValidation } from "../../../Validation/validation";
import { buildErrors } from "../../../Validation/error";
import { createToast } from "../../../Utils/toastUtils";
import { useNavigate } from "react-router-dom";
+import { useMonitorsFetch } from "../../StatusPage/Create/Hooks/useMonitorsFetch";
const CreateStatus = () => {
const theme = useTheme();
const { monitorId, url } = useParams();
const navigate = useNavigate();
const isCreate = typeof url === "undefined";
+
const [createStatusPage, isLoading, networkError] = useCreateStatusPage(isCreate);
- const [statusPage, statusPageMonitors, statusPageIsLoading, statusPageNetworkError] =
- useStatusPageFetch(isCreate, url);
+ const [statusPage, statusPageIsLoading, statusPageNetworkError] = useDUStatusPageFetch(
+ isCreate,
+ url
+ );
+
+ const [monitors, monitorsIsLoading, monitorsNetworkError] = useMonitorsFetch();
const BREADCRUMBS = [
{ name: "distributed uptime", path: "/distributed-uptime" },
@@ -43,6 +51,8 @@ const CreateStatus = () => {
monitors: [monitorId],
});
const [errors, setErrors] = useState({});
+ const [search, setSearch] = useState("");
+ const [selectedMonitors, setSelectedMonitors] = useState([]);
const handleFormChange = (e) => {
const { name, value, checked, type } = e.target;
@@ -62,6 +72,16 @@ const CreateStatus = () => {
setForm({ ...form, [name]: value });
};
+ const handleMonitorsChange = (selectedMonitors) => {
+ handleFormChange({
+ target: {
+ name: "subMonitors",
+ value: selectedMonitors.map((monitor) => monitor._id),
+ },
+ });
+ setSelectedMonitors(selectedMonitors);
+ };
+
const handleImageUpload = (e) => {
const img = e.target?.files?.[0];
setForm((prev) => ({
@@ -73,19 +93,20 @@ const CreateStatus = () => {
let logoToSubmit = undefined;
// Handle image
- if (typeof form.logo !== "undefined") {
+ if (typeof form.logo !== "undefined" && typeof form.logo.src === "undefined") {
logoToSubmit = {
src: URL.createObjectURL(form.logo),
name: form.logo.name,
type: form.logo.type,
size: form.logo.size,
};
+ } else if (typeof form.logo !== "undefined") {
+ logoToSubmit = form.logo;
}
const formToSubmit = { ...form };
if (typeof logoToSubmit !== "undefined") {
formToSubmit.logo = logoToSubmit;
}
-
// Validate
const { error } = statusPageValidation.validate(formToSubmit, { abortEarly: false });
if (typeof error === "undefined") {
@@ -127,12 +148,20 @@ const CreateStatus = () => {
companyName: statusPage?.companyName,
isPublished: statusPage?.isPublished,
timezone: statusPage?.timezone,
- monitors: statusPageMonitors.map((monitor) => monitor._id),
+ monitors: statusPage?.monitors,
+ subMonitors: statusPage?.subMonitors.map((monitor) => monitor._id),
color: statusPage?.color,
logo: newLogo,
};
});
- }, [isCreate, statusPage, statusPageMonitors]);
+ setSelectedMonitors(statusPage?.subMonitors);
+ }, [isCreate, statusPage]);
+
+ const imgSrc = form?.logo?.src
+ ? form.logo.src
+ : form.logo
+ ? URL.createObjectURL(form.logo)
+ : undefined;
return (
@@ -212,7 +241,7 @@ const CreateStatus = () => {
alignItems="center"
>
{
+
+
+ Standard Monitors
+
+ Attach standard monitors to your status page.
+
+
+
+
+
+
+
{
+ const theme = useTheme();
+ const { determineState } = useUtils();
+ return (
+ <>
+ {monitors?.map((monitor) => {
+ const status = determineState(monitor);
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+ })}
+ >
+ );
+};
+
+MonitorsList.propTypes = {
+ monitors: PropTypes.array.isRequired,
+};
+
+export default MonitorsList;
diff --git a/src/Pages/DistributedUptimeStatus/Status/Components/TimeframeHeader/index.jsx b/src/Pages/DistributedUptimeStatus/Status/Components/TimeframeHeader/index.jsx
new file mode 100644
index 000000000..011f470aa
--- /dev/null
+++ b/src/Pages/DistributedUptimeStatus/Status/Components/TimeframeHeader/index.jsx
@@ -0,0 +1,39 @@
+import { Stack, Button, ButtonGroup } from "@mui/material";
+import { RowContainer } from "../../../../../Components/StandardContainer";
+import { useTheme } from "@emotion/react";
+
+const TimeFrameHeader = ({ timeFrame, setTimeFrame }) => {
+ const theme = useTheme();
+ return (
+
+
+
+
+
+
+
+ );
+};
+
+export default TimeFrameHeader;
diff --git a/src/Pages/DistributedUptimeStatus/Status/Hooks/useStatusPageFetchByUrl.jsx b/src/Pages/DistributedUptimeStatus/Status/Hooks/useStatusPageFetchByUrl.jsx
index 0ca6346a0..61c4f1b65 100644
--- a/src/Pages/DistributedUptimeStatus/Status/Hooks/useStatusPageFetchByUrl.jsx
+++ b/src/Pages/DistributedUptimeStatus/Status/Hooks/useStatusPageFetchByUrl.jsx
@@ -2,25 +2,41 @@ import { useState, useEffect } from "react";
import { networkService } from "../../../../main";
import { createToast } from "../../../../Utils/toastUtils";
import { useSelector } from "react-redux";
+import { useTheme } from "@emotion/react";
+import { useMonitorUtils } from "../../../../Hooks/useMonitorUtils";
-const useStatusPageFetchByUrl = ({ url }) => {
+const useStatusPageFetchByUrl = ({ url, timeFrame }) => {
const [isLoading, setIsLoading] = useState(true);
const [networkError, setNetworkError] = useState(false);
const [statusPage, setStatusPage] = useState(undefined);
const [monitorId, setMonitorId] = useState(undefined);
const [isPublished, setIsPublished] = useState(false);
const { authToken } = useSelector((state) => state.auth);
+ const theme = useTheme();
+ const { getMonitorWithPercentage } = useMonitorUtils();
+
useEffect(() => {
const fetchStatusPageByUrl = async () => {
try {
- const response = await networkService.getStatusPageByUrl({
+ const response = await networkService.getDistributedStatusPageByUrl({
authToken,
url,
type: "distributed",
+ timeFrame,
});
if (!response?.data?.data) return;
const statusPage = response.data.data;
- setStatusPage(statusPage);
+
+ const monitorsWithPercentage = statusPage?.subMonitors.map((monitor) =>
+ getMonitorWithPercentage(monitor, theme)
+ );
+
+ const statusPageWithSubmonitorPercentages = {
+ ...statusPage,
+ subMonitors: monitorsWithPercentage,
+ };
+ setStatusPage(statusPageWithSubmonitorPercentages);
+
setMonitorId(statusPage?.monitors[0]);
setIsPublished(statusPage?.isPublished);
} catch (error) {
@@ -33,7 +49,7 @@ const useStatusPageFetchByUrl = ({ url }) => {
}
};
fetchStatusPageByUrl();
- }, [authToken, url]);
+ }, [authToken, url, getMonitorWithPercentage, theme, timeFrame]);
return [isLoading, networkError, statusPage, monitorId, isPublished];
};
diff --git a/src/Pages/DistributedUptimeStatus/Status/index.jsx b/src/Pages/DistributedUptimeStatus/Status/index.jsx
index fc3043eda..68493ed81 100644
--- a/src/Pages/DistributedUptimeStatus/Status/index.jsx
+++ b/src/Pages/DistributedUptimeStatus/Status/index.jsx
@@ -16,6 +16,7 @@ import UptLogo from "../../../assets/icons/upt_logo.png";
import PeopleAltOutlinedIcon from "@mui/icons-material/PeopleAltOutlined";
import InfoBox from "../../../Components/InfoBox";
import StatusHeader from "../../DistributedUptime/Details/Components/StatusHeader";
+import MonitorsList from "./Components/MonitorsList";
//Utils
import { useTheme } from "@mui/material/styles";
@@ -27,6 +28,8 @@ import { useStatusPageDelete } from "../../StatusPage/Status/Hooks/useStatusPage
import { useNavigate } from "react-router-dom";
import { useLocation } from "react-router-dom";
+import TimeFrameHeader from "./Components/TimeframeHeader";
+
import SubHeader from "../../DistributedUptime/Details/Components/Subheader";
const DistributedUptimeStatus = () => {
const { url } = useParams();
@@ -37,9 +40,11 @@ const DistributedUptimeStatus = () => {
// Local State
const [dateRange, setDateRange] = useState("day");
const [isDeleteOpen, setIsDeleteOpen] = useState(false);
+ const [timeFrame, setTimeFrame] = useState(30);
// Utils
const theme = useTheme();
const navigate = useNavigate();
+
const [
statusPageIsLoading,
statusPageNetworkError,
@@ -48,6 +53,7 @@ const DistributedUptimeStatus = () => {
isPublished,
] = useStatusPageFetchByUrl({
url,
+ timeFrame,
});
const [isLoading, networkError, connectionStatus, monitor, lastUpdateTrigger] =
@@ -56,6 +62,7 @@ const DistributedUptimeStatus = () => {
const [deleteStatusPage, isDeleting] = useStatusPageDelete(() => {
navigate("/distributed-uptime");
}, url);
+
// Constants
const BREADCRUMBS = [
{ name: "Distributed Uptime", path: "/distributed-uptime" },
@@ -223,6 +230,14 @@ const DistributedUptimeStatus = () => {
monitor={monitor}
lastUpdateTrigger={lastUpdateTrigger}
/>
+
+