refactor create status
This commit is contained in:
1 parent
7a3b6a679f
commit
acc692ea97
10 files changed
+410
-11
No files matched your search
@@ -0,0 +1,106 @@
|
||||
// Components
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import ReorderRoundedIcon from "@mui/icons-material/ReorderRounded";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
|
||||
// Utils
|
||||
import { DragDropContext, Droppable, Draggable } from "@hello-pangea/dnd";
|
||||
import { useTheme } from "@emotion/react";
|
||||
const MonitorListItem = ({
|
||||
monitor,
|
||||
innerRef,
|
||||
draggableProps,
|
||||
dragHandleProps,
|
||||
onDelete,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Stack
|
||||
direction={"row"}
|
||||
{...draggableProps}
|
||||
{...dragHandleProps}
|
||||
ref={innerRef}
|
||||
gap={theme.spacing(4)}
|
||||
margin={theme.spacing(4)}
|
||||
padding={theme.spacing(4)}
|
||||
borderRadius={theme.shape.borderRadius}
|
||||
alignItems={"center"}
|
||||
justifyContent={"start"}
|
||||
border={`1px solid ${theme.palette.primary.lowContrast}`}
|
||||
>
|
||||
<ReorderRoundedIcon />
|
||||
<Typography>{monitor.name}</Typography>
|
||||
<DeleteIcon
|
||||
sx={{ marginLeft: "auto" }}
|
||||
onClick={() => {
|
||||
onDelete(monitor);
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const MonitorList = ({ selectedMonitors, setSelectedMonitors }) => {
|
||||
const onDelete = (monitorToDelete) => {
|
||||
const newMonitors = selectedMonitors.filter(
|
||||
(monitor) => monitor._id !== monitorToDelete._id
|
||||
);
|
||||
setSelectedMonitors(newMonitors);
|
||||
};
|
||||
const reorder = (list, startIndex, endIndex) => {
|
||||
const result = Array.from(list);
|
||||
const [removed] = result.splice(startIndex, 1);
|
||||
result.splice(endIndex, 0, removed);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const onDragEnd = (result) => {
|
||||
// dropped outside the list
|
||||
if (!result.destination) {
|
||||
return;
|
||||
}
|
||||
|
||||
const reorderedMonitors = reorder(
|
||||
selectedMonitors,
|
||||
result.source.index,
|
||||
result.destination.index
|
||||
);
|
||||
|
||||
setSelectedMonitors(reorderedMonitors);
|
||||
};
|
||||
|
||||
return (
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<Droppable droppableId="droppable">
|
||||
{(provided, snapshot) => (
|
||||
<Stack
|
||||
{...provided.droppableProps}
|
||||
ref={provided.innerRef}
|
||||
>
|
||||
{selectedMonitors?.map((monitor, index) => (
|
||||
<Draggable
|
||||
key={monitor._id}
|
||||
draggableId={monitor._id}
|
||||
index={index}
|
||||
>
|
||||
{(provided, snapshot) => (
|
||||
<MonitorListItem
|
||||
monitor={monitor}
|
||||
innerRef={provided.innerRef}
|
||||
draggableProps={provided.draggableProps}
|
||||
dragHandleProps={provided.dragHandleProps}
|
||||
onDelete={onDelete}
|
||||
/>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
</Stack>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
);
|
||||
};
|
||||
|
||||
export default MonitorList;
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Stack, Skeleton } from "@mui/material";
|
||||
|
||||
const SkeletonLayout = () => {
|
||||
return (
|
||||
<Stack>
|
||||
<Skeleton
|
||||
variant="rectangular"
|
||||
height={"90vh"}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default SkeletonLayout;
|
||||
@@ -1,12 +1,115 @@
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
// Components
|
||||
import { Stack, Typography, Button } from "@mui/material";
|
||||
import { TabPanel } from "@mui/lab";
|
||||
import ConfigBox from "../../../../../Components/ConfigBox";
|
||||
import MonitorList from "../MonitorList";
|
||||
import Search from "../../../../../Components/Inputs/Search";
|
||||
import Checkbox from "../../../../../Components/Inputs/Checkbox";
|
||||
// Utils
|
||||
import { useState } from "react";
|
||||
import { useTheme } from "@emotion/react";
|
||||
const Content = ({
|
||||
tabValue,
|
||||
form,
|
||||
monitors,
|
||||
handleFormChange,
|
||||
errors,
|
||||
selectedMonitors,
|
||||
setSelectedMonitors,
|
||||
}) => {
|
||||
// Local state
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
// Handlers
|
||||
const handleMonitorsChange = (selectedMonitors) => {
|
||||
handleFormChange({
|
||||
target: { name: "monitors", value: selectedMonitors.map((monitor) => monitor._id) },
|
||||
});
|
||||
setSelectedMonitors(selectedMonitors);
|
||||
};
|
||||
|
||||
// Utils
|
||||
const theme = useTheme();
|
||||
|
||||
const Content = ({ tabValue }) => {
|
||||
return (
|
||||
<TabPanel value={tabValue}>
|
||||
<Stack>
|
||||
<Typography>Content</Typography>
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<ConfigBox>
|
||||
<Stack gap={theme.spacing(6)}>
|
||||
<Typography component="h2">Status page servers</Typography>
|
||||
<Typography component="p">
|
||||
You can add any number of servers that you monitor to your status page. You
|
||||
can also reorder them for the best viewing experience.
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
{/* <Typography
|
||||
component="p"
|
||||
alignSelf={"center"}
|
||||
>
|
||||
{" "}
|
||||
Servers list{" "}
|
||||
</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
>
|
||||
Add new
|
||||
</Button> */}
|
||||
<Search
|
||||
options={monitors}
|
||||
multiple={true}
|
||||
filteredBy="name"
|
||||
value={selectedMonitors}
|
||||
inputValue={search}
|
||||
handleInputChange={setSearch}
|
||||
handleChange={handleMonitorsChange}
|
||||
/>
|
||||
</Stack>
|
||||
<Typography
|
||||
component="span"
|
||||
className="input-error"
|
||||
color={theme.palette.error.main}
|
||||
sx={{
|
||||
opacity: 0.8,
|
||||
}}
|
||||
>
|
||||
{errors["monitors"]}
|
||||
</Typography>
|
||||
<MonitorList
|
||||
selectedMonitors={selectedMonitors}
|
||||
setSelectedMonitors={handleMonitorsChange}
|
||||
/>
|
||||
</Stack>
|
||||
</ConfigBox>{" "}
|
||||
<ConfigBox>
|
||||
<Stack gap={theme.spacing(6)}>
|
||||
<Typography component="h2">Features</Typography>
|
||||
<Typography component="p">Show more details on the status page</Typography>
|
||||
</Stack>
|
||||
<Stack sx={{ margin: theme.spacing(6) }}>
|
||||
<Checkbox
|
||||
id="showCharts"
|
||||
name="showCharts"
|
||||
label={`Show charts`}
|
||||
isChecked={form.showCharts}
|
||||
onChange={handleFormChange}
|
||||
/>
|
||||
<Checkbox
|
||||
id="showUptimePercentage"
|
||||
name="showUptimePercentage"
|
||||
label={`Show uptime percentage`}
|
||||
isChecked={form.showUptimePercentage}
|
||||
onChange={handleFormChange}
|
||||
/>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
</Stack>
|
||||
<Stack gap={theme.spacing(6)}></Stack>
|
||||
</TabPanel>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Stack, Skeleton } from "@mui/material";
|
||||
|
||||
const SkeletonLayout = () => {
|
||||
return (
|
||||
<Stack>
|
||||
<Skeleton
|
||||
variant="rectangular"
|
||||
height={"80vh"}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default SkeletonLayout;
|
||||
@@ -0,0 +1,59 @@
|
||||
import { TabContext, TabList } from "@mui/lab";
|
||||
import { Stack, Tab } from "@mui/material";
|
||||
import Settings from "./Settings";
|
||||
import Content from "./Content";
|
||||
const Tabs = ({
|
||||
form,
|
||||
errors,
|
||||
monitors,
|
||||
selectedMonitors,
|
||||
setSelectedMonitors,
|
||||
handleFormChange,
|
||||
handleImageChange,
|
||||
progress,
|
||||
removeLogo,
|
||||
tab,
|
||||
setTab,
|
||||
TAB_LIST,
|
||||
}) => {
|
||||
return (
|
||||
<TabContext value={TAB_LIST[tab]}>
|
||||
<TabList
|
||||
onChange={(_, tab) => {
|
||||
setTab(TAB_LIST.indexOf(tab));
|
||||
}}
|
||||
>
|
||||
{TAB_LIST.map((tab, idx) => (
|
||||
<Tab
|
||||
key={Math.random()}
|
||||
label={TAB_LIST[idx]}
|
||||
value={TAB_LIST[idx]}
|
||||
/>
|
||||
))}
|
||||
</TabList>
|
||||
{tab === 0 ? (
|
||||
<Settings
|
||||
tabValue={TAB_LIST[0]}
|
||||
form={form}
|
||||
handleFormChange={handleFormChange}
|
||||
handleImageChange={handleImageChange}
|
||||
progress={progress}
|
||||
removeLogo={removeLogo}
|
||||
errors={errors}
|
||||
/>
|
||||
) : (
|
||||
<Content
|
||||
tabValue={TAB_LIST[1]}
|
||||
form={form}
|
||||
monitors={monitors}
|
||||
handleFormChange={handleFormChange}
|
||||
errors={errors}
|
||||
selectedMonitors={selectedMonitors}
|
||||
setSelectedMonitors={setSelectedMonitors}
|
||||
/>
|
||||
)}
|
||||
</TabContext>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tabs;
|
||||
@@ -0,0 +1,28 @@
|
||||
import { useState } from "react";
|
||||
import { networkService } from "../../../../main";
|
||||
import { useSelector } from "react-redux";
|
||||
import { createToast } from "../../../../Utils/toastUtils";
|
||||
|
||||
const useCreateStatusPage = () => {
|
||||
const { authToken, user } = useSelector((state) => state.auth);
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [networkError, setNetworkError] = useState(false);
|
||||
|
||||
const createStatusPage = async ({ form }) => {
|
||||
try {
|
||||
await networkService.createStatusPage({ authToken, user, form });
|
||||
return true;
|
||||
} catch (error) {
|
||||
setNetworkError(true);
|
||||
createToast({ body: error?.response?.data?.msg ?? error.message });
|
||||
return false;
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return [createStatusPage, isLoading, networkError];
|
||||
};
|
||||
|
||||
export { useCreateStatusPage };
|
||||
@@ -0,0 +1,35 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { networkService } from "../../../../main";
|
||||
import { useSelector } from "react-redux";
|
||||
import { createToast } from "../../../../Utils/toastUtils";
|
||||
|
||||
const useMonitorsFetch = () => {
|
||||
const { user, authToken } = useSelector((state) => state.auth);
|
||||
|
||||
const [monitors, setMonitors] = useState(undefined);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [networkError, setNetworkError] = useState(false);
|
||||
useEffect(() => {
|
||||
const fetchMonitors = async () => {
|
||||
try {
|
||||
const response = await networkService.getMonitorsByTeamId({
|
||||
authToken: authToken,
|
||||
teamId: user.teamId,
|
||||
limit: null, // donot return any checks for the monitors
|
||||
types: ["http"], // status page is available only for the uptime type
|
||||
});
|
||||
setMonitors(response.data.data.monitors);
|
||||
} catch (error) {
|
||||
setNetworkError(true);
|
||||
createToast({ body: error.message });
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
fetchMonitors();
|
||||
}, [authToken, user]);
|
||||
|
||||
return [monitors, isLoading, networkError];
|
||||
};
|
||||
|
||||
export { useMonitorsFetch };
|
||||
@@ -1,9 +1,33 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { networkService } from "../../../../main";
|
||||
import { useSelector } from "react-redux";
|
||||
import { createToast } from "../../../../Utils/toastUtils";
|
||||
|
||||
const useStatusPageFetch = () => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [networkError, setNetworkError] = useState(false);
|
||||
const [statusPage, setStatusPage] = useState(undefined);
|
||||
const { authToken } = useSelector((state) => state.auth);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchStatusPage = async () => {
|
||||
try {
|
||||
const res = await networkService.getStatusPage({ authToken });
|
||||
setStatusPage(res.data.data);
|
||||
} 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);
|
||||
}
|
||||
};
|
||||
fetchStatusPage();
|
||||
}, [authToken]);
|
||||
|
||||
return [statusPage, isLoading, networkError];
|
||||
};
|
||||
|
||||
@@ -12,7 +12,6 @@ const PublicStatus = () => {
|
||||
const theme = useTheme();
|
||||
const isAdmin = useIsAdmin();
|
||||
const [statusPage, isLoading, networkError] = useStatusPageFetch();
|
||||
|
||||
if (networkError === true) {
|
||||
return (
|
||||
<GenericFallback>
|
||||
|
||||
@@ -862,9 +862,9 @@ class NetworkService {
|
||||
);
|
||||
}
|
||||
|
||||
async getStatusPageByUrl(config) {
|
||||
const { url, authToken } = config;
|
||||
return this.axiosInstance.get(`/status-page/${url}`, {
|
||||
async getStatusPage(config) {
|
||||
const { authToken } = config;
|
||||
return this.axiosInstance.get(`/status-page`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
"Content-Type": "application/json",
|
||||
@@ -873,11 +873,28 @@ class NetworkService {
|
||||
}
|
||||
|
||||
async createStatusPage(config) {
|
||||
const { url, authToken, data } = config;
|
||||
return this.axiosInstance.post(`/status-page/${url}`, data, {
|
||||
const { authToken, user, form } = config;
|
||||
const fd = new FormData();
|
||||
fd.append("isPublished", form.isPublished);
|
||||
fd.append("companyName", form.companyName);
|
||||
fd.append("url", form.url);
|
||||
fd.append("timezone", form.timezone);
|
||||
fd.append("color", form.color);
|
||||
fd.append("showCharts", form.showCharts);
|
||||
fd.append("showUptimePercentage", form.showUptimePercentage);
|
||||
form.monitors.forEach((monitorId) => {
|
||||
fd.append("monitors[]", monitorId);
|
||||
});
|
||||
if (form?.logo?.src && form?.logo?.src !== "") {
|
||||
const imageResult = await axios.get(form.logo.src, {
|
||||
responseType: "blob",
|
||||
});
|
||||
fd.append("logo", imageResult.data);
|
||||
}
|
||||
|
||||
return this.axiosInstance.post(`/status-page`, fd, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user