mirror of
				https://github.com/louislam/uptime-kuma.git
				synced 2025-11-01 03:49:24 +08:00 
			
		
		
		
	Merge remote-tracking branch 'origin/master'
This commit is contained in:
		
							
								
								
									
										6
									
								
								.github/ISSUE_TEMPLATE/ask-for-help.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/ISSUE_TEMPLATE/ask-for-help.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -26,6 +26,12 @@ body: | ||||
|       label: "📝 Describe your problem" | ||||
|       description: "Please walk us through it step by step." | ||||
|       placeholder: "Describe what are you asking for..." | ||||
|   - type: textarea | ||||
|     id: error-msg | ||||
|     validations: | ||||
|       required: false | ||||
|     attributes: | ||||
|       label: "📝 Error Message(s) or Log" | ||||
|   - type: input | ||||
|     id: uptime-kuma-version | ||||
|     attributes: | ||||
|   | ||||
							
								
								
									
										25
									
								
								.github/workflows/json-yaml-validate.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								.github/workflows/json-yaml-validate.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| name: json-yaml-validate  | ||||
| on: | ||||
|   push: | ||||
|     branches: | ||||
|       - master | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - master | ||||
|   workflow_dispatch: | ||||
|  | ||||
| permissions: | ||||
|   contents: read | ||||
|   pull-requests: write # enable write permissions for pull request comments | ||||
|  | ||||
| jobs: | ||||
|   json-yaml-validate: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: actions/checkout@v3 | ||||
|  | ||||
|       - name: json-yaml-validate | ||||
|         id: json-yaml-validate | ||||
|         uses: GrantBirki/json-yaml-validate@v1.2.0 | ||||
|         with: | ||||
|           comment: "true" # enable comment mode | ||||
| @@ -1245,7 +1245,7 @@ class Monitor extends BeanModel { | ||||
|  | ||||
|         if (notificationList.length > 0) { | ||||
|  | ||||
|             let row = await R.getRow("SELECT * FROM notification_sent_history WHERE type = ? AND monitor_id = ? AND days = ?", [ | ||||
|             let row = await R.getRow("SELECT * FROM notification_sent_history WHERE type = ? AND monitor_id = ? AND days <= ?", [ | ||||
|                 "certificate", | ||||
|                 this.id, | ||||
|                 targetDays, | ||||
|   | ||||
							
								
								
									
										97
									
								
								server/notification-providers/opsgenie.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								server/notification-providers/opsgenie.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,97 @@ | ||||
| const NotificationProvider = require("./notification-provider"); | ||||
| const axios = require("axios"); | ||||
| const { UP, DOWN } = require("../../src/util"); | ||||
|  | ||||
| const opsgenieAlertsUrlEU = "https://api.eu.opsgenie.com/v2/alerts"; | ||||
| const opsgenieAlertsUrlUS = "https://api.opsgenie.com/v2/alerts"; | ||||
| let okMsg = "Sent Successfully."; | ||||
|  | ||||
| class Opsgenie extends NotificationProvider { | ||||
|  | ||||
|     name = "Opsgenie"; | ||||
|  | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { | ||||
|         let opsgenieAlertsUrl; | ||||
|         let priority = (notification.opsgeniePriority == "") ? 3 : notification.opsgeniePriority; | ||||
|         const textMsg = "Uptime Kuma Alert"; | ||||
|  | ||||
|         try { | ||||
|             switch (notification.opsgenieRegion) { | ||||
|                 case "US": | ||||
|                     opsgenieAlertsUrl = opsgenieAlertsUrlUS; | ||||
|                     break; | ||||
|                 case "EU": | ||||
|                     opsgenieAlertsUrl = opsgenieAlertsUrlEU; | ||||
|                     break; | ||||
|                 default: | ||||
|                     opsgenieAlertsUrl = opsgenieAlertsUrlUS; | ||||
|             } | ||||
|  | ||||
|             if (heartbeatJSON == null) { | ||||
|                 let notificationTestAlias = "uptime-kuma-notification-test"; | ||||
|                 let data = { | ||||
|                     "message": msg, | ||||
|                     "alias": notificationTestAlias, | ||||
|                     "source": "Uptime Kuma", | ||||
|                     "priority": "P5" | ||||
|                 }; | ||||
|  | ||||
|                 return this.post(notification, opsgenieAlertsUrl, data); | ||||
|             } | ||||
|  | ||||
|             if (heartbeatJSON.status === DOWN) { | ||||
|                 let data = { | ||||
|                     "message": monitorJSON ? textMsg + `: ${monitorJSON.name}` : textMsg, | ||||
|                     "alias": monitorJSON.name, | ||||
|                     "description": msg, | ||||
|                     "source": "Uptime Kuma", | ||||
|                     "priority": `P${priority}` | ||||
|                 }; | ||||
|  | ||||
|                 return this.post(notification, opsgenieAlertsUrl, data); | ||||
|             } | ||||
|  | ||||
|             if (heartbeatJSON.status === UP) { | ||||
|                 let opsgenieAlertsCloseUrl = `${opsgenieAlertsUrl}/${encodeURIComponent(monitorJSON.name)}/close?identifierType=alias`; | ||||
|                 let data = { | ||||
|                     "source": "Uptime Kuma", | ||||
|                 }; | ||||
|  | ||||
|                 return this.post(notification, opsgenieAlertsCloseUrl, data); | ||||
|             } | ||||
|         } catch (error) { | ||||
|             this.throwGeneralAxiosError(error); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * | ||||
|      * @param {BeanModel} notification | ||||
|      * @param {string} url Request url | ||||
|      * @param {Object} data Request body | ||||
|      * @returns {Promise<string>} | ||||
|      */ | ||||
|     async post(notification, url, data) { | ||||
|         let config = { | ||||
|             headers: { | ||||
|                 "Content-Type": "application/json", | ||||
|                 "Authorization": `GenieKey ${notification.opsgenieApiKey}`, | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         let res = await axios.post(url, data, config); | ||||
|         if (res.status == null) { | ||||
|             return "Opsgenie notification failed with invalid response!"; | ||||
|         } | ||||
|         if (res.status < 200 || res.status >= 300) { | ||||
|             return `Opsgenie notification failed with status code ${res.status}`; | ||||
|         } | ||||
|  | ||||
|         return okMsg; | ||||
|     } | ||||
| } | ||||
|  | ||||
| module.exports = Opsgenie; | ||||
							
								
								
									
										41
									
								
								server/notification-providers/twilio.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								server/notification-providers/twilio.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| const NotificationProvider = require("./notification-provider"); | ||||
| const axios = require("axios"); | ||||
|  | ||||
| class Twilio extends NotificationProvider { | ||||
|  | ||||
|     name = "twilio"; | ||||
|  | ||||
|     async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { | ||||
|  | ||||
|         let okMsg = "Sent Successfully."; | ||||
|  | ||||
|         let accountSID = notification.twilioAccountSID; | ||||
|         let authToken = notification.twilioAuthToken; | ||||
|  | ||||
|         try { | ||||
|  | ||||
|             let config = { | ||||
|                 headers: { | ||||
|                     "Content-Type": "application/x-www-form-urlencoded;charset=utf-8", | ||||
|                     "Authorization": "Basic " + Buffer.from(accountSID + ":" + authToken).toString("base64"), | ||||
|                 } | ||||
|             }; | ||||
|  | ||||
|             let data = new URLSearchParams(); | ||||
|             data.append("To", notification.twilioToNumber); | ||||
|             data.append("From", notification.twilioFromNumber); | ||||
|             data.append("Body", msg); | ||||
|  | ||||
|             let url = "https://api.twilio.com/2010-04-01/Accounts/" + accountSID + "/Messages.json"; | ||||
|  | ||||
|             await axios.post(url, data, config); | ||||
|  | ||||
|             return okMsg; | ||||
|         } catch (error) { | ||||
|             this.throwGeneralAxiosError(error); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| module.exports = Twilio; | ||||
| @@ -23,6 +23,7 @@ const Mattermost = require("./notification-providers/mattermost"); | ||||
| const Ntfy = require("./notification-providers/ntfy"); | ||||
| const Octopush = require("./notification-providers/octopush"); | ||||
| const OneBot = require("./notification-providers/onebot"); | ||||
| const Opsgenie = require("./notification-providers/opsgenie"); | ||||
| const PagerDuty = require("./notification-providers/pagerduty"); | ||||
| const PagerTree = require("./notification-providers/pagertree"); | ||||
| const PromoSMS = require("./notification-providers/promosms"); | ||||
| @@ -41,6 +42,7 @@ const Stackfield = require("./notification-providers/stackfield"); | ||||
| const Teams = require("./notification-providers/teams"); | ||||
| const TechulusPush = require("./notification-providers/techulus-push"); | ||||
| const Telegram = require("./notification-providers/telegram"); | ||||
| const Twilio = require("./notification-providers/twilio"); | ||||
| const Splunk = require("./notification-providers/splunk"); | ||||
| const Webhook = require("./notification-providers/webhook"); | ||||
| const WeCom = require("./notification-providers/wecom"); | ||||
| @@ -83,6 +85,7 @@ class Notification { | ||||
|             new Ntfy(), | ||||
|             new Octopush(), | ||||
|             new OneBot(), | ||||
|             new Opsgenie(), | ||||
|             new PagerDuty(), | ||||
|             new PagerTree(), | ||||
|             new PromoSMS(), | ||||
| @@ -103,6 +106,7 @@ class Notification { | ||||
|             new Teams(), | ||||
|             new TechulusPush(), | ||||
|             new Telegram(), | ||||
|             new Twilio(), | ||||
|             new Splunk(), | ||||
|             new Webhook(), | ||||
|             new WeCom(), | ||||
|   | ||||
| @@ -147,7 +147,11 @@ router.get("/api/badge/:id/status", cache("5 minutes"), async (request, response | ||||
|             const heartbeat = await Monitor.getPreviousHeartbeat(requestedMonitorId); | ||||
|             const state = overrideValue !== undefined ? overrideValue : heartbeat.status; | ||||
|  | ||||
|             badgeValues.label = label ?? "Status"; | ||||
|             if (label === undefined) { | ||||
|                 badgeValues.label = "Status"; | ||||
|             } else { | ||||
|                 badgeValues.label = label; | ||||
|             } | ||||
|             switch (state) { | ||||
|                 case DOWN: | ||||
|                     badgeValues.color = downColor; | ||||
| @@ -224,7 +228,7 @@ router.get("/api/badge/:id/uptime/:duration?", cache("5 minutes"), async (reques | ||||
|             ); | ||||
|  | ||||
|             // limit the displayed uptime percentage to four (two, when displayed as percent) decimal digits | ||||
|             const cleanUptime = parseFloat(uptime.toPrecision(4)); | ||||
|             const cleanUptime = (uptime * 100).toPrecision(4); | ||||
|  | ||||
|             // use a given, custom color or calculate one based on the uptime value | ||||
|             badgeValues.color = color ?? percentageToColor(uptime); | ||||
| @@ -235,7 +239,7 @@ router.get("/api/badge/:id/uptime/:duration?", cache("5 minutes"), async (reques | ||||
|                 labelPrefix, | ||||
|                 label ?? `Uptime (${requestedDuration}${labelSuffix})`, | ||||
|             ]); | ||||
|             badgeValues.message = filterAndJoin([ prefix, `${cleanUptime * 100}`, suffix ]); | ||||
|             badgeValues.message = filterAndJoin([ prefix, cleanUptime, suffix ]); | ||||
|         } | ||||
|  | ||||
|         // build the SVG based on given values | ||||
|   | ||||
| @@ -129,6 +129,7 @@ export default { | ||||
|                 "ntfy": "Ntfy", | ||||
|                 "octopush": "Octopush", | ||||
|                 "OneBot": "OneBot", | ||||
|                 "Opsgenie": "Opsgenie", | ||||
|                 "PagerDuty": "PagerDuty", | ||||
|                 "pushbullet": "Pushbullet", | ||||
|                 "PushByTechulus": "Push by Techulus", | ||||
| @@ -143,6 +144,7 @@ export default { | ||||
|                 "stackfield": "Stackfield", | ||||
|                 "teams": "Microsoft Teams", | ||||
|                 "telegram": "Telegram", | ||||
|                 "twilio": "Twilio", | ||||
|                 "Splunk": "Splunk", | ||||
|                 "webhook": "Webhook", | ||||
|                 "GoAlert": "GoAlert", | ||||
|   | ||||
							
								
								
									
										36
									
								
								src/components/notifications/Opsgenie.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/components/notifications/Opsgenie.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| <template> | ||||
|     <div class="mb-3"> | ||||
|         <label for="opsgenie-region" class="form-label">{{ $t("Region") }}<span style="color: red;"><sup>*</sup></span></label> | ||||
|         <select id="opsgenie-region" v-model="$parent.notification.opsgenieRegion" class="form-select" required> | ||||
|             <option value="us"> | ||||
|                 US (Default) | ||||
|             </option> | ||||
|             <option value="eu"> | ||||
|                 EU | ||||
|             </option> | ||||
|         </select> | ||||
|     </div> | ||||
|     <div class="mb-3"> | ||||
|         <label for="opsgenie-apikey" class="form-label">{{ $t("API Key") }}<span style="color: red;"><sup>*</sup></span></label> | ||||
|         <HiddenInput id="opsgenie-apikey" v-model="$parent.notification.opsgenieApiKey" required="true" autocomplete="false"></HiddenInput> | ||||
|     </div> | ||||
|     <div class="mb-3"> | ||||
|         <label for="opsgenie-priority" class="form-label">{{ $t("Priority") }}</label> | ||||
|         <input id="opsgenie-priority" v-model="$parent.notification.opsgeniePriority" type="number" class="form-control" min="1" max="5" step="1"> | ||||
|     </div> | ||||
|     <div class="form-text"> | ||||
|         <span style="color: red;"><sup>*</sup></span>{{ $t("Required") }} | ||||
|         <i18n-t tag="p" keypath="aboutWebhooks" style="margin-top: 8px;"> | ||||
|             <a href="https://docs.opsgenie.com/docs/alert-api" target="_blank">https://docs.opsgenie.com/docs/alert-api</a> | ||||
|         </i18n-t> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import HiddenInput from "../HiddenInput.vue"; | ||||
| export default { | ||||
|     components: { | ||||
|         HiddenInput, | ||||
|     }, | ||||
| }; | ||||
| </script> | ||||
							
								
								
									
										27
									
								
								src/components/notifications/Twilio.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/components/notifications/Twilio.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| <template> | ||||
|     <div class="mb-3"> | ||||
|         <label for="twilio-account-sid" class="form-label">{{ $t("Account SID") }}</label> | ||||
|         <input id="twilio-account-sid" v-model="$parent.notification.twilioAccountSID" type="text" class="form-control" required> | ||||
|     </div> | ||||
|  | ||||
|     <div class="mb-3"> | ||||
|         <label for="twilio-auth-token" class="form-label">{{ $t("Auth Token") }}</label> | ||||
|         <input id="twilio-auth-token" v-model="$parent.notification.twilioAuthToken" type="text" class="form-control" required> | ||||
|     </div> | ||||
|  | ||||
|     <div class="mb-3"> | ||||
|         <label for="twilio-from-number" class="form-label">{{ $t("From Number") }}</label> | ||||
|         <input id="twilio-from-number" v-model="$parent.notification.twilioFromNumber" type="text" class="form-control" required> | ||||
|     </div> | ||||
|  | ||||
|     <div class="mb-3"> | ||||
|         <label for="twilio-to-number" class="form-label">{{ $t("To Number") }}</label> | ||||
|         <input id="twilio-to-number" v-model="$parent.notification.twilioToNumber" type="text" class="form-control" required> | ||||
|     </div> | ||||
|  | ||||
|     <div class="mb-3"> | ||||
|         <i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;"> | ||||
|             <a href="https://www.twilio.com/docs/sms" target="_blank">https://www.twilio.com/docs/sms</a> | ||||
|         </i18n-t> | ||||
|     </div> | ||||
| </template> | ||||
| @@ -21,6 +21,7 @@ import Mattermost from "./Mattermost.vue"; | ||||
| import Ntfy from "./Ntfy.vue"; | ||||
| import Octopush from "./Octopush.vue"; | ||||
| import OneBot from "./OneBot.vue"; | ||||
| import Opsgenie from "./Opsgenie.vue"; | ||||
| import PagerDuty from "./PagerDuty.vue"; | ||||
| import PagerTree from "./PagerTree.vue"; | ||||
| import PromoSMS from "./PromoSMS.vue"; | ||||
| @@ -41,6 +42,7 @@ import STMP from "./SMTP.vue"; | ||||
| import Teams from "./Teams.vue"; | ||||
| import TechulusPush from "./TechulusPush.vue"; | ||||
| import Telegram from "./Telegram.vue"; | ||||
| import Twilio from "./Twilio.vue"; | ||||
| import Webhook from "./Webhook.vue"; | ||||
| import WeCom from "./WeCom.vue"; | ||||
| import GoAlert from "./GoAlert.vue"; | ||||
| @@ -76,6 +78,7 @@ const NotificationFormList = { | ||||
|     "ntfy": Ntfy, | ||||
|     "octopush": Octopush, | ||||
|     "OneBot": OneBot, | ||||
|     "Opsgenie": Opsgenie, | ||||
|     "PagerDuty": PagerDuty, | ||||
|     "PagerTree": PagerTree, | ||||
|     "promosms": PromoSMS, | ||||
| @@ -95,6 +98,7 @@ const NotificationFormList = { | ||||
|     "stackfield": Stackfield, | ||||
|     "teams": Teams, | ||||
|     "telegram": Telegram, | ||||
|     "twilio": Twilio, | ||||
|     "Splunk": Splunk, | ||||
|     "webhook": Webhook, | ||||
|     "WeCom": WeCom, | ||||
|   | ||||
| @@ -707,5 +707,9 @@ | ||||
|     "wayToGetPagerTreeIntegrationURL": "After creating the Uptime Kuma integration in PagerTree, copy the Endpoint. See full details {0}", | ||||
|     "lunaseaTarget": "Target", | ||||
|     "lunaseaDeviceID": "Device ID", | ||||
|     "lunaseaUserID": "User ID" | ||||
|     "lunaseaUserID": "User ID", | ||||
|     "twilioAccountSID": "Account SID", | ||||
|     "twilioAuthToken": "Auth Token", | ||||
|     "twilioFromNumber": "From Number", | ||||
|     "twilioToNumber": "To Number" | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user