mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-12-18 04:04:41 -05:00
Merge branch 'master' into ntfy-bearer-authorization
This commit is contained in:
commit
fc4312ca1a
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"
|
label: "📝 Describe your problem"
|
||||||
description: "Please walk us through it step by step."
|
description: "Please walk us through it step by step."
|
||||||
placeholder: "Describe what are you asking for..."
|
placeholder: "Describe what are you asking for..."
|
||||||
|
- type: textarea
|
||||||
|
id: error-msg
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
attributes:
|
||||||
|
label: "📝 Error Message(s) or Log"
|
||||||
- type: input
|
- type: input
|
||||||
id: uptime-kuma-version
|
id: uptime-kuma-version
|
||||||
attributes:
|
attributes:
|
||||||
|
@ -637,9 +637,7 @@ class Monitor extends BeanModel {
|
|||||||
} else if (this.type === "mysql") {
|
} else if (this.type === "mysql") {
|
||||||
let startTime = dayjs().valueOf();
|
let startTime = dayjs().valueOf();
|
||||||
|
|
||||||
await mysqlQuery(this.databaseConnectionString, this.databaseQuery);
|
bean.msg = await mysqlQuery(this.databaseConnectionString, this.databaseQuery);
|
||||||
|
|
||||||
bean.msg = "";
|
|
||||||
bean.status = UP;
|
bean.status = UP;
|
||||||
bean.ping = dayjs().valueOf() - startTime;
|
bean.ping = dayjs().valueOf() - startTime;
|
||||||
} else if (this.type === "mongodb") {
|
} else if (this.type === "mongodb") {
|
||||||
@ -1247,7 +1245,7 @@ class Monitor extends BeanModel {
|
|||||||
|
|
||||||
if (notificationList.length > 0) {
|
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",
|
"certificate",
|
||||||
this.id,
|
this.id,
|
||||||
targetDays,
|
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 Ntfy = require("./notification-providers/ntfy");
|
||||||
const Octopush = require("./notification-providers/octopush");
|
const Octopush = require("./notification-providers/octopush");
|
||||||
const OneBot = require("./notification-providers/onebot");
|
const OneBot = require("./notification-providers/onebot");
|
||||||
|
const Opsgenie = require("./notification-providers/opsgenie");
|
||||||
const PagerDuty = require("./notification-providers/pagerduty");
|
const PagerDuty = require("./notification-providers/pagerduty");
|
||||||
const PagerTree = require("./notification-providers/pagertree");
|
const PagerTree = require("./notification-providers/pagertree");
|
||||||
const PromoSMS = require("./notification-providers/promosms");
|
const PromoSMS = require("./notification-providers/promosms");
|
||||||
@ -41,6 +42,7 @@ const Stackfield = require("./notification-providers/stackfield");
|
|||||||
const Teams = require("./notification-providers/teams");
|
const Teams = require("./notification-providers/teams");
|
||||||
const TechulusPush = require("./notification-providers/techulus-push");
|
const TechulusPush = require("./notification-providers/techulus-push");
|
||||||
const Telegram = require("./notification-providers/telegram");
|
const Telegram = require("./notification-providers/telegram");
|
||||||
|
const Twilio = require("./notification-providers/twilio");
|
||||||
const Splunk = require("./notification-providers/splunk");
|
const Splunk = require("./notification-providers/splunk");
|
||||||
const Webhook = require("./notification-providers/webhook");
|
const Webhook = require("./notification-providers/webhook");
|
||||||
const WeCom = require("./notification-providers/wecom");
|
const WeCom = require("./notification-providers/wecom");
|
||||||
@ -83,6 +85,7 @@ class Notification {
|
|||||||
new Ntfy(),
|
new Ntfy(),
|
||||||
new Octopush(),
|
new Octopush(),
|
||||||
new OneBot(),
|
new OneBot(),
|
||||||
|
new Opsgenie(),
|
||||||
new PagerDuty(),
|
new PagerDuty(),
|
||||||
new PagerTree(),
|
new PagerTree(),
|
||||||
new PromoSMS(),
|
new PromoSMS(),
|
||||||
@ -103,6 +106,7 @@ class Notification {
|
|||||||
new Teams(),
|
new Teams(),
|
||||||
new TechulusPush(),
|
new TechulusPush(),
|
||||||
new Telegram(),
|
new Telegram(),
|
||||||
|
new Twilio(),
|
||||||
new Splunk(),
|
new Splunk(),
|
||||||
new Webhook(),
|
new Webhook(),
|
||||||
new WeCom(),
|
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 heartbeat = await Monitor.getPreviousHeartbeat(requestedMonitorId);
|
||||||
const state = overrideValue !== undefined ? overrideValue : heartbeat.status;
|
const state = overrideValue !== undefined ? overrideValue : heartbeat.status;
|
||||||
|
|
||||||
badgeValues.label = label ?? "Status";
|
if (label === undefined) {
|
||||||
|
badgeValues.label = "Status";
|
||||||
|
} else {
|
||||||
|
badgeValues.label = label;
|
||||||
|
}
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case DOWN:
|
case DOWN:
|
||||||
badgeValues.color = downColor;
|
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
|
// 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
|
// use a given, custom color or calculate one based on the uptime value
|
||||||
badgeValues.color = color ?? percentageToColor(uptime);
|
badgeValues.color = color ?? percentageToColor(uptime);
|
||||||
@ -235,7 +239,7 @@ router.get("/api/badge/:id/uptime/:duration?", cache("5 minutes"), async (reques
|
|||||||
labelPrefix,
|
labelPrefix,
|
||||||
label ?? `Uptime (${requestedDuration}${labelSuffix})`,
|
label ?? `Uptime (${requestedDuration}${labelSuffix})`,
|
||||||
]);
|
]);
|
||||||
badgeValues.message = filterAndJoin([ prefix, `${cleanUptime * 100}`, suffix ]);
|
badgeValues.message = filterAndJoin([ prefix, cleanUptime, suffix ]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// build the SVG based on given values
|
// build the SVG based on given values
|
||||||
|
@ -74,6 +74,7 @@ class UptimeKumaServer {
|
|||||||
// SSL
|
// SSL
|
||||||
const sslKey = args["ssl-key"] || process.env.UPTIME_KUMA_SSL_KEY || process.env.SSL_KEY || undefined;
|
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 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");
|
log.info("server", "Creating express and socket.io instance");
|
||||||
this.app = express();
|
this.app = express();
|
||||||
@ -81,7 +82,8 @@ class UptimeKumaServer {
|
|||||||
log.info("server", "Server Type: HTTPS");
|
log.info("server", "Server Type: HTTPS");
|
||||||
this.httpServer = https.createServer({
|
this.httpServer = https.createServer({
|
||||||
key: fs.readFileSync(sslKey),
|
key: fs.readFileSync(sslKey),
|
||||||
cert: fs.readFileSync(sslCert)
|
cert: fs.readFileSync(sslCert),
|
||||||
|
passphrase: sslKeyPassphrase,
|
||||||
}, this.app);
|
}, this.app);
|
||||||
} else {
|
} else {
|
||||||
log.info("server", "Server Type: HTTP");
|
log.info("server", "Server Type: HTTP");
|
||||||
|
@ -322,19 +322,26 @@ exports.postgresQuery = function (connectionString, query) {
|
|||||||
* Run a query on MySQL/MariaDB
|
* Run a query on MySQL/MariaDB
|
||||||
* @param {string} connectionString The database connection string
|
* @param {string} connectionString The database connection string
|
||||||
* @param {string} query The query to validate the database with
|
* @param {string} query The query to validate the database with
|
||||||
* @returns {Promise<(string[]|Object[]|Object)>}
|
* @returns {Promise<(string)>}
|
||||||
*/
|
*/
|
||||||
exports.mysqlQuery = function (connectionString, query) {
|
exports.mysqlQuery = function (connectionString, query) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const connection = mysql.createConnection(connectionString);
|
const connection = mysql.createConnection(connectionString);
|
||||||
connection.promise().query(query)
|
|
||||||
.then(res => {
|
connection.on("error", (err) => {
|
||||||
resolve(res);
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
reject(err);
|
reject(err);
|
||||||
})
|
});
|
||||||
.finally(() => {
|
|
||||||
|
connection.query(query, (err, res) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} 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();
|
connection.destroy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -159,6 +159,16 @@ export default {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Clear Form inputs */
|
||||||
|
clearForm() {
|
||||||
|
this.key = {
|
||||||
|
name: "",
|
||||||
|
expires: this.minDate,
|
||||||
|
active: 1,
|
||||||
|
};
|
||||||
|
this.noExpire = false;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -13,6 +13,9 @@
|
|||||||
:disabled="disabled"
|
: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)">
|
<a class="btn btn-outline-primary" @click="copyToClipboard(model)">
|
||||||
<font-awesome-icon :icon="icon" />
|
<font-awesome-icon :icon="icon" />
|
||||||
</a>
|
</a>
|
||||||
@ -111,24 +114,19 @@ export default {
|
|||||||
}, 3000);
|
}, 3000);
|
||||||
|
|
||||||
// navigator clipboard api needs a secure context (https)
|
// navigator clipboard api needs a secure context (https)
|
||||||
|
// For http, use the text area method (else part)
|
||||||
if (navigator.clipboard && window.isSecureContext) {
|
if (navigator.clipboard && window.isSecureContext) {
|
||||||
// navigator clipboard api method'
|
// navigator clipboard api method'
|
||||||
return navigator.clipboard.writeText(textToCopy);
|
return navigator.clipboard.writeText(textToCopy);
|
||||||
} else {
|
} else {
|
||||||
// text area method
|
// text area method
|
||||||
let textArea = document.createElement("textarea");
|
let textArea = this.$refs.hiddenTextarea;
|
||||||
textArea.value = textToCopy;
|
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.focus();
|
||||||
textArea.select();
|
textArea.select();
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
// here the magic happens
|
// here the magic happens
|
||||||
document.execCommand("copy") ? res() : rej();
|
document.execCommand("copy") ? res() : rej();
|
||||||
textArea.remove();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,6 +129,7 @@ export default {
|
|||||||
"ntfy": "Ntfy",
|
"ntfy": "Ntfy",
|
||||||
"octopush": "Octopush",
|
"octopush": "Octopush",
|
||||||
"OneBot": "OneBot",
|
"OneBot": "OneBot",
|
||||||
|
"Opsgenie": "Opsgenie",
|
||||||
"PagerDuty": "PagerDuty",
|
"PagerDuty": "PagerDuty",
|
||||||
"pushbullet": "Pushbullet",
|
"pushbullet": "Pushbullet",
|
||||||
"PushByTechulus": "Push by Techulus",
|
"PushByTechulus": "Push by Techulus",
|
||||||
@ -143,6 +144,7 @@ export default {
|
|||||||
"stackfield": "Stackfield",
|
"stackfield": "Stackfield",
|
||||||
"teams": "Microsoft Teams",
|
"teams": "Microsoft Teams",
|
||||||
"telegram": "Telegram",
|
"telegram": "Telegram",
|
||||||
|
"twilio": "Twilio",
|
||||||
"Splunk": "Splunk",
|
"Splunk": "Splunk",
|
||||||
"webhook": "Webhook",
|
"webhook": "Webhook",
|
||||||
"GoAlert": "GoAlert",
|
"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 Ntfy from "./Ntfy.vue";
|
||||||
import Octopush from "./Octopush.vue";
|
import Octopush from "./Octopush.vue";
|
||||||
import OneBot from "./OneBot.vue";
|
import OneBot from "./OneBot.vue";
|
||||||
|
import Opsgenie from "./Opsgenie.vue";
|
||||||
import PagerDuty from "./PagerDuty.vue";
|
import PagerDuty from "./PagerDuty.vue";
|
||||||
import PagerTree from "./PagerTree.vue";
|
import PagerTree from "./PagerTree.vue";
|
||||||
import PromoSMS from "./PromoSMS.vue";
|
import PromoSMS from "./PromoSMS.vue";
|
||||||
@ -41,6 +42,7 @@ import STMP from "./SMTP.vue";
|
|||||||
import Teams from "./Teams.vue";
|
import Teams from "./Teams.vue";
|
||||||
import TechulusPush from "./TechulusPush.vue";
|
import TechulusPush from "./TechulusPush.vue";
|
||||||
import Telegram from "./Telegram.vue";
|
import Telegram from "./Telegram.vue";
|
||||||
|
import Twilio from "./Twilio.vue";
|
||||||
import Webhook from "./Webhook.vue";
|
import Webhook from "./Webhook.vue";
|
||||||
import WeCom from "./WeCom.vue";
|
import WeCom from "./WeCom.vue";
|
||||||
import GoAlert from "./GoAlert.vue";
|
import GoAlert from "./GoAlert.vue";
|
||||||
@ -76,6 +78,7 @@ const NotificationFormList = {
|
|||||||
"ntfy": Ntfy,
|
"ntfy": Ntfy,
|
||||||
"octopush": Octopush,
|
"octopush": Octopush,
|
||||||
"OneBot": OneBot,
|
"OneBot": OneBot,
|
||||||
|
"Opsgenie": Opsgenie,
|
||||||
"PagerDuty": PagerDuty,
|
"PagerDuty": PagerDuty,
|
||||||
"PagerTree": PagerTree,
|
"PagerTree": PagerTree,
|
||||||
"promosms": PromoSMS,
|
"promosms": PromoSMS,
|
||||||
@ -95,6 +98,7 @@ const NotificationFormList = {
|
|||||||
"stackfield": Stackfield,
|
"stackfield": Stackfield,
|
||||||
"teams": Teams,
|
"teams": Teams,
|
||||||
"telegram": Telegram,
|
"telegram": Telegram,
|
||||||
|
"twilio": Twilio,
|
||||||
"Splunk": Splunk,
|
"Splunk": Splunk,
|
||||||
"webhook": Webhook,
|
"webhook": Webhook,
|
||||||
"WeCom": WeCom,
|
"WeCom": WeCom,
|
||||||
|
@ -174,6 +174,7 @@
|
|||||||
"Avg. Response": "Avg. Response",
|
"Avg. Response": "Avg. Response",
|
||||||
"Entry Page": "Entry Page",
|
"Entry Page": "Entry Page",
|
||||||
"statusPageNothing": "Nothing here, please add a group or a monitor.",
|
"statusPageNothing": "Nothing here, please add a group or a monitor.",
|
||||||
|
"statusPageRefreshIn": "Refresh in: {0}",
|
||||||
"No Services": "No Services",
|
"No Services": "No Services",
|
||||||
"All Systems Operational": "All Systems Operational",
|
"All Systems Operational": "All Systems Operational",
|
||||||
"Partially Degraded Service": "Partially Degraded Service",
|
"Partially Degraded Service": "Partially Degraded Service",
|
||||||
@ -708,5 +709,9 @@
|
|||||||
"lunaseaDeviceID": "Device ID",
|
"lunaseaDeviceID": "Device ID",
|
||||||
"lunaseaUserID": "User ID",
|
"lunaseaUserID": "User ID",
|
||||||
"AuthenticationMethod": "Authentication Method",
|
"AuthenticationMethod": "Authentication Method",
|
||||||
"UsernameAndPassword": "Username and Password"
|
"UsernameAndPassword": "Username and Password",
|
||||||
|
"twilioAccountSID": "Account SID",
|
||||||
|
"twilioAuthToken": "Auth Token",
|
||||||
|
"twilioFromNumber": "From Number",
|
||||||
|
"twilioToNumber": "To Number"
|
||||||
}
|
}
|
||||||
|
@ -944,6 +944,14 @@ message HealthCheckResponse {
|
|||||||
} else if (this.isEdit || this.isClone) {
|
} else if (this.isEdit || this.isClone) {
|
||||||
this.$root.getSocket().emit("getMonitor", this.$route.params.id, (res) => {
|
this.$root.getSocket().emit("getMonitor", this.$route.params.id, (res) => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
|
|
||||||
|
if (this.isClone) {
|
||||||
|
// Reset push token for cloned monitors
|
||||||
|
if (res.monitor.type === "push") {
|
||||||
|
res.monitor.pushToken = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.monitor = res.monitor;
|
this.monitor = res.monitor;
|
||||||
|
|
||||||
if (this.isClone) {
|
if (this.isClone) {
|
||||||
|
@ -306,6 +306,11 @@
|
|||||||
<p v-if="config.showPoweredBy">
|
<p v-if="config.showPoweredBy">
|
||||||
{{ $t("Powered by") }} <a target="_blank" rel="noopener noreferrer" href="https://github.com/louislam/uptime-kuma">{{ $t("Uptime Kuma" ) }}</a>
|
{{ $t("Powered by") }} <a target="_blank" rel="noopener noreferrer" href="https://github.com/louislam/uptime-kuma">{{ $t("Uptime Kuma" ) }}</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<div class="refresh-info mb-2">
|
||||||
|
<div>{{ $t("Last Updated") }}: <date-time :value="lastUpdateTime" /></div>
|
||||||
|
<div>{{ $tc("statusPageRefreshIn", [ updateCountdownText]) }}</div>
|
||||||
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -322,6 +327,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import duration from "dayjs/plugin/duration";
|
||||||
import Favico from "favico.js";
|
import Favico from "favico.js";
|
||||||
// import highlighting library (you can use any library you want just return html string)
|
// import highlighting library (you can use any library you want just return html string)
|
||||||
import { highlight, languages } from "prismjs/components/prism-core";
|
import { highlight, languages } from "prismjs/components/prism-core";
|
||||||
@ -337,10 +343,12 @@ import DOMPurify from "dompurify";
|
|||||||
import Confirm from "../components/Confirm.vue";
|
import Confirm from "../components/Confirm.vue";
|
||||||
import PublicGroupList from "../components/PublicGroupList.vue";
|
import PublicGroupList from "../components/PublicGroupList.vue";
|
||||||
import MaintenanceTime from "../components/MaintenanceTime.vue";
|
import MaintenanceTime from "../components/MaintenanceTime.vue";
|
||||||
|
import DateTime from "../components/Datetime.vue";
|
||||||
import { getResBaseURL } from "../util-frontend";
|
import { getResBaseURL } from "../util-frontend";
|
||||||
import { STATUS_PAGE_ALL_DOWN, STATUS_PAGE_ALL_UP, STATUS_PAGE_MAINTENANCE, STATUS_PAGE_PARTIAL_DOWN, UP, MAINTENANCE } from "../util.ts";
|
import { STATUS_PAGE_ALL_DOWN, STATUS_PAGE_ALL_UP, STATUS_PAGE_MAINTENANCE, STATUS_PAGE_PARTIAL_DOWN, UP, MAINTENANCE } from "../util.ts";
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
dayjs.extend(duration);
|
||||||
|
|
||||||
const leavePageMsg = "Do you really want to leave? you have unsaved changes!";
|
const leavePageMsg = "Do you really want to leave? you have unsaved changes!";
|
||||||
|
|
||||||
@ -359,6 +367,7 @@ export default {
|
|||||||
Confirm,
|
Confirm,
|
||||||
PrismEditor,
|
PrismEditor,
|
||||||
MaintenanceTime,
|
MaintenanceTime,
|
||||||
|
DateTime,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Leave Page for vue route change
|
// Leave Page for vue route change
|
||||||
@ -400,6 +409,10 @@ export default {
|
|||||||
baseURL: "",
|
baseURL: "",
|
||||||
clickedEditButton: false,
|
clickedEditButton: false,
|
||||||
maintenanceList: [],
|
maintenanceList: [],
|
||||||
|
autoRefreshInterval: 5,
|
||||||
|
lastUpdateTime: dayjs(),
|
||||||
|
updateCountdown: null,
|
||||||
|
updateCountdownText: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -637,11 +650,13 @@ export default {
|
|||||||
console.log(error);
|
console.log(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 5mins a loop
|
// Configure auto-refresh loop
|
||||||
this.updateHeartbeatList();
|
this.updateHeartbeatList();
|
||||||
feedInterval = setInterval(() => {
|
feedInterval = setInterval(() => {
|
||||||
this.updateHeartbeatList();
|
this.updateHeartbeatList();
|
||||||
}, (300 + 10) * 1000);
|
}, (this.autoRefreshInterval * 60 + 10) * 1000);
|
||||||
|
|
||||||
|
this.updateUpdateTimer();
|
||||||
|
|
||||||
// Go to edit page if ?edit present
|
// Go to edit page if ?edit present
|
||||||
// null means ?edit present, but no value
|
// null means ?edit present, but no value
|
||||||
@ -700,10 +715,29 @@ export default {
|
|||||||
favicon.badge(downMonitors);
|
favicon.badge(downMonitors);
|
||||||
|
|
||||||
this.loadedData = true;
|
this.loadedData = true;
|
||||||
|
this.lastUpdateTime = dayjs();
|
||||||
|
this.updateUpdateTimer();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup timer to display countdown to refresh
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
updateUpdateTimer() {
|
||||||
|
clearInterval(this.updateCountdown);
|
||||||
|
|
||||||
|
this.updateCountdown = setInterval(() => {
|
||||||
|
const countdown = dayjs.duration(this.lastUpdateTime.add(this.autoRefreshInterval, "minutes").add(10, "seconds").diff(dayjs()));
|
||||||
|
if (countdown.as("seconds") < 0) {
|
||||||
|
clearInterval(this.updateCountdown);
|
||||||
|
} else {
|
||||||
|
this.updateCountdownText = countdown.format("mm:ss");
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
|
||||||
/** Enable editing mode */
|
/** Enable editing mode */
|
||||||
edit() {
|
edit() {
|
||||||
if (this.hasToken) {
|
if (this.hasToken) {
|
||||||
@ -1118,4 +1152,8 @@ footer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.refresh-info {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
Loading…
Reference in New Issue
Block a user