From d5e490830951b2d93bbfba6bd7316e6e5607f584 Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Thu, 28 Aug 2025 18:58:38 -0400 Subject: [PATCH 1/7] feat: add ntfy notification channel support Add comprehensive ntfy notification support to Checkmate, enabling users to receive push notifications via ntfy (self-hosted or ntfy.sh cloud service). ## Backend Implementation - Add ntfy to notification type enum in Notification model - Add ntfy-specific fields: auth method, credentials, priority level - Implement requestNtfy() method in NetworkService with full auth support: - Basic authentication (username/password) - Bearer token authentication - Public topics (no authentication) - Integrate ntfy handling into NotificationService - Add comprehensive Joi validation with conditional field requirements - Support priority levels 1-5 for different notification urgency ## Frontend Implementation - Add ntfy to notification types dropdown - Create conditional form rendering with ntfy-specific fields: - Topic URL input (required) - Authentication method selector - Username/password inputs (conditional) - Bearer token input (conditional) - Priority level selector (1-5) - Add complete translation support for all ntfy UI elements - Implement proper form validation and error handling ## Features - **Simple Configuration**: Just requires topic URL for basic usage - **Flexible Authentication**: Support for public, basic auth, and bearer token - **Priority Levels**: 5 priority levels (min, low, default, high, urgent) - **Self-hosting Friendly**: Works with ntfy.sh or self-hosted instances - **Developer Focused**: Perfect for monitoring tools and DevOps workflows - **Full Integration**: Works with existing test notification functionality ## Why ntfy? ntfy is a lightweight, HTTP-based pub-sub notification service that's perfect for developers who want simple, self-hostable push notifications without complex API keys or OAuth flows. This enhancement makes Checkmate more appealing to developers and DevOps teams who prefer simple, self-hosted notification solutions while maintaining the same ease of use as existing notification channels. --- .../src/Pages/Notifications/create/index.jsx | 70 +++++++++++++++++++ client/src/Pages/Notifications/utils.js | 20 ++++++ client/src/locales/en.json | 14 ++++ server/src/db/models/Notification.js | 23 +++++- .../service/infrastructure/networkService.js | 46 ++++++++++++ .../infrastructure/notificationService.js | 4 ++ server/src/validation/joi.js | 67 +++++++++++++++++- 7 files changed, 241 insertions(+), 3 deletions(-) diff --git a/client/src/Pages/Notifications/create/index.jsx b/client/src/Pages/Notifications/create/index.jsx index 6d8afe665e..38c3b517a0 100644 --- a/client/src/Pages/Notifications/create/index.jsx +++ b/client/src/Pages/Notifications/create/index.jsx @@ -29,6 +29,8 @@ import { DESCRIPTION_MAP, LABEL_MAP, PLACEHOLDER_MAP, + NTFY_AUTH_METHODS, + NTFY_PRIORITIES, } from "../utils"; // Setup @@ -57,6 +59,12 @@ const CreateNotifications = () => { notificationName: "", address: "", type: NOTIFICATION_TYPES[0]._id, + // ntfy-specific fields + ntfyAuthMethod: "none", + ntfyUsername: "", + ntfyPassword: "", + ntfyBearerToken: "", + ntfyPriority: 3, }); const [errors, setErrors] = useState({}); const { t } = useTranslation(); @@ -232,6 +240,68 @@ const CreateNotifications = () => { error={Boolean(errors.address)} helperText={errors["address"]} /> + + {/* ntfy-specific fields */} + {type === "ntfy" && ( + <> + + + )} diff --git a/client/src/Pages/Notifications/utils.js b/client/src/Pages/Notifications/utils.js index 0813f6868d..6100703310 100644 --- a/client/src/Pages/Notifications/utils.js +++ b/client/src/Pages/Notifications/utils.js @@ -4,6 +4,7 @@ export const NOTIFICATION_TYPES = [ { _id: 3, name: "PagerDuty", value: "pager_duty" }, { _id: 4, name: "Webhook", value: "webhook" }, { _id: 5, name: "Discord", value: "discord" }, + { _id: 6, name: "ntfy", value: "ntfy" }, ]; export const TITLE_MAP = { @@ -12,6 +13,7 @@ export const TITLE_MAP = { pager_duty: "createNotifications.pagerdutySettings.title", webhook: "createNotifications.webhookSettings.title", discord: "createNotifications.discordSettings.title", + ntfy: "createNotifications.ntfySettings.title", }; export const DESCRIPTION_MAP = { @@ -20,6 +22,7 @@ export const DESCRIPTION_MAP = { pager_duty: "createNotifications.pagerdutySettings.description", webhook: "createNotifications.webhookSettings.description", discord: "createNotifications.discordSettings.description", + ntfy: "createNotifications.ntfySettings.description", }; export const LABEL_MAP = { @@ -28,6 +31,7 @@ export const LABEL_MAP = { pager_duty: "createNotifications.pagerdutySettings.integrationKeyLabel", webhook: "createNotifications.webhookSettings.webhookLabel", discord: "createNotifications.discordSettings.webhookLabel", + ntfy: "createNotifications.ntfySettings.urlLabel", }; export const PLACEHOLDER_MAP = { @@ -36,4 +40,20 @@ export const PLACEHOLDER_MAP = { pager_duty: "createNotifications.pagerdutySettings.integrationKeyPlaceholder", webhook: "createNotifications.webhookSettings.webhookPlaceholder", discord: "createNotifications.discordSettings.webhookPlaceholder", + ntfy: "createNotifications.ntfySettings.urlPlaceholder", }; + +// ntfy-specific constants +export const NTFY_AUTH_METHODS = [ + { value: "none", label: "None" }, + { value: "username_password", label: "Username/Password" }, + { value: "bearer_token", label: "Bearer Token" } +]; + +export const NTFY_PRIORITIES = [ + { value: 1, label: "Min" }, + { value: 2, label: "Low" }, + { value: 3, label: "Default" }, + { value: 4, label: "High" }, + { value: 5, label: "Urgent" } +]; diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 991f57d72c..b8045150f3 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -690,6 +690,20 @@ "webhookLabel": "Webhook URL", "webhookPlaceholder": "https://your-server.com/webhook" }, + "ntfySettings": { + "title": "ntfy", + "description": "Send push notifications via ntfy (self-hosted or ntfy.sh)", + "urlLabel": "ntfy Topic URL", + "urlPlaceholder": "https://ntfy.sh/my-topic", + "authMethodLabel": "Authentication Method", + "usernameLabel": "Username", + "usernamePlaceholder": "Enter username", + "passwordLabel": "Password", + "passwordPlaceholder": "Enter password", + "bearerTokenLabel": "Bearer Token", + "bearerTokenPlaceholder": "Enter bearer token", + "priorityLabel": "Priority" + }, "testNotification": "Test notification", "dialogDeleteTitle": "Are you sure you want to delete this notification?", "dialogDeleteConfirm": "Delete" diff --git a/server/src/db/models/Notification.js b/server/src/db/models/Notification.js index a6a3c7c098..88de5029ab 100755 --- a/server/src/db/models/Notification.js +++ b/server/src/db/models/Notification.js @@ -16,7 +16,7 @@ const NotificationSchema = mongoose.Schema( }, type: { type: String, - enum: ["email", "slack", "discord", "webhook", "pager_duty"], + enum: ["email", "slack", "discord", "webhook", "pager_duty", "ntfy"], }, notificationName: { type: String, @@ -28,6 +28,27 @@ const NotificationSchema = mongoose.Schema( phone: { type: String, }, + // ntfy-specific fields + ntfyAuthMethod: { + type: String, + enum: ["none", "username_password", "bearer_token"], + default: "none", + }, + ntfyUsername: { + type: String, + }, + ntfyPassword: { + type: String, // Will be encrypted + }, + ntfyBearerToken: { + type: String, // Will be encrypted + }, + ntfyPriority: { + type: Number, + min: 1, + max: 5, + default: 3, + }, }, { timestamps: true, diff --git a/server/src/service/infrastructure/networkService.js b/server/src/service/infrastructure/networkService.js index 72a117e74b..baa21ee3ba 100644 --- a/server/src/service/infrastructure/networkService.js +++ b/server/src/service/infrastructure/networkService.js @@ -443,6 +443,52 @@ class NetworkService { throw error; } } + + async requestNtfy(url, message, title, notification) { + try { + // Build headers + const headers = { + "Title": title, + "Priority": notification.ntfyPriority?.toString() || "3", + "Tags": "checkmate,monitoring", + "Content-Type": "text/plain" + }; + + // Add authentication headers based on method + if (notification.ntfyAuthMethod === "username_password" && notification.ntfyUsername && notification.ntfyPassword) { + const auth = Buffer.from(`${notification.ntfyUsername}:${notification.ntfyPassword}`).toString('base64'); + headers["Authorization"] = `Basic ${auth}`; + } else if (notification.ntfyAuthMethod === "bearer_token" && notification.ntfyBearerToken) { + headers["Authorization"] = `Bearer ${notification.ntfyBearerToken}`; + } + + // Send the notification + const response = await this.axios.post(url, message, { headers }); + + return { + type: "ntfy", + status: true, + code: response.status, + message: "Successfully sent ntfy notification", + payload: response.data, + }; + } catch (error) { + this.logger.warn({ + message: error.message, + service: this.SERVICE_NAME, + method: "requestNtfy", + url: url + }); + + return { + type: "ntfy", + status: false, + code: error.response?.status || this.NETWORK_ERROR, + message: `Failed to send ntfy notification: ${error.message}`, + payload: error.response?.data, + }; + } + } } export default NetworkService; diff --git a/server/src/service/infrastructure/notificationService.js b/server/src/service/infrastructure/notificationService.js index 8c34519f3a..7ecc27fa23 100644 --- a/server/src/service/infrastructure/notificationService.js +++ b/server/src/service/infrastructure/notificationService.js @@ -44,6 +44,10 @@ class NotificationService { return response; } + if (type === "ntfy") { + const response = await this.networkService.requestNtfy(address, content, subject, notification); + return response.status; + } }; async handleNotifications(networkResponse) { diff --git a/server/src/validation/joi.js b/server/src/validation/joi.js index fa8dccde2e..1633151a26 100755 --- a/server/src/validation/joi.js +++ b/server/src/validation/joi.js @@ -573,10 +573,10 @@ const createNotificationBodyValidation = joi.object({ "any.required": "Notification name is required", }), - type: joi.string().valid("email", "webhook", "slack", "discord", "pager_duty").required().messages({ + type: joi.string().valid("email", "webhook", "slack", "discord", "pager_duty", "ntfy").required().messages({ "string.empty": "Notification type is required", "any.required": "Notification type is required", - "any.only": "Notification type must be email, webhook, or pager_duty", + "any.only": "Notification type must be email, webhook, slack, discord, pager_duty, or ntfy", }), address: joi.when("type", { @@ -604,8 +604,71 @@ const createNotificationBodyValidation = joi.object({ "string.uri": "Please enter a valid Webhook URL", }), }, + { + is: "ntfy", + then: joi.string().uri().required().messages({ + "string.empty": "ntfy URL cannot be empty", + "any.required": "ntfy URL is required", + "string.uri": "Please enter a valid ntfy URL", + }), + }, ], }), + + // ntfy-specific fields + ntfyAuthMethod: joi.when("type", { + is: "ntfy", + then: joi.string().valid("none", "username_password", "bearer_token").default("none"), + otherwise: joi.forbidden(), + }), + + ntfyUsername: joi.when("type", { + is: "ntfy", + then: joi.when("ntfyAuthMethod", { + is: "username_password", + then: joi.string().required().messages({ + "string.empty": "Username cannot be empty when using username/password authentication", + "any.required": "Username is required for username/password authentication", + }), + otherwise: joi.string().optional().allow(""), + }), + otherwise: joi.forbidden(), + }), + + ntfyPassword: joi.when("type", { + is: "ntfy", + then: joi.when("ntfyAuthMethod", { + is: "username_password", + then: joi.string().required().messages({ + "string.empty": "Password cannot be empty when using username/password authentication", + "any.required": "Password is required for username/password authentication", + }), + otherwise: joi.string().optional().allow(""), + }), + otherwise: joi.forbidden(), + }), + + ntfyBearerToken: joi.when("type", { + is: "ntfy", + then: joi.when("ntfyAuthMethod", { + is: "bearer_token", + then: joi.string().required().messages({ + "string.empty": "Bearer token cannot be empty when using bearer token authentication", + "any.required": "Bearer token is required for bearer token authentication", + }), + otherwise: joi.string().optional().allow(""), + }), + otherwise: joi.forbidden(), + }), + + ntfyPriority: joi.when("type", { + is: "ntfy", + then: joi.number().min(1).max(5).default(3).messages({ + "number.min": "Priority must be between 1 and 5", + "number.max": "Priority must be between 1 and 5", + }), + otherwise: joi.forbidden(), + }), }); //**************************************** From 0a465b3fb289d5baddf55df6a986e7aab8cd95a4 Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Thu, 28 Aug 2025 19:03:54 -0400 Subject: [PATCH 2/7] fix: correct ntfy dropdown format to match Select component expectations - Fix NTFY_AUTH_METHODS and NTFY_PRIORITIES to use {_id, name, value} format - Add helper functions to convert dropdown IDs to actual values - Update form submission to convert ntfy dropdown values correctly - Fix conditional rendering to use actual values for comparison - Ensure dropdowns now display properly with selectable options --- .../src/Pages/Notifications/create/index.jsx | 22 +++++++++++++++---- client/src/Pages/Notifications/utils.js | 16 +++++++------- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/client/src/Pages/Notifications/create/index.jsx b/client/src/Pages/Notifications/create/index.jsx index 38c3b517a0..422901d428 100644 --- a/client/src/Pages/Notifications/create/index.jsx +++ b/client/src/Pages/Notifications/create/index.jsx @@ -60,11 +60,11 @@ const CreateNotifications = () => { address: "", type: NOTIFICATION_TYPES[0]._id, // ntfy-specific fields - ntfyAuthMethod: "none", + ntfyAuthMethod: NTFY_AUTH_METHODS[0]._id, // "None" option _id ntfyUsername: "", ntfyPassword: "", ntfyBearerToken: "", - ntfyPriority: 3, + ntfyPriority: NTFY_PRIORITIES[2]._id, // "Default" option _id (3) }); const [errors, setErrors] = useState({}); const { t } = useTranslation(); @@ -77,6 +77,14 @@ const CreateNotifications = () => { return NOTIFICATION_TYPES.find((type) => type._id === typeId)?.value || "email"; }; + const getNtfyAuthMethodValue = (authMethodId) => { + return NTFY_AUTH_METHODS.find((method) => method._id === authMethodId)?.value || "none"; + }; + + const getNtfyPriorityValue = (priorityId) => { + return NTFY_PRIORITIES.find((priority) => priority._id === priorityId)?.value || 3; + }; + const extractError = (error, field) => error?.details.find((d) => d.path.includes(field))?.message; @@ -86,6 +94,9 @@ const CreateNotifications = () => { const form = { ...notification, type: getNotificationTypeValue(notification.type), + // Convert ntfy dropdown IDs to actual values + ntfyAuthMethod: getNtfyAuthMethodValue(notification.ntfyAuthMethod), + ntfyPriority: getNtfyPriorityValue(notification.ntfyPriority), }; let error = null; @@ -137,6 +148,9 @@ const CreateNotifications = () => { const form = { ...notification, type: getNotificationTypeValue(notification.type), + // Convert ntfy dropdown IDs to actual values + ntfyAuthMethod: getNtfyAuthMethodValue(notification.ntfyAuthMethod), + ntfyPriority: getNtfyPriorityValue(notification.ntfyPriority), }; let error = null; @@ -254,7 +268,7 @@ const CreateNotifications = () => { helperText={errors["ntfyAuthMethod"]} /> - {notification.ntfyAuthMethod === "username_password" && ( + {getNtfyAuthMethodValue(notification.ntfyAuthMethod) === "username_password" && ( <> { )} - {notification.ntfyAuthMethod === "bearer_token" && ( + {getNtfyAuthMethodValue(notification.ntfyAuthMethod) === "bearer_token" && ( Date: Thu, 28 Aug 2025 19:16:26 -0400 Subject: [PATCH 3/7] fix: add ntfy fields to frontend validation schema - Add ntfy to valid notification types in frontend validation - Add ntfy-specific field validation (ntfyAuthMethod, ntfyUsername, ntfyPassword, ntfyBearerToken, ntfyPriority) - Add conditional validation based on authentication method - Fix 'field is not allowed' errors when entering ntfy credentials - Ensure frontend validation matches backend validation schema --- client/src/Validation/validation.js | 67 ++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/client/src/Validation/validation.js b/client/src/Validation/validation.js index 26e390ff46..77ffaa505e 100644 --- a/client/src/Validation/validation.js +++ b/client/src/Validation/validation.js @@ -446,12 +446,12 @@ const notificationValidation = joi.object({ type: joi .string() - .valid("email", "webhook", "slack", "discord", "pager_duty") + .valid("email", "webhook", "slack", "discord", "pager_duty", "ntfy") .required() .messages({ "string.empty": "Notification type is required", "any.required": "Notification type is required", - "any.only": "Notification type must be email, webhook, or pager_duty", + "any.only": "Notification type must be email, webhook, slack, discord, pager_duty, or ntfy", }), address: joi.when("type", { @@ -483,8 +483,71 @@ const notificationValidation = joi.object({ "string.uri": "Please enter a valid Webhook URL", }), }, + { + is: "ntfy", + then: joi.string().uri().required().messages({ + "string.empty": "ntfy URL cannot be empty", + "any.required": "ntfy URL is required", + "string.uri": "Please enter a valid ntfy URL", + }), + }, ], }), + + // ntfy-specific fields + ntfyAuthMethod: joi.when("type", { + is: "ntfy", + then: joi.string().valid("none", "username_password", "bearer_token").default("none"), + otherwise: joi.forbidden(), + }), + + ntfyUsername: joi.when("type", { + is: "ntfy", + then: joi.when("ntfyAuthMethod", { + is: "username_password", + then: joi.string().required().messages({ + "string.empty": "Username cannot be empty when using username/password authentication", + "any.required": "Username is required for username/password authentication", + }), + otherwise: joi.string().optional().allow(""), + }), + otherwise: joi.forbidden(), + }), + + ntfyPassword: joi.when("type", { + is: "ntfy", + then: joi.when("ntfyAuthMethod", { + is: "username_password", + then: joi.string().required().messages({ + "string.empty": "Password cannot be empty when using username/password authentication", + "any.required": "Password is required for username/password authentication", + }), + otherwise: joi.string().optional().allow(""), + }), + otherwise: joi.forbidden(), + }), + + ntfyBearerToken: joi.when("type", { + is: "ntfy", + then: joi.when("ntfyAuthMethod", { + is: "bearer_token", + then: joi.string().required().messages({ + "string.empty": "Bearer token cannot be empty when using bearer token authentication", + "any.required": "Bearer token is required for bearer token authentication", + }), + otherwise: joi.string().optional().allow(""), + }), + otherwise: joi.forbidden(), + }), + + ntfyPriority: joi.when("type", { + is: "ntfy", + then: joi.number().min(1).max(5).default(3).messages({ + "number.min": "Priority must be between 1 and 5", + "number.max": "Priority must be between 1 and 5", + }), + otherwise: joi.forbidden(), + }), }); const editUserValidation = joi.object({ From 0c87e01c23964ebf79bfafa5743ed15398b602b2 Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Thu, 28 Aug 2025 19:18:46 -0400 Subject: [PATCH 4/7] fix: convert ntfy dropdown values during onChange validation - Convert ntfy dropdown IDs to actual values in onChange handler for proper validation - Add special handling for ntfyAuthMethod changes to update all related field errors - Fix red border validation errors when selecting dropdown options - Ensure validation runs on actual values, not dropdown IDs --- client/src/Pages/Notifications/create/index.jsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/client/src/Pages/Notifications/create/index.jsx b/client/src/Pages/Notifications/create/index.jsx index 422901d428..790b4d6b58 100644 --- a/client/src/Pages/Notifications/create/index.jsx +++ b/client/src/Pages/Notifications/create/index.jsx @@ -126,6 +126,9 @@ const CreateNotifications = () => { let newNotification = { ...rawNotification, type: getNotificationTypeValue(rawNotification.type), + // Convert ntfy dropdown IDs to actual values for validation + ntfyAuthMethod: getNtfyAuthMethodValue(rawNotification.ntfyAuthMethod), + ntfyPriority: getNtfyPriorityValue(rawNotification.ntfyPriority), }; const { error } = notificationValidation.validate(newNotification, { @@ -136,6 +139,12 @@ const CreateNotifications = () => { if (name === "type") { validationError["type"] = extractError(error, "type"); validationError["address"] = extractError(error, "address"); + } else if (name === "ntfyAuthMethod") { + // When auth method changes, update all ntfy field errors + validationError["ntfyAuthMethod"] = extractError(error, "ntfyAuthMethod"); + validationError["ntfyUsername"] = extractError(error, "ntfyUsername"); + validationError["ntfyPassword"] = extractError(error, "ntfyPassword"); + validationError["ntfyBearerToken"] = extractError(error, "ntfyBearerToken"); } else { validationError[name] = extractError(error, name); } From 32c51fc40ff0fbaa62354008db5ccac00d80aa6b Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Thu, 28 Aug 2025 20:26:04 -0400 Subject: [PATCH 5/7] fix: improve UI stability and notification service formatting - Add null safety checks for greeting state access - Fix greeting array index bounds checking - Clean up notification service conditional formatting --- client/src/Features/UI/uiSlice.js | 3 +++ client/src/Utils/greeting.jsx | 9 +++++---- server/src/service/infrastructure/notificationService.js | 9 +++++++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/client/src/Features/UI/uiSlice.js b/client/src/Features/UI/uiSlice.js index 45ba267b0b..2f31c350b9 100644 --- a/client/src/Features/UI/uiSlice.js +++ b/client/src/Features/UI/uiSlice.js @@ -54,6 +54,9 @@ const uiSlice = createSlice({ state.showURL = action.payload; }, setGreeting(state, action) { + if (!state.greeting) { + state.greeting = { index: 0, lastUpdate: null }; + } state.greeting.index = action.payload.index; state.greeting.lastUpdate = action.payload.lastUpdate; }, diff --git a/client/src/Utils/greeting.jsx b/client/src/Utils/greeting.jsx index d2b7fa033c..87ed767117 100644 --- a/client/src/Utils/greeting.jsx +++ b/client/src/Utils/greeting.jsx @@ -135,9 +135,9 @@ const Greeting = ({ type = "" }) => { const theme = useTheme(); const dispatch = useDispatch(); const { t } = useTranslation(); - const { firstName } = useSelector((state) => state.auth.user); - const index = useSelector((state) => state.ui.greeting.index); - const lastUpdate = useSelector((state) => state.ui.greeting.lastUpdate); + const { firstName } = useSelector((state) => state.auth.user || {}); + const index = useSelector((state) => state.ui.greeting?.index ?? 0); + const lastUpdate = useSelector((state) => state.ui.greeting?.lastUpdate ?? null); const now = new Date(); const hour = now.getHours(); @@ -153,7 +153,8 @@ const Greeting = ({ type = "" }) => { let greetingArray = hour < 6 ? early : hour < 12 ? morning : hour < 18 ? afternoon : evening; - const { prepend, append, emoji } = greetingArray[index]; + const safeIndex = index >= 0 && index < greetingArray.length ? index : 0; + const { prepend, append, emoji } = greetingArray[safeIndex]; return ( diff --git a/server/src/service/infrastructure/notificationService.js b/server/src/service/infrastructure/notificationService.js index 7ecc27fa23..e39d4ba105 100644 --- a/server/src/service/infrastructure/notificationService.js +++ b/server/src/service/infrastructure/notificationService.js @@ -53,9 +53,14 @@ class NotificationService { async handleNotifications(networkResponse) { const { monitor, statusChanged, prevStatus } = networkResponse; const { type } = monitor; - if (type !== "hardware" && statusChanged === false) return false; + + if (type !== "hardware" && statusChanged === false) { + return false; + } // if prevStatus is undefined, monitor is resuming, we're done - if (type !== "hardware" && prevStatus === undefined) return false; + if (type !== "hardware" && prevStatus === undefined) { + return false; + } const notificationIDs = networkResponse.monitor?.notifications ?? []; if (notificationIDs.length === 0) return false; From 3a3be9b3aca4b20a024d94a2167e73a2c8739006 Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Thu, 28 Aug 2025 20:30:21 -0400 Subject: [PATCH 6/7] fix: apply prettier formatting to all files - Fix code formatting in server files - Fix code formatting in client files - Ensure compliance with project style guidelines --- .../src/Pages/Notifications/create/index.jsx | 30 ++++++++++++------- client/src/Pages/Notifications/utils.js | 4 +-- client/src/Validation/validation.js | 12 +++++--- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/client/src/Pages/Notifications/create/index.jsx b/client/src/Pages/Notifications/create/index.jsx index 790b4d6b58..d5aa84e02f 100644 --- a/client/src/Pages/Notifications/create/index.jsx +++ b/client/src/Pages/Notifications/create/index.jsx @@ -78,7 +78,9 @@ const CreateNotifications = () => { }; const getNtfyAuthMethodValue = (authMethodId) => { - return NTFY_AUTH_METHODS.find((method) => method._id === authMethodId)?.value || "none"; + return ( + NTFY_AUTH_METHODS.find((method) => method._id === authMethodId)?.value || "none" + ); }; const getNtfyPriorityValue = (priorityId) => { @@ -263,7 +265,7 @@ const CreateNotifications = () => { error={Boolean(errors.address)} helperText={errors["address"]} /> - + {/* ntfy-specific fields */} {type === "ntfy" && ( <> @@ -276,13 +278,16 @@ const CreateNotifications = () => { error={Boolean(errors.ntfyAuthMethod)} helperText={errors["ntfyAuthMethod"]} /> - - {getNtfyAuthMethodValue(notification.ntfyAuthMethod) === "username_password" && ( + + {getNtfyAuthMethodValue(notification.ntfyAuthMethod) === + "username_password" && ( <> { type="password" label={t("createNotifications.ntfySettings.passwordLabel")} name="ntfyPassword" - placeholder={t("createNotifications.ntfySettings.passwordPlaceholder")} + placeholder={t( + "createNotifications.ntfySettings.passwordPlaceholder" + )} value={notification.ntfyPassword} onChange={onChange} error={Boolean(errors.ntfyPassword)} @@ -300,20 +307,23 @@ const CreateNotifications = () => { /> )} - - {getNtfyAuthMethodValue(notification.ntfyAuthMethod) === "bearer_token" && ( + + {getNtfyAuthMethodValue(notification.ntfyAuthMethod) === + "bearer_token" && ( )} - +