Skip to content

Commit

Permalink
Merge branch 'master' into ntfy-bearer-authorization
Browse files Browse the repository at this point in the history
  • Loading branch information
sharknoon authored Mar 26, 2023
2 parents df47609 + fbdeb30 commit fc4312c
Show file tree
Hide file tree
Showing 17 changed files with 315 additions and 28 deletions.
6 changes: 6 additions & 0 deletions .github/ISSUE_TEMPLATE/ask-for-help.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
6 changes: 2 additions & 4 deletions server/model/monitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -637,9 +637,7 @@ class Monitor extends BeanModel {
} else if (this.type === "mysql") {
let startTime = dayjs().valueOf();

await mysqlQuery(this.databaseConnectionString, this.databaseQuery);

bean.msg = "";
bean.msg = await mysqlQuery(this.databaseConnectionString, this.databaseQuery);
bean.status = UP;
bean.ping = dayjs().valueOf() - startTime;
} else if (this.type === "mongodb") {
Expand Down Expand Up @@ -1247,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,
Expand Down
97 changes: 97 additions & 0 deletions server/notification-providers/opsgenie.js
Original file line number Diff line number Diff line change
@@ -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 changes: 41 additions & 0 deletions server/notification-providers/twilio.js
Original file line number Diff line number Diff line change
@@ -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;
4 changes: 4 additions & 0 deletions server/notification.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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");
Expand Down Expand Up @@ -83,6 +85,7 @@ class Notification {
new Ntfy(),
new Octopush(),
new OneBot(),
new Opsgenie(),
new PagerDuty(),
new PagerTree(),
new PromoSMS(),
Expand All @@ -103,6 +106,7 @@ class Notification {
new Teams(),
new TechulusPush(),
new Telegram(),
new Twilio(),
new Splunk(),
new Webhook(),
new WeCom(),
Expand Down
10 changes: 7 additions & 3 deletions server/routers/api-router.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand Down
4 changes: 3 additions & 1 deletion server/uptime-kuma-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,16 @@ class UptimeKumaServer {
// SSL
const sslKey = args["ssl-key"] || process.env.UPTIME_KUMA_SSL_KEY || process.env.SSL_KEY || undefined;
const sslCert = args["ssl-cert"] || process.env.UPTIME_KUMA_SSL_CERT || process.env.SSL_CERT || undefined;
const sslKeyPassphrase = args["ssl-key-passphrase"] || process.env.UPTIME_KUMA_SSL_KEY_PASSPHRASE || process.env.SSL_KEY_PASSPHRASE || undefined;

log.info("server", "Creating express and socket.io instance");
this.app = express();
if (sslKey && sslCert) {
log.info("server", "Server Type: HTTPS");
this.httpServer = https.createServer({
key: fs.readFileSync(sslKey),
cert: fs.readFileSync(sslCert)
cert: fs.readFileSync(sslCert),
passphrase: sslKeyPassphrase,
}, this.app);
} else {
log.info("server", "Server Type: HTTP");
Expand Down
27 changes: 17 additions & 10 deletions server/util-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -322,21 +322,28 @@ exports.postgresQuery = function (connectionString, query) {
* Run a query on MySQL/MariaDB
* @param {string} connectionString The database connection string
* @param {string} query The query to validate the database with
* @returns {Promise<(string[]|Object[]|Object)>}
* @returns {Promise<(string)>}
*/
exports.mysqlQuery = function (connectionString, query) {
return new Promise((resolve, reject) => {
const connection = mysql.createConnection(connectionString);
connection.promise().query(query)
.then(res => {
resolve(res);
})
.catch(err => {

connection.on("error", (err) => {
reject(err);
});

connection.query(query, (err, res) => {
if (err) {
reject(err);
})
.finally(() => {
connection.destroy();
});
} else {
if (Array.isArray(res)) {
resolve("Rows: " + res.length);
} else {
resolve("No Error, but the result is not an array. Type: " + typeof res);
}
}
connection.destroy();
});
});
};

Expand Down
10 changes: 10 additions & 0 deletions src/components/APIKeyDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,16 @@ export default {
}
});
},
/** Clear Form inputs */
clearForm() {
this.key = {
name: "",
expires: this.minDate,
active: 1,
};
this.noExpire = false;
},
}
};
</script>
Expand Down
12 changes: 5 additions & 7 deletions src/components/CopyableInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
:disabled="disabled"
>

<!-- A hidden textarea for copying text on non-https -->
<textarea ref="hiddenTextarea" style="position: fixed; left: -999999px; top: -999999px;"></textarea>

<a class="btn btn-outline-primary" @click="copyToClipboard(model)">
<font-awesome-icon :icon="icon" />
</a>
Expand Down Expand Up @@ -111,24 +114,19 @@ export default {
}, 3000);
// navigator clipboard api needs a secure context (https)
// For http, use the text area method (else part)
if (navigator.clipboard && window.isSecureContext) {
// navigator clipboard api method'
return navigator.clipboard.writeText(textToCopy);
} else {
// text area method
let textArea = document.createElement("textarea");
let textArea = this.$refs.hiddenTextarea;
textArea.value = textToCopy;
// make the textarea out of viewport
textArea.style.position = "fixed";
textArea.style.left = "-999999px";
textArea.style.top = "-999999px";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
return new Promise((res, rej) => {
// here the magic happens
document.execCommand("copy") ? res() : rej();
textArea.remove();
});
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/components/NotificationDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ export default {
"ntfy": "Ntfy",
"octopush": "Octopush",
"OneBot": "OneBot",
"Opsgenie": "Opsgenie",
"PagerDuty": "PagerDuty",
"pushbullet": "Pushbullet",
"PushByTechulus": "Push by Techulus",
Expand All @@ -143,6 +144,7 @@ export default {
"stackfield": "Stackfield",
"teams": "Microsoft Teams",
"telegram": "Telegram",
"twilio": "Twilio",
"Splunk": "Splunk",
"webhook": "Webhook",
"GoAlert": "GoAlert",
Expand Down
36 changes: 36 additions & 0 deletions src/components/notifications/Opsgenie.vue
Original file line number Diff line number Diff line change
@@ -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>
Loading

0 comments on commit fc4312c

Please sign in to comment.