diff --git a/client/src/Pages/Infrastructure/Create/index.jsx b/client/src/Pages/Infrastructure/Create/index.jsx
index 0800bb500..fa6ac4fcb 100644
--- a/client/src/Pages/Infrastructure/Create/index.jsx
+++ b/client/src/Pages/Infrastructure/Create/index.jsx
@@ -3,7 +3,7 @@ import { useTheme } from "@emotion/react";
import { useParams } from "react-router-dom";
import { useState, useEffect } from "react";
import { useSelector } from "react-redux";
-
+import axios from "axios";
// Utility and Network
import { infrastructureMonitorValidation } from "../../../Validation/validation";
import { useFetchHardwareMonitorById } from "../../../Hooks/monitorHooks";
@@ -87,35 +87,59 @@ const CreateInfrastructureMonitor = () => {
});
// Populate form fields if editing
+ // Populate global thresholds from /settings if creating
+ const { authToken } = useSelector((state) => state.auth);
+
useEffect(() => {
- if (isCreate || !monitor) return;
+ const fetchGlobalThresholds = async () => {
+ if (!isCreate) return;
- setInfrastructureMonitor({
- url: monitor.url.replace(/^https?:\/\//, ""),
- name: monitor.name || "",
- notifications: monitor.notifications,
- interval: monitor.interval / MS_PER_MINUTE,
- cpu: monitor.thresholds?.usage_cpu !== undefined,
- usage_cpu: monitor.thresholds?.usage_cpu ? monitor.thresholds.usage_cpu * 100 : "",
+ try {
+ const token = authToken;
+ if (!token) {
+ console.warn("No token found for fetching global thresholds.");
+ return;
+ }
- memory: monitor.thresholds?.usage_memory !== undefined,
- usage_memory: monitor.thresholds?.usage_memory
- ? monitor.thresholds.usage_memory * 100
- : "",
+ const response = await axios.get(`${import.meta.env.VITE_APP_API_BASE_URL}/settings`, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
- disk: monitor.thresholds?.usage_disk !== undefined,
- usage_disk: monitor.thresholds?.usage_disk
- ? monitor.thresholds.usage_disk * 100
- : "",
+ const globalThresholds = response.data?.data?.settings?.globalThresholds;
- temperature: monitor.thresholds?.usage_temperature !== undefined,
- usage_temperature: monitor.thresholds?.usage_temperature
- ? monitor.thresholds.usage_temperature * 100
- : "",
- secret: monitor.secret || "",
- });
- setHttps(monitor.url.startsWith("https"));
- }, [isCreate, monitor]);
+ if (!globalThresholds) {
+ console.warn("No global thresholds found in /settings response.");
+ return;
+ }
+
+ setInfrastructureMonitor((prev) => ({
+ ...prev,
+ cpu: globalThresholds.cpu != null,
+ usage_cpu: globalThresholds.cpu != null ? globalThresholds.cpu.toString() : "",
+
+ memory: globalThresholds.memory != null,
+ usage_memory:
+ globalThresholds.memory != null ? globalThresholds.memory.toString() : "",
+
+ disk: globalThresholds.disk != null,
+ usage_disk:
+ globalThresholds.disk != null ? globalThresholds.disk.toString() : "",
+
+ temperature: globalThresholds.temperature != null,
+ usage_temperature:
+ globalThresholds.temperature != null
+ ? globalThresholds.temperature.toString()
+ : "",
+ }));
+ } catch (error) {
+ console.error("Failed to fetch global thresholds:", error);
+ }
+ };
+
+ fetchGlobalThresholds();
+ }, [isCreate, user]);
// Handlers
const onSubmit = async (event) => {
diff --git a/client/src/Pages/Settings/SettingsGlobalThresholds.jsx b/client/src/Pages/Settings/SettingsGlobalThresholds.jsx
new file mode 100644
index 000000000..eca752688
--- /dev/null
+++ b/client/src/Pages/Settings/SettingsGlobalThresholds.jsx
@@ -0,0 +1,117 @@
+import Box from "@mui/material/Box";
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import ConfigBox from "../../Components/ConfigBox";
+import TextInput from "../../Components/Inputs/TextInput";
+
+import { useTheme } from "@emotion/react";
+import { PropTypes } from "prop-types";
+import { useState, useEffect } from "react";
+import { useTranslation } from "react-i18next";
+
+const SettingsGlobalThresholds = ({
+ isAdmin,
+ HEADING_SX,
+ settingsData,
+ setSettingsData,
+}) => {
+ const { t } = useTranslation(); // For language translation
+ const theme = useTheme(); // MUI theme access
+
+ // Local state for thresholds
+ const [thresholds, setThresholds] = useState({
+ cpu: "",
+ memory: "",
+ disk: "",
+ temperature: "",
+ });
+
+ // Load existing thresholds from settingsData on first render or when settingsData changes
+ useEffect(() => {
+ if (settingsData?.settings?.globalThresholds) {
+ setThresholds({
+ cpu: settingsData.settings.globalThresholds.cpu || "",
+ memory: settingsData.settings.globalThresholds.memory || "",
+ disk: settingsData.settings.globalThresholds.disk || "",
+ temperature: settingsData.settings.globalThresholds.temperature || "",
+ });
+ }
+ }, [settingsData]);
+
+ // Handles input change and updates state & parent data
+ const handleChange = (e, min, max) => {
+ const { name, value } = e.target;
+
+ // Allow empty or numeric value within range
+ if (
+ value === "" ||
+ (/^\d+$/.test(value) && Number(value) >= min && Number(value) <= max)
+ ) {
+ const updatedThresholds = {
+ ...thresholds,
+ [name]: value,
+ };
+
+ setThresholds(updatedThresholds);
+
+ // Update settingsData without enabling/disabling metrics
+ setSettingsData({
+ ...settingsData,
+ settings: {
+ ...settingsData.settings,
+ globalThresholds: updatedThresholds,
+ },
+ });
+ }
+ };
+
+ // Only render this section for admins
+ if (!isAdmin) return null;
+
+ return (
+
+ {/* Header and description */}
+
+
+ {t("settingsPage.globalThresholds.title", "Global Thresholds")}
+
+
+ {t(
+ "settingsPage.globalThresholds.description",
+ "Configure global CPU, Memory, Disk, and Temperature thresholds."
+ )}
+
+
+
+ {/* Threshold inputs */}
+
+ {[
+ ["CPU Threshold (%)", "cpu", 1, 100],
+ ["Memory Threshold (%)", "memory", 1, 100],
+ ["Disk Threshold (%)", "disk", 1, 100],
+ ["Temperature Threshold (°C)", "temperature", 1, 150],
+ ].map(([label, name, min, max]) => (
+ handleChange(e, min, max)}
+ />
+ ))}
+
+
+ );
+};
+
+// Prop types
+SettingsGlobalThresholds.propTypes = {
+ isAdmin: PropTypes.bool,
+ HEADING_SX: PropTypes.object,
+ settingsData: PropTypes.object,
+ setSettingsData: PropTypes.func,
+};
+
+export default SettingsGlobalThresholds;
diff --git a/client/src/Pages/Settings/index.jsx b/client/src/Pages/Settings/index.jsx
index dae6bd680..ab529eb68 100644
--- a/client/src/Pages/Settings/index.jsx
+++ b/client/src/Pages/Settings/index.jsx
@@ -8,6 +8,7 @@ import SettingsPagespeed from "./SettingsPagespeed";
import SettingsDemoMonitors from "./SettingsDemoMonitors";
import SettingsAbout from "./SettingsAbout";
import SettingsEmail from "./SettingsEmail";
+import SettingsGlobalThresholds from "./SettingsGlobalThresholds";
import Button from "@mui/material/Button";
// Utils
import { settingsValidation } from "../../Validation/validation";
@@ -48,6 +49,7 @@ const Settings = () => {
setIsApiKeySet,
setIsEmailPasswordSet,
});
+
const [addDemoMonitors, isAddingDemoMonitors] = useAddDemoMonitors();
const [isSaving, saveError, saveSettings] = useSaveSettings({
@@ -60,6 +62,7 @@ const Settings = () => {
const [deleteAllMonitors, isDeletingMonitors] = useDeleteAllMonitors();
const [deleteMonitorStats, isDeletingMonitorStats] = useDeleteMonitorStats();
+
// Setup
const isAdmin = useIsAdmin();
const theme = useTheme();
@@ -149,6 +152,7 @@ const Settings = () => {
error.details.forEach((err) => {
newErrors[err.path[0]] = err.message;
});
+
setErrors(newErrors);
}
saveSettings(settingsData?.settings);
@@ -190,6 +194,13 @@ const Settings = () => {
handleChange={handleChange}
errors={errors}
/>
+
+
{
if (!dayjs(value).isValid()) {
return helpers.error("any.invalid");
diff --git a/client/src/locales/en.json b/client/src/locales/en.json
index 78d0f2aed..5c69ecd81 100644
--- a/client/src/locales/en.json
+++ b/client/src/locales/en.json
@@ -837,6 +837,10 @@
"title": "Display timezone"
},
"title": "Settings",
+ "globalThresholds": {
+ "title": "Global Thresholds",
+ "description": "Configure global CPU, Memory, Disk, and Temperature thresholds. If a value is provided, it will automatically be enabled for monitoring."
+ },
"uiSettings": {
"description": "Switch between light and dark mode, or change user interface language.",
"labelLanguage": "Language",
diff --git a/server/src/db/models/AppSettings.js b/server/src/db/models/AppSettings.js
index cd63118e9..be7c856a4 100755
--- a/server/src/db/models/AppSettings.js
+++ b/server/src/db/models/AppSettings.js
@@ -65,7 +65,13 @@ const AppSettingsSchema = mongoose.Schema(
type: Number,
default: 1,
},
- },
+ globalThresholds: {
+ cpu: { type: Number },
+ memory: { type: Number},
+ disk: { type: Number},
+ temperature: { type: Number},
+ }
+},
{
timestamps: true,
}
diff --git a/server/src/validation/joi.js b/server/src/validation/joi.js
index 744b0ec74..51d8a94a2 100755
--- a/server/src/validation/joi.js
+++ b/server/src/validation/joi.js
@@ -416,8 +416,16 @@ const updateAppSettingsBodyValidation = joi.object({
systemEmailIgnoreTLS: joi.boolean(),
systemEmailRequireTLS: joi.boolean(),
systemEmailRejectUnauthorized: joi.boolean(),
+
+ globalThresholds: joi.object({
+ cpu: joi.number().min(1).max(100).allow("").optional(),
+ memory: joi.number().min(1).max(100).allow("").optional(),
+ disk: joi.number().min(1).max(100).allow("").optional(),
+ temperature: joi.number().min(1).max(150).allow("").optional(),
+ }).optional(),
});
+
//****************************************
// Status Page Validation
//****************************************