-
-
Notifications
You must be signed in to change notification settings - Fork 614
feat: add ntfy notification channel support #2872
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
d5e4908
0a465b3
214bbc6
0c87e01
32c51fc
3a3be9b
f7e25bf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -446,12 +446,13 @@ 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 +484,74 @@ 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(""), | ||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+523
to
+529
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Trim password. Same rationale as username. - then: joi.string().required().messages({
+ /* biome-ignore lint/suspicious/noThenProperty: Joi uses 'then'. */
+ then: joi.string().trim().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.string().trim().optional().allow(""),📝 Committable suggestion
Suggested change
🧰 Tools🪛 Biome (2.1.2)[error] 523-523: Do not add then to an object. (lint/suspicious/noThenProperty) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
| 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(), | ||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+547
to
+554
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Enforce integer priority 1–5. Avoids floats like 3.14. - then: joi.number().min(1).max(5).default(3).messages({
+ then: joi.number().integer().min(1).max(5).default(3).messages({
+ "number.integer": "Priority must be an integer between 1 and 5",
"number.min": "Priority must be between 1 and 5",
"number.max": "Priority must be between 1 and 5",
}),📝 Committable suggestion
Suggested change
🧰 Tools🪛 Biome (2.1.2)[error] 545-545: Do not add then to an object. (lint/suspicious/noThenProperty) 🤖 Prompt for AI Agents
Comment on lines
+487
to
+554
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Silence Biome’s false-positive on Joi’s Biome’s Happy to open a quick PR to add targeted 🧰 Tools🪛 Biome (2.1.2)[error] 489-489: Do not add then to an object. (lint/suspicious/noThenProperty) [error] 501-501: Do not add then to an object. (lint/suspicious/noThenProperty) [error] 507-507: Do not add then to an object. (lint/suspicious/noThenProperty) [error] 509-509: Do not add then to an object. (lint/suspicious/noThenProperty) [error] 521-521: Do not add then to an object. (lint/suspicious/noThenProperty) [error] 523-523: Do not add then to an object. (lint/suspicious/noThenProperty) [error] 535-535: Do not add then to an object. (lint/suspicious/noThenProperty) [error] 537-537: Do not add then to an object. (lint/suspicious/noThenProperty) [error] 549-549: Do not add then to an object. (lint/suspicious/noThenProperty) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const editUserValidation = joi.object({ | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+693
to
+706
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add i18n for ntfy option labels to avoid hard-coded UI text NTFY auth-method and priority options are hard-coded in utils.js. Provide localized strings here so the UI can use i18n consistently. "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",
+ "authMethodOptions": {
+ "none": "None",
+ "username_password": "Username/Password",
+ "bearer_token": "Bearer Token"
+ },
"usernameLabel": "Username",
"usernamePlaceholder": "Enter username",
"passwordLabel": "Password",
"passwordPlaceholder": "Enter password",
"bearerTokenLabel": "Bearer Token",
"bearerTokenPlaceholder": "Enter bearer token",
- "priorityLabel": "Priority"
+ "priorityLabel": "Priority",
+ "priorityOptions": {
+ "1": "Min",
+ "2": "Low",
+ "3": "Default",
+ "4": "High",
+ "5": "Urgent"
+ }
},📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "testNotification": "Test notification", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "dialogDeleteTitle": "Are you sure you want to delete this notification?", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "dialogDeleteConfirm": "Delete" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix optional chaining to avoid runtime crash when no errors.
error?.details.findwill throw becausedetailscan be undefined. Chaindetailstoo.Apply this diff:
📝 Committable suggestion
🤖 Prompt for AI Agents