add notifications to infra page, add generic webhook notification channel
This commit is contained in:
1 parent
de673ea5d1
commit
1b40aa0ded
6 files changed
+75
-76
No files matched your search
@@ -13,6 +13,8 @@ import {
|
||||
import { useHardwareMonitorsFetch } from "../Details/Hooks/useHardwareMonitorsFetch";
|
||||
import { capitalizeFirstLetter } from "../../../Utils/stringUtils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications";
|
||||
import NotificationsConfig from "../../../Components/NotificationConfig";
|
||||
|
||||
// MUI
|
||||
import { Box, Stack, Typography, Button, ButtonGroup } from "@mui/material";
|
||||
@@ -24,7 +26,6 @@ import ConfigBox from "../../../Components/ConfigBox";
|
||||
import TextInput from "../../../Components/Inputs/TextInput";
|
||||
import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments";
|
||||
import { createToast } from "../../../Utils/toastUtils";
|
||||
import Checkbox from "../../../Components/Inputs/Checkbox";
|
||||
import Select from "../../../Components/Inputs/Select";
|
||||
import { CustomThreshold } from "./Components/CustomThreshold";
|
||||
|
||||
@@ -65,6 +66,8 @@ const CreateInfrastructureMonitor = () => {
|
||||
|
||||
// Fetch monitor details if editing
|
||||
const { monitor, isLoading, networkError } = useHardwareMonitorsFetch({ monitorId });
|
||||
const [notifications, notificationsAreLoading, notificationsError] =
|
||||
useGetNotificationsByTeamId();
|
||||
|
||||
// State
|
||||
const [errors, setErrors] = useState({});
|
||||
@@ -93,8 +96,7 @@ const CreateInfrastructureMonitor = () => {
|
||||
setInfrastructureMonitor({
|
||||
url: monitor.url.replace(/^https?:\/\//, ""),
|
||||
name: monitor.name || "",
|
||||
notifications: monitor.notifications?.filter((n) => typeof n === "object") || [],
|
||||
notify_email: (monitor.notifications?.length ?? 0) > 0,
|
||||
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 : "",
|
||||
@@ -119,17 +121,9 @@ const CreateInfrastructureMonitor = () => {
|
||||
}, [isCreate, monitor]);
|
||||
|
||||
// Handlers
|
||||
const handleCreateInfrastructureMonitor = async (event) => {
|
||||
const onSubmit = async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
const formattedNotifications = infrastructureMonitor.notifications.map((n) =>
|
||||
typeof n === "string" ? { type: "email", address: n } : n
|
||||
);
|
||||
|
||||
if (infrastructureMonitor.notify_email) {
|
||||
formattedNotifications.push({ type: "email", address: user.email });
|
||||
}
|
||||
|
||||
// Build the form
|
||||
let form = {
|
||||
url: `http${https ? "s" : ""}://` + infrastructureMonitor.url,
|
||||
@@ -155,7 +149,6 @@ const CreateInfrastructureMonitor = () => {
|
||||
? { usage_temperature: infrastructureMonitor.usage_temperature }
|
||||
: {}),
|
||||
secret: infrastructureMonitor.secret,
|
||||
notifications: formattedNotifications,
|
||||
};
|
||||
|
||||
const { error } = infrastructureMonitorValidation.validate(form, {
|
||||
@@ -167,6 +160,7 @@ const CreateInfrastructureMonitor = () => {
|
||||
error.details.forEach((err) => {
|
||||
newErrors[err.path[0]] = err.message;
|
||||
});
|
||||
console.log(newErrors);
|
||||
setErrors(newErrors);
|
||||
createToast({ body: "Please check the form for errors." });
|
||||
return;
|
||||
@@ -218,7 +212,7 @@ const CreateInfrastructureMonitor = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (event) => {
|
||||
const onChange = (event) => {
|
||||
const { value, name } = event.target;
|
||||
setInfrastructureMonitor({
|
||||
...infrastructureMonitor,
|
||||
@@ -244,27 +238,6 @@ const CreateInfrastructureMonitor = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleNotifications = (event, type) => {
|
||||
const { value, checked } = event.target;
|
||||
let notifications = [...infrastructureMonitor.notifications];
|
||||
|
||||
if (checked) {
|
||||
if (!notifications.some((n) => n.type === type && n.address === value)) {
|
||||
notifications.push({ type, address: value });
|
||||
}
|
||||
} else {
|
||||
notifications = notifications.filter(
|
||||
(n) => !(n.type === type && n.address === value)
|
||||
);
|
||||
}
|
||||
|
||||
setInfrastructureMonitor((prev) => ({
|
||||
...prev,
|
||||
notifications,
|
||||
...(type === "email" ? { notify_email: checked } : {}),
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<Box className="create-infrastructure-monitor">
|
||||
<Breadcrumbs
|
||||
@@ -280,8 +253,7 @@ const CreateInfrastructureMonitor = () => {
|
||||
/>
|
||||
<Stack
|
||||
component="form"
|
||||
className="create-infrastructure-monitor-form"
|
||||
onSubmit={handleCreateInfrastructureMonitor}
|
||||
onSubmit={onSubmit}
|
||||
noValidate
|
||||
spellCheck="false"
|
||||
gap={theme.spacing(12)}
|
||||
@@ -336,7 +308,7 @@ const CreateInfrastructureMonitor = () => {
|
||||
label={t("infrastructureServerUrlLabel")}
|
||||
https={https}
|
||||
value={infrastructureMonitor.url}
|
||||
onChange={handleChange}
|
||||
onChange={onChange}
|
||||
error={errors["url"] ? true : false}
|
||||
helperText={errors["url"]}
|
||||
disabled={!isCreate}
|
||||
@@ -370,7 +342,7 @@ const CreateInfrastructureMonitor = () => {
|
||||
placeholder="Google"
|
||||
isOptional={true}
|
||||
value={infrastructureMonitor.name}
|
||||
onChange={handleChange}
|
||||
onChange={onChange}
|
||||
error={errors["name"]}
|
||||
/>
|
||||
<TextInput
|
||||
@@ -379,7 +351,7 @@ const CreateInfrastructureMonitor = () => {
|
||||
name="secret"
|
||||
label={t("infrastructureAuthorizationSecretLabel")}
|
||||
value={infrastructureMonitor.secret}
|
||||
onChange={handleChange}
|
||||
onChange={onChange}
|
||||
error={errors["secret"] ? true : false}
|
||||
helperText={errors["secret"]}
|
||||
/>
|
||||
@@ -387,25 +359,14 @@ const CreateInfrastructureMonitor = () => {
|
||||
</ConfigBox>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography
|
||||
component="h2"
|
||||
variant="h2"
|
||||
>
|
||||
{t("distributedUptimeCreateIncidentNotification")}
|
||||
</Typography>
|
||||
<Typography component="p">
|
||||
{t("distributedUptimeCreateIncidentDescription")}
|
||||
</Typography>
|
||||
<Typography component="h2">{t("notificationConfig.title")}</Typography>
|
||||
<Typography component="p">{t("notificationConfig.description")}</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(6)}>
|
||||
<Checkbox
|
||||
id="notify-email-default"
|
||||
label={`Notify via email (to ${user.email})`}
|
||||
isChecked={infrastructureMonitor.notify_email}
|
||||
value={user?.email}
|
||||
onChange={(event) => handleNotifications(event, "email")}
|
||||
/>
|
||||
</Stack>
|
||||
<NotificationsConfig
|
||||
notifications={notifications}
|
||||
setMonitor={setInfrastructureMonitor}
|
||||
setNotifications={infrastructureMonitor.notifications}
|
||||
/>
|
||||
</ConfigBox>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
@@ -438,7 +399,7 @@ const CreateInfrastructureMonitor = () => {
|
||||
fieldId={METRIC_PREFIX + metric}
|
||||
fieldName={METRIC_PREFIX + metric}
|
||||
fieldValue={String(infrastructureMonitor[METRIC_PREFIX + metric])}
|
||||
onFieldChange={handleChange}
|
||||
onFieldChange={onChange}
|
||||
alertUnit={metric == "temperature" ? "°C" : "%"}
|
||||
/>
|
||||
);
|
||||
@@ -474,7 +435,7 @@ const CreateInfrastructureMonitor = () => {
|
||||
name="interval"
|
||||
label="Check frequency"
|
||||
value={infrastructureMonitor.interval || 15}
|
||||
onChange={handleChange}
|
||||
onChange={onChange}
|
||||
items={SELECT_VALUES}
|
||||
/>
|
||||
</Stack>
|
||||
@@ -484,9 +445,9 @@ const CreateInfrastructureMonitor = () => {
|
||||
justifyContent="flex-end"
|
||||
>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={handleCreateInfrastructureMonitor}
|
||||
loading={monitorState?.isLoading}
|
||||
>
|
||||
{t(isCreate ? "infrastructureCreateMonitor" : "infrastructureEditMonitor")}
|
||||
|
||||
@@ -27,6 +27,7 @@ const NOTIFICATION_TYPES = [
|
||||
{ _id: 1, name: "E-mail", value: "email" },
|
||||
{ _id: 2, name: "Slack", value: "webhook" },
|
||||
{ _id: 3, name: "PagerDuty", value: "pager_duty" },
|
||||
{ _id: 4, name: "Webhook", value: "webhook" },
|
||||
];
|
||||
|
||||
const CreateNotifications = () => {
|
||||
@@ -130,6 +131,12 @@ const CreateNotifications = () => {
|
||||
newNotification.config = value;
|
||||
}
|
||||
newNotification.config.platform = "pager_duty";
|
||||
} else if (newNotification.type === 4) {
|
||||
newNotification.config = newNotification.config || {};
|
||||
if (name === "config") {
|
||||
newNotification.config = value;
|
||||
}
|
||||
newNotification.config.platform = "webhook";
|
||||
}
|
||||
|
||||
// Field-level validation
|
||||
@@ -247,7 +254,7 @@ const CreateNotifications = () => {
|
||||
<Stack gap={theme.spacing(12)}>
|
||||
<TextInput
|
||||
label={t("createNotifications.slackSettings.webhookLabel")}
|
||||
value={notification.config.webhookUrl}
|
||||
value={notification.config.webhookUrl || ""}
|
||||
error={Boolean(errors.config)}
|
||||
helperText={errors["config"]}
|
||||
onChange={(e) => {
|
||||
@@ -283,7 +290,7 @@ const CreateNotifications = () => {
|
||||
placeholder={t(
|
||||
"createNotifications.pagerdutySettings.integrationKeyPlaceholder"
|
||||
)}
|
||||
value={notification.config.routingKey}
|
||||
value={notification.config.routingKey || ""}
|
||||
error={Boolean(errors.config)}
|
||||
helperText={errors["config"]}
|
||||
onChange={(e) => {
|
||||
@@ -303,6 +310,39 @@ const CreateNotifications = () => {
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
)}
|
||||
{notification.type === 4 && (
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h2">
|
||||
{t("createNotifications.genericWebhookSettings.title")}
|
||||
</Typography>
|
||||
<Typography component="p">
|
||||
{t("createNotifications.genericWebhookSettings.description")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(12)}>
|
||||
<TextInput
|
||||
label={t("createNotifications.genericWebhookSettings.webhookLabel")}
|
||||
value={notification.config.webhookUrl || ""}
|
||||
error={Boolean(errors.config)}
|
||||
helperText={errors["config"]}
|
||||
onChange={(e) => {
|
||||
const updatedConfig = {
|
||||
...notification.config,
|
||||
webhookUrl: e.target.value,
|
||||
};
|
||||
|
||||
onChange({
|
||||
target: {
|
||||
name: "config",
|
||||
value: updatedConfig,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
)}
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="flex-end"
|
||||
|
||||
@@ -388,15 +388,7 @@ const infrastructureMonitorValidation = joi.object({
|
||||
"number.base": "Frequency must be a number.",
|
||||
"any.required": "Frequency is required.",
|
||||
}),
|
||||
notifications: joi.array().items(
|
||||
joi.object({
|
||||
type: joi.string().valid("email").required(),
|
||||
address: joi
|
||||
.string()
|
||||
.email({ tlds: { allow: false } })
|
||||
.required(),
|
||||
})
|
||||
),
|
||||
notifications: joi.array().items(joi.string()),
|
||||
});
|
||||
|
||||
const notificationEmailValidation = joi.object({
|
||||
|
||||
@@ -164,6 +164,12 @@
|
||||
"description": "Configure your PagerDuty integration here",
|
||||
"integrationKeyLabel": "Integration key",
|
||||
"integrationKeyPlaceholder": "1234567890"
|
||||
},
|
||||
"genericWebhookSettings": {
|
||||
"title": "Webhook",
|
||||
"description": "Configure your webhook here",
|
||||
"webhookLabel": "Webhook URL",
|
||||
"webhookPlaceholder": "https://your-server.com/webhook"
|
||||
}
|
||||
},
|
||||
"notificationConfig": {
|
||||
@@ -172,7 +178,7 @@
|
||||
},
|
||||
"notifications": {
|
||||
"fallback": {
|
||||
"title": "Notification",
|
||||
"title": "notification channel",
|
||||
"checks": "Alert teams about downtime or performance issues"
|
||||
},
|
||||
"createButton": "Create notification channel",
|
||||
|
||||
@@ -7,7 +7,7 @@ const configSchema = mongoose.Schema(
|
||||
chatId: { type: String },
|
||||
platform: {
|
||||
type: String,
|
||||
enum: ["slack", "pager_duty"],
|
||||
enum: ["slack", "pager_duty", "webhook"],
|
||||
},
|
||||
routingKey: { type: String },
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const SERVICE_NAME = "BufferService";
|
||||
const BUFFER_TIMEOUT = 1000 * 60 * 1; // 1 minute
|
||||
const BUFFER_TIMEOUT = process.env.NODE_ENV === "development" ? 5000 : 1000 * 60 * 1; // 1 minute
|
||||
const TYPE_MAP = {
|
||||
http: "checks",
|
||||
ping: "checks",
|
||||
@@ -89,7 +89,7 @@ class BufferService {
|
||||
}
|
||||
this.buffers[bufferName] = [];
|
||||
}
|
||||
this.logger.info({
|
||||
this.logger.debug({
|
||||
message: `Flushed ${items} items`,
|
||||
service: this.SERVICE_NAME,
|
||||
method: "flushBuffers",
|
||||
|
||||
Reference in new issue
Block a user