mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-01-24 02:29:35 -06:00
Adding Global Threshold Settings
This commit is contained in:
@@ -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) => {
|
||||
|
||||
117
client/src/Pages/Settings/SettingsGlobalThresholds.jsx
Normal file
117
client/src/Pages/Settings/SettingsGlobalThresholds.jsx
Normal 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;
|
||||
@@ -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}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
//****************************************
|
||||
|
||||
Reference in New Issue
Block a user