Adding Global Threshold Settings

This commit is contained in:
singh-kanwarpreet
2025-07-26 15:24:25 +05:30
parent 0c6d635b67
commit bf5d7f3c53
7 changed files with 203 additions and 26 deletions

View File

@@ -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) => {

View File

@@ -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 (
<ConfigBox>
{/* Header and description */}
<Box>
<Typography component="h1" variant="h2">
{t("settingsPage.globalThresholds.title", "Global Thresholds")}
</Typography>
<Typography sx={HEADING_SX}>
{t(
"settingsPage.globalThresholds.description",
"Configure global CPU, Memory, Disk, and Temperature thresholds."
)}
</Typography>
</Box>
{/* Threshold inputs */}
<Stack gap={theme.spacing(20)}>
{[
["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]) => (
<TextInput
key={name}
name={name}
label={label}
placeholder={`${min} - ${max}`}
type="number"
value={thresholds[name]}
onChange={(e) => handleChange(e, min, max)}
/>
))}
</Stack>
</ConfigBox>
);
};
// Prop types
SettingsGlobalThresholds.propTypes = {
isAdmin: PropTypes.bool,
HEADING_SX: PropTypes.object,
settingsData: PropTypes.object,
setSettingsData: PropTypes.func,
};
export default SettingsGlobalThresholds;

View File

@@ -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}
/>
<SettingsGlobalThresholds
isAdmin={isAdmin}
HEADING_SX={HEADING_SX}
settingsData={settingsData}
setSettingsData={setSettingsData}
/>
<SettingsDemoMonitors
isAdmin={isAdmin}
HEADER_SX={HEADING_SX}

View File

@@ -302,8 +302,15 @@ const settingsValidation = 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(),
});
const dayjsValidator = (value, helpers) => {
if (!dayjs(value).isValid()) {
return helpers.error("any.invalid");

View File

@@ -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",

View File

@@ -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,
}

View File

@@ -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
//****************************************