diff --git a/.gitignore b/.gitignore index f411b5c8b..81e5668b6 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ mongo node_modules/ docs/architecture -docs/reviews \ No newline at end of file +docs/reviews +docs/todo \ No newline at end of file diff --git a/client/src/Hooks/useSettingsForm.ts b/client/src/Hooks/useSettingsForm.ts index ce04a81e2..3af8c229e 100644 --- a/client/src/Hooks/useSettingsForm.ts +++ b/client/src/Hooks/useSettingsForm.ts @@ -22,10 +22,22 @@ export const useSettingsForm = ({ data = null }: UseSettingsFormOptions = {}) => systemEmailTLSServername: data?.systemEmailTLSServername || "", systemEmailPort: data?.systemEmailPort, globalThresholds: { - cpu: data?.globalThresholds?.cpu ?? 0, - memory: data?.globalThresholds?.memory ?? 0, - disk: data?.globalThresholds?.disk ?? 0, - temperature: data?.globalThresholds?.temperature ?? 0, + cpu: + data?.globalThresholds?.cpu && data.globalThresholds.cpu >= 1 + ? data.globalThresholds.cpu + : 80, + memory: + data?.globalThresholds?.memory && data.globalThresholds.memory >= 1 + ? data.globalThresholds.memory + : 80, + disk: + data?.globalThresholds?.disk && data.globalThresholds.disk >= 1 + ? data.globalThresholds.disk + : 80, + temperature: + data?.globalThresholds?.temperature && data.globalThresholds.temperature >= 1 + ? data.globalThresholds.temperature + : 80, }, checkTTL: data?.checkTTL ?? 30, pagespeedApiKey: "", diff --git a/client/src/Pages/Settings/index.tsx b/client/src/Pages/Settings/index.tsx index 484ab3690..dbeba6096 100644 --- a/client/src/Pages/Settings/index.tsx +++ b/client/src/Pages/Settings/index.tsx @@ -1,6 +1,13 @@ import { BasePage, ConfigBox, TextLink } from "@/Components/design-elements"; import { Autocomplete, Select, Dialog, SwitchComponent } from "@/Components/inputs"; -import { Stack, useTheme, MenuItem, Link, type SelectChangeEvent } from "@mui/material"; +import { + Stack, + useTheme, + MenuItem, + Link, + Alert, + type SelectChangeEvent, +} from "@mui/material"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; @@ -14,7 +21,7 @@ import { useIsAdmin } from "@/Hooks/useIsAdmin.js"; import type { SettingsFormData } from "@/Validation/settings"; import { useState } from "react"; import { Controller } from "react-hook-form"; -import { TextField, Button, FieldLabel } from "@/Components/inputs"; +import { TextField, Button, FieldLabel, SliderWithLabel } from "@/Components/inputs"; import { Box, Typography } from "@mui/material"; import { useDelete } from "@/Hooks/UseApi"; @@ -257,8 +264,7 @@ export const SettingsPage = () => { (acc as any)[typedKey] = Object.entries(value).reduce( (nested, [nestedKey, nestedValue]) => ({ ...nested, - [nestedKey]: - nestedValue === undefined || nestedValue === 0 ? "" : nestedValue, + [nestedKey]: nestedValue === undefined ? "" : nestedValue, }), {} ); @@ -465,24 +471,16 @@ export const SettingsPage = () => { name="globalThresholds.cpu" control={form.control} defaultValue={defaults.globalThresholds?.cpu} - render={({ field, fieldState }) => ( - ( + { - const val = e.target.value; - field.onChange(val === "" ? 0 : Number(val)); - }} fieldLabel={t("pages.settings.form.thresholds.option.cpu.label")} - type="number" - inputProps={{ min: 0 }} - placeholder={t( - "pages.settings.form.thresholds.option.cpu.placeholder" - )} - error={!!fieldState.error} - helperText={fieldState.error?.message} + min={1} + max={100} + sliderMaxWidth={{ xs: "100%", md: "50%" }} + value={field.value || 1} + onChange={(_, value) => field.onChange(value)} + valueLabelDisplay="auto" /> )} /> @@ -490,24 +488,16 @@ export const SettingsPage = () => { name="globalThresholds.memory" control={form.control} defaultValue={defaults.globalThresholds?.memory} - render={({ field, fieldState }) => ( - ( + { - const val = e.target.value; - field.onChange(val === "" ? 0 : Number(val)); - }} fieldLabel={t("pages.settings.form.thresholds.option.memory.label")} - type="number" - inputProps={{ min: 0 }} - placeholder={t( - "pages.settings.form.thresholds.option.memory.placeholder" - )} - error={!!fieldState.error} - helperText={fieldState.error?.message} + min={1} + max={100} + sliderMaxWidth={{ xs: "100%", md: "50%" }} + value={field.value || 1} + onChange={(_, value) => field.onChange(value)} + valueLabelDisplay="auto" /> )} /> @@ -515,24 +505,16 @@ export const SettingsPage = () => { name="globalThresholds.disk" control={form.control} defaultValue={defaults.globalThresholds?.disk} - render={({ field, fieldState }) => ( - ( + { - const val = e.target.value; - field.onChange(val === "" ? 0 : Number(val)); - }} fieldLabel={t("pages.settings.form.thresholds.option.disk.label")} - type="number" - inputProps={{ min: 0 }} - placeholder={t( - "pages.settings.form.thresholds.option.disk.placeholder" - )} - error={!!fieldState.error} - helperText={fieldState.error?.message} + min={1} + max={100} + sliderMaxWidth={{ xs: "100%", md: "50%" }} + value={field.value || 1} + onChange={(_, value) => field.onChange(value)} + valueLabelDisplay="auto" /> )} /> @@ -540,26 +522,18 @@ export const SettingsPage = () => { name="globalThresholds.temperature" control={form.control} defaultValue={defaults.globalThresholds?.temperature} - render={({ field, fieldState }) => ( - ( + { - const val = e.target.value; - field.onChange(val === "" ? 0 : Number(val)); - }} fieldLabel={t( "pages.settings.form.thresholds.option.temperature.label" )} - type="number" - inputProps={{ min: 0 }} - placeholder={t( - "pages.settings.form.thresholds.option.temperature.placeholder" - )} - error={!!fieldState.error} - helperText={fieldState.error?.message} + min={1} + max={150} + sliderMaxWidth={{ xs: "100%", md: "50%" }} + value={field.value || 1} + onChange={(_, value) => field.onChange(value)} + valueLabelDisplay="auto" /> )} /> @@ -997,6 +971,39 @@ export const SettingsPage = () => { zIndex: 1000, }} > + {/* Validation Error Display */} + {Object.keys(form.formState.errors).length > 0 && ( + + + {t("pages.settings.form.validation.errorMessage")} + + + {Object.entries(form.formState.errors).map(([field, error]) => { + const message = + typeof error === "object" && error?.message + ? error.message + : "Invalid value"; + return ( +
  • + + {field}: {message} + +
  • + ); + })} +
    +
    + )} +