diff --git a/client/src/Components/Inputs/Select/index.jsx b/client/src/Components/Inputs/Select/index.jsx index a132b5d77..c5c488f91 100644 --- a/client/src/Components/Inputs/Select/index.jsx +++ b/client/src/Components/Inputs/Select/index.jsx @@ -50,6 +50,7 @@ const Select = ({ onChange, onBlur, sx, + error = false, name = "", labelControlSpacing = 6, maxWidth, @@ -93,6 +94,7 @@ const Select = ({ onChange={onChange} onBlur={onBlur} displayEmpty + error={error} name={name} inputProps={{ id: id }} IconComponent={KeyboardArrowDownIcon} @@ -172,6 +174,7 @@ Select.propTypes = { label: PropTypes.string, placeholder: PropTypes.string, isHidden: PropTypes.bool, + error: PropTypes.bool, value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]) .isRequired, items: PropTypes.arrayOf( diff --git a/client/src/Hooks/monitorHooks.js b/client/src/Hooks/monitorHooks.js index 616a37877..970b5d97f 100644 --- a/client/src/Hooks/monitorHooks.js +++ b/client/src/Hooks/monitorHooks.js @@ -202,6 +202,25 @@ const useFetchStatsByMonitorId = ({ return [monitor, audits, isLoading, networkError]; }; +const useFetchMonitorGames = ({ setGames, updateTrigger }) => { + const [isLoading, setIsLoading] = useState(true); + useEffect(() => { + const fetchGames = async () => { + try { + setIsLoading(true); + const res = await networkService.getMonitorGames(); + setGames(res.data.data); + } catch (error) { + createToast({ body: error.message }); + } finally { + setIsLoading(false); + } + }; + fetchGames(); + }, [setGames, updateTrigger]); + return [isLoading]; +}; + const useFetchMonitorById = ({ monitorId, setMonitor, updateTrigger }) => { const [isLoading, setIsLoading] = useState(true); useEffect(() => { @@ -357,7 +376,12 @@ const useUpdateMonitor = () => { expectedValue: monitor.expectedValue, ignoreTlsErrors: monitor.ignoreTlsErrors, jsonPath: monitor.jsonPath, - ...(monitor.type === "port" && { port: monitor.port }), + ...((monitor.type === "port" || monitor.type === "game") && { + port: monitor.port, + }), + ...(monitor.type == "game" && { + gameId: monitor.gameId, + }), ...(monitor.type === "hardware" && { thresholds: monitor.thresholds, secret: monitor.secret, @@ -530,4 +554,5 @@ export { useDeleteMonitorStats, useCreateBulkMonitors, useExportMonitors, + useFetchMonitorGames, }; diff --git a/client/src/Pages/Uptime/Create/index.jsx b/client/src/Pages/Uptime/Create/index.jsx index 2d918cb9f..f821ea9e4 100644 --- a/client/src/Pages/Uptime/Create/index.jsx +++ b/client/src/Pages/Uptime/Create/index.jsx @@ -41,6 +41,7 @@ import { useUpdateMonitor, usePauseMonitor, useFetchMonitorById, + useFetchMonitorGames, } from "../../../Hooks/monitorHooks"; /** @@ -81,6 +82,7 @@ const UptimeCreate = ({ isClone = false }) => { const [isOpen, setIsOpen] = useState(false); const [useAdvancedMatching, setUseAdvancedMatching] = useState(false); const [updateTrigger, setUpdateTrigger] = useState(false); + const [games, setGames] = useState({}); const triggerUpdate = () => { setUpdateTrigger(!updateTrigger); }; @@ -89,12 +91,22 @@ const UptimeCreate = ({ isClone = false }) => { const [notifications, notificationsAreLoading, notificationsError] = useGetNotificationsByTeamId(); const { determineState, statusColor } = useMonitorUtils(); - // Network - const [isLoading] = useFetchMonitorById({ + // Fetch monitor details + const [isFetchingMonitor] = useFetchMonitorById({ monitorId, setMonitor, updateTrigger: true, }); + + // Fetch games + const [isFetchingGames] = useFetchMonitorGames({ + setGames, + triggerUpdate: true, + }); + + // Combine the loading states + const isLoading = isFetchingMonitor || isFetchingGames; + const [createMonitor, isCreating] = useCreateMonitor(); const [pauseMonitor, isPausing] = usePauseMonitor({}); const [deleteMonitor, isDeleting] = useDeleteMonitor(); @@ -113,6 +125,12 @@ const UptimeCreate = ({ isClone = false }) => { { _id: 4, name: t("time.fourMinutes") }, { _id: 5, name: t("time.fiveMinutes") }, ]; + + const GAMELIST = Object.entries(games).map(([key, value]) => ({ + _id: key, + name: value.name, + })); + const CRUMBS = [ { name: "uptime", path: "/uptime" }, ...(isCreate @@ -153,6 +171,11 @@ const UptimeCreate = ({ isClone = false }) => { placeholder: t("monitorType.port.placeholder"), namePlaceholder: t("monitorType.port.namePlaceholder"), }, + game: { + label: t("monitorType.game.label"), + placeholder: t("monitorType.game.placeholder"), + namePlaceholder: t("monitorType.game.namePlaceholder"), + }, }; // Handlers @@ -169,12 +192,14 @@ const UptimeCreate = ({ isClone = false }) => { : monitor.url, name: monitor.name || monitor.url.substring(0, 50), type: monitor.type, - port: monitor.type === "port" ? monitor.port : undefined, + port: + monitor.type === "port" || monitor.type === "game" ? monitor.port : undefined, interval: monitor.interval, matchMethod: monitor.matchMethod, expectedValue: monitor.expectedValue, jsonPath: monitor.jsonPath, ignoreTlsErrors: monitor.ignoreTlsErrors, + gameId: monitor.gameId || undefined, }; } else { form = { @@ -188,8 +213,10 @@ const UptimeCreate = ({ isClone = false }) => { interval: monitor.interval, teamId: monitor.teamId, userId: monitor.userId, - port: monitor.type === "port" ? monitor.port : undefined, + port: + monitor.type === "port" || monitor.type === "game" ? monitor.port : undefined, ignoreTlsErrors: monitor.ignoreTlsErrors, + gameId: monitor.gameId || undefined, }; } if (!useAdvancedMatching) { @@ -243,6 +270,11 @@ const UptimeCreate = ({ isClone = false }) => { setMonitor((prev) => ({ ...prev, [name]: value })); + if (name === "type") { + setErrors({}); + return; + } + const { error } = monitorValidation.validate( { type: monitor.type, [name]: value }, { abortEarly: false } @@ -250,7 +282,9 @@ const UptimeCreate = ({ isClone = false }) => { setErrors((prev) => ({ ...prev, - ...(error ? { [name]: error.details[0].message } : { [name]: undefined }), + ...(error && error.details[0].path[0] === name + ? { [name]: error.details[0].message } + : { [name]: undefined }), })); }; @@ -486,6 +520,15 @@ const UptimeCreate = ({ isClone = false }) => { checked={monitor.type === "port"} onChange={onChange} /> + {errors["type"] ? ( { onChange={onChange} error={errors["port"] ? true : false} helperText={errors["port"]} - hidden={monitor.type !== "port"} + hidden={monitor.type !== "port" && monitor.type !== "game"} /> + {monitor.type === "game" && ( +