diff --git a/Client/src/Components/Inputs/Search/index.jsx b/Client/src/Components/Inputs/Search/index.jsx index 9a1995726..a2f35cac7 100644 --- a/Client/src/Components/Inputs/Search/index.jsx +++ b/Client/src/Components/Inputs/Search/index.jsx @@ -54,6 +54,8 @@ const Search = ({ isAdorned = true, error, disabled, + startAdornment, + endAdornment }) => { const theme = useTheme(); @@ -66,15 +68,15 @@ const Search = ({ onInputChange={(_, newValue) => { handleInputChange(newValue); }} - onChange={(_, newValue) => { - handleChange && handleChange(newValue); + onChange={(e, newValue) => { + handleChange && handleChange(e, newValue); }} fullWidth freeSolo disabled={disabled} disableClearable options={options} - getOptionLabel={(option) => option[filteredBy]} + getOptionLabel={(option) => option[filteredBy]??""} renderInput={(params) => ( }), + slotProps={{ + input: { + ...params.InputProps, + ...(isAdorned && { startAdornment: }), + ...(startAdornment && { startAdornment: startAdornment }), + ...(endAdornment && { endAdornment: endAdornment }), + }, }} sx={{ "& fieldset": { @@ -187,7 +193,7 @@ Search.propTypes = { options: PropTypes.array.isRequired, filteredBy: PropTypes.string.isRequired, secondaryLabel: PropTypes.string, - value: PropTypes.array, + value: PropTypes.oneOfType([PropTypes.array, PropTypes.object]), inputValue: PropTypes.string.isRequired, handleInputChange: PropTypes.func.isRequired, handleChange: PropTypes.func, @@ -195,6 +201,8 @@ Search.propTypes = { sx: PropTypes.object, error: PropTypes.string, disabled: PropTypes.bool, + startAdornment: PropTypes.object, + endAdornment: PropTypes.object }; export default Search; diff --git a/Client/src/Components/Inputs/TextInput/Adornments/index.jsx b/Client/src/Components/Inputs/TextInput/Adornments/index.jsx index 5c309b3a6..4d7b09d0f 100644 --- a/Client/src/Components/Inputs/TextInput/Adornments/index.jsx +++ b/Client/src/Components/Inputs/TextInput/Adornments/index.jsx @@ -3,8 +3,8 @@ import { useTheme } from "@mui/material/styles"; import PropTypes from "prop-types"; import VisibilityOff from "@mui/icons-material/VisibilityOff"; import Visibility from "@mui/icons-material/Visibility"; -import Docs from "../../../../assets/icons/docs.svg?react"; -import DeleteIcon from "../../../../assets/icons/trash-bin.svg?react"; +import ReorderRoundedIcon from '@mui/icons-material/ReorderRounded'; +import DeleteIcon from "../../../../assets/icons/trash-bin.svg?react"; export const HttpAdornment = ({ https, prefix }) => { const theme = useTheme(); @@ -70,7 +70,7 @@ PasswordEndAdornment.propTypes = { export const ServerStartAdornment = () => { return ( - + ); }; diff --git a/Client/src/Components/TabPanels/Status/Card/Server/index.jsx b/Client/src/Components/TabPanels/Status/Card/Server/index.jsx index ed874ec01..c21f6963f 100644 --- a/Client/src/Components/TabPanels/Status/Card/Server/index.jsx +++ b/Client/src/Components/TabPanels/Status/Card/Server/index.jsx @@ -1,11 +1,21 @@ -import TextInput from "../../../../Inputs/TextInput"; import { ServerStartAdornment, ServerEndAdornment } from "../../../../Inputs/TextInput/Adornments"; +import Search from "../../../../Inputs/Search"; +import {useState} from "react" - -const Server = ({ id, removeItem, value, onChange }) => { +const Server = ({ id, removeItem, onChange, monitors, value, label }) => { + const [search, setSearch] = useState(""); + const handleSearch = (val) => { + setSearch(val); + }; return ( - } endAdornment={ { removeItem={removeItem} /> } - id={id} - value= {value} - onChange ={ onChange } - > + handleInputChange={handleSearch} + handleChange={onChange} + /> ); }; diff --git a/Client/src/Components/TabPanels/Status/Card/index.jsx b/Client/src/Components/TabPanels/Status/Card/index.jsx index a16c3d1ee..35c7cf2b0 100644 --- a/Client/src/Components/TabPanels/Status/Card/index.jsx +++ b/Client/src/Components/TabPanels/Status/Card/index.jsx @@ -13,12 +13,13 @@ const CustomCard = React.forwardRef((props, ref) => ( removeItem={props.removeCard} value={props.value} onChange={props.onChange} + monitors={props.monitors} /> )); -const MyCard = ({ id, index, moveCard, removeCard, value, onChange }) => { +const MyCard = ({ id, index, moveCard, removeCard, value, onChange, monitors }) => { const ref = useRef(null); const [, drop] = useDrop({ accept: ItemTypes.CARD, @@ -80,6 +81,7 @@ const MyCard = ({ id, index, moveCard, removeCard, value, onChange }) => { removeCard={removeCard} value={value} onChange={onChange} + monitors={monitors} /> ); }; diff --git a/Client/src/Components/TabPanels/Status/ContentPanel.jsx b/Client/src/Components/TabPanels/Status/ContentPanel.jsx index 3d5fef0e3..39c18c9ba 100644 --- a/Client/src/Components/TabPanels/Status/ContentPanel.jsx +++ b/Client/src/Components/TabPanels/Status/ContentPanel.jsx @@ -1,4 +1,4 @@ -import { useState, useCallback, useContext } from "react"; +import { useState, useCallback, useContext, useEffect } from "react"; import { Button, Box, Stack, Typography } from "@mui/material"; import ConfigBox from "../../../Components/ConfigBox"; import { useTheme } from "@emotion/react"; @@ -6,11 +6,38 @@ import TabPanel from "@mui/lab/TabPanel"; import Card from "./Card"; import update from "immutability-helper"; import { StatusFormContext } from "../../../Pages/Status/CreateStatusContext"; +import { useSelector } from "react-redux"; +import { logger } from "../../../Utils/Logger" +import { createToast } from "../../../Utils/toastUtils"; +import { networkService } from "../../../main"; const ContentPanel = () => { const theme = useTheme(); const { form, setForm, errors, setErrors } = useContext(StatusFormContext); - const [cards, setCards] = useState(form.monitors); + const [cards, setCards] = useState(form.monitors.length>0? form.monitors.map((m, idx)=>({id: idx, val: m})): []); + const {user, authToken } = useSelector((state) => state.auth); + const [monitors, setMonitors] = useState([]); + + useEffect(() => { + const fetchMonitors = async () => { + try { + const response = await networkService.getMonitorsByTeamId({ + authToken: authToken, + teamId: user.teamId, + limit: -1, + types: ["http", "ping", "pagespeed", "hardware"], + }); + if(response.data.data.monitors.length==0){ + setErrors({monitors: "Please config monitors to setup status page"}) + } + setMonitors(response.data.data.monitors); + } catch (error) { + createToast({ body: "Failed to fetch monitors data" }); + logger.error("Failed to fetch monitors", error); + } + }; + fetchMonitors(); + }, [user, authToken]); const moveCard = useCallback( (dragIndex, hoverIndex) => { @@ -27,31 +54,40 @@ const ContentPanel = () => { [cards] ); - const handleCardChange = (event) => { + const handleCardChange = (event, val) => { event.preventDefault(); - const { value, id } = event.target; - let idx = cards.findIndex((a) => a.id == id); + const { id } = event.target; + let idx = cards.findIndex((a) => { + let found = false; + let optionIdx = id.indexOf("-option"); + if (optionIdx !== -1) found = a.id == id.substr(0, optionIdx); + else found = a.id == id; + return found; + }); let newCards; if (idx >= 0) { let x = JSON.parse(JSON.stringify(cards[idx])); - x.url = value; + x.val = val; newCards = update(cards, { $splice: [[idx, 1, x]] }); setCards(newCards); } else { - newCards = update(cards, { $push: [{ id: id, url: value }] }); + newCards = update(cards, { $push: [{ id: id, val: val }] }); setCards(newCards); } - setForm({ ...form, monitors: newCards }); + setForm({ ...form, monitors: newCards.map(c=>c.val) }); }; const handleAddNew = () => { + if (cards.length === monitors.length) return; const newCards = [...cards, { id: "" + Math.random() }]; setCards(newCards); - setForm({ ...form, monitors: newCards }); }; const removeCard = (id) => { const newCards = cards.filter((c) => c?.id != id); setCards(newCards); - setForm({ ...form, monitors: newCards }); + setForm({ + ...form, + monitors: newCards.filter((c) => c.val !== undefined).map((c) => c.val), + }); }; return ( @@ -107,7 +143,12 @@ const ContentPanel = () => { {" "} Servers list{" "} - + {cards.length > 0 && ( { direction="column" gap={theme.spacing(2)} > - {cards.map((card, idx) => ( - - ))} + {cards.map((card, idx) => { + return ( + + ); + })} )} {errors["monitors"] && ( diff --git a/Client/src/Pages/Maintenance/CreateMaintenance/index.jsx b/Client/src/Pages/Maintenance/CreateMaintenance/index.jsx index c832d463a..e38c3c058 100644 --- a/Client/src/Pages/Maintenance/CreateMaintenance/index.jsx +++ b/Client/src/Pages/Maintenance/CreateMaintenance/index.jsx @@ -181,7 +181,7 @@ const CreateMaintenance = () => { setSearch(value); }; - const handleSelectMonitors = (monitors) => { + const handleSelectMonitors = (_, monitors) => { setForm({ ...form, monitors }); const { error } = maintenanceWindowValidation.validate( { monitors }, diff --git a/Client/src/Pages/Status/CreateStatus/index.jsx b/Client/src/Pages/Status/CreateStatus/index.jsx index 8f81a3c31..1f112c19d 100644 --- a/Client/src/Pages/Status/CreateStatus/index.jsx +++ b/Client/src/Pages/Status/CreateStatus/index.jsx @@ -22,7 +22,7 @@ import { networkService } from "../../../main"; const CreateStatus = ({ initForm }) => { const theme = useTheme(); const mode = useSelector((state) => state.ui.mode); - const {authToken} = useSelector((state) => state.auth); + const { authToken } = useSelector((state) => state.auth); const {apiBaseUrl} = useSelector((state) => state.settings); const [tabIdx, setTabIdx] = useState(0); const [errors, setErrors] = useState({}); @@ -46,7 +46,6 @@ import { networkService } from "../../../main"; // showBarcode: false, } ); - const setActiveTabOnErrors = () => { let newIdx = -1; Object.keys(errors).map((id) => { @@ -72,7 +71,6 @@ import { networkService } from "../../../main"; delete form.logo; //let localData = {...form, monitors: form.monitors.map(m=>m.url)} let localData = {...form, monitors: form.monitors, theme: mode} - if (hasValidationErrors(localData, publicPageGeneralSettingsValidation, setErrors)) { setActiveTabOnErrors(); return; @@ -85,7 +83,8 @@ import { networkService } from "../../../main"; // if (error) return; //form.logo = { ...logo, size: formatBytes(logo?.size) }; - let config = { authToken: authToken, url: apiBaseUrl, data: localData }; + localData = {...form, monitors: form.monitors.map(m=>m.url),theme: mode} + let config = { authToken: authToken, url: "status-page", data: localData }; try{ const res = await networkService.createStatusPage(config) if (!res.status === 200) { diff --git a/Client/src/Pages/Status/index.jsx b/Client/src/Pages/Status/index.jsx index 84ad6e7db..eefcf347d 100644 --- a/Client/src/Pages/Status/index.jsx +++ b/Client/src/Pages/Status/index.jsx @@ -6,6 +6,8 @@ import CreateStatus from "./CreateStatus"; import { useEffect, useState } from "react"; import { networkService } from "../../main"; import { useSelector } from "react-redux"; +import { createToast } from "../../Utils/toastUtils" +import { logger } from "../../Utils/Logger"; const Status = () => { const theme = useTheme(); @@ -13,13 +15,18 @@ const Status = () => { const {authToken} = useSelector((state) => state.auth); const {apiBaseUrl} = useSelector((state) => state.settings); const [initForm, setInitForm] = useState({}); - useEffect(() => { const getStatusPage = async () => { - let config = { authToken: authToken, url: apiBaseUrl }; - let res = await networkService.getStatusPageByUrl(config); - if(res && res.data) - setInitForm( res.data.data) + let config = { authToken: authToken, url: "status-page" }; + try { + let res = await networkService.getStatusPageByUrl(config); + if(res && res.data) + setInitForm( res.data.data) + }catch (error) { + createToast({ body: "Failed to fetch status page data" }); + logger.error("Failed to fetch status page", error); + } + }; getStatusPage(); }, []);