Compare commits
8 Commits
d69799f9b9
...
15d5d3a8f0
Author | SHA1 | Date |
---|---|---|
Frank Elsinga | 15d5d3a8f0 | |
Frank Elsinga | dbbc79a05a | |
Rakovskij Stanislav | 9f2cf28a76 | |
Ezhil Shanmugham | 988ba79679 | |
Joschua Becker | 19e8c75c3b | |
凯观生活 | 126d6cd912 | |
Frank Elsinga | 48abd79005 | |
Frank Elsinga | 3a89624d32 |
|
@ -43,11 +43,18 @@ It is a temporary live demo, all data will be deleted after 10 minutes. Sponsore
|
|||
docker run -d --restart=always -p 3001:3001 -v uptime-kuma:/app/data --name uptime-kuma louislam/uptime-kuma:1
|
||||
```
|
||||
|
||||
Uptime Kuma is now running on http://localhost:3001
|
||||
Uptime Kuma is now running on <http://0.0.0.0:3001>.
|
||||
|
||||
> [!WARNING]
|
||||
> File Systems like **NFS** (Network File System) are **NOT** supported. Please map to a local directory or volume.
|
||||
|
||||
> [!NOTE]
|
||||
> If you want to limit exposure to localhost (without exposing port for other users or to use a [reverse proxy](https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy)), you can expose the port like this:
|
||||
>
|
||||
> ```bash
|
||||
> docker run -d --restart=always -p 127.0.0.1:3001:3001 -v uptime-kuma:/app/data --name uptime-kuma louislam/uptime-kuma:1
|
||||
> ```
|
||||
|
||||
### 💪🏻 Non-Docker
|
||||
|
||||
Requirements:
|
||||
|
|
|
@ -386,39 +386,6 @@ class Monitor extends BeanModel {
|
|||
if (await Monitor.isUnderMaintenance(this.id)) {
|
||||
bean.msg = "Monitor under maintenance";
|
||||
bean.status = MAINTENANCE;
|
||||
} else if (this.type === "group") {
|
||||
const children = await Monitor.getChildren(this.id);
|
||||
|
||||
if (children.length > 0) {
|
||||
bean.status = UP;
|
||||
bean.msg = "All children up and running";
|
||||
for (const child of children) {
|
||||
if (!child.active) {
|
||||
// Ignore inactive childs
|
||||
continue;
|
||||
}
|
||||
const lastBeat = await Monitor.getPreviousHeartbeat(child.id);
|
||||
|
||||
// Only change state if the monitor is in worse conditions then the ones before
|
||||
// lastBeat.status could be null
|
||||
if (!lastBeat) {
|
||||
bean.status = PENDING;
|
||||
} else if (bean.status === UP && (lastBeat.status === PENDING || lastBeat.status === DOWN)) {
|
||||
bean.status = lastBeat.status;
|
||||
} else if (bean.status === PENDING && lastBeat.status === DOWN) {
|
||||
bean.status = lastBeat.status;
|
||||
}
|
||||
}
|
||||
|
||||
if (bean.status !== UP) {
|
||||
bean.msg = "Child inaccessible";
|
||||
}
|
||||
} else {
|
||||
// Set status pending if group is empty
|
||||
bean.status = PENDING;
|
||||
bean.msg = "Group empty";
|
||||
}
|
||||
|
||||
} else if (this.type === "http" || this.type === "keyword" || this.type === "json-query") {
|
||||
// Do not do any queries/high loading things before the "bean.ping"
|
||||
let startTime = dayjs().valueOf();
|
||||
|
@ -1523,7 +1490,7 @@ class Monitor extends BeanModel {
|
|||
/**
|
||||
* Gets all Children of the monitor
|
||||
* @param {number} monitorID ID of monitor to get
|
||||
* @returns {Promise<LooseObject<any>>} Children
|
||||
* @returns {Promise<LooseObject<any>[]>} Children
|
||||
*/
|
||||
static async getChildren(monitorID) {
|
||||
return await R.getAll(`
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
const { UP, PENDING, DOWN } = require("../../src/util");
|
||||
const { MonitorType } = require("./monitor-type");
|
||||
const Monitor = require("../model/monitor");
|
||||
|
||||
class GroupMonitorType extends MonitorType {
|
||||
name = "group";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async check(monitor, heartbeat, _server) {
|
||||
const children = await Monitor.getChildren(monitor.id);
|
||||
|
||||
if (children.length > 0) {
|
||||
heartbeat.status = UP;
|
||||
heartbeat.msg = "All children up and running";
|
||||
for (const child of children) {
|
||||
if (!child.active) {
|
||||
// Ignore inactive childs
|
||||
continue;
|
||||
}
|
||||
const lastBeat = await Monitor.getPreviousHeartbeat(child.id);
|
||||
|
||||
// Only change state if the monitor is in worse conditions then the ones before
|
||||
// lastBeat.status could be null
|
||||
if (!lastBeat) {
|
||||
heartbeat.status = PENDING;
|
||||
} else if (heartbeat.status === UP && (lastBeat.status === PENDING || lastBeat.status === DOWN)) {
|
||||
heartbeat.status = lastBeat.status;
|
||||
} else if (heartbeat.status === PENDING && lastBeat.status === DOWN) {
|
||||
heartbeat.status = lastBeat.status;
|
||||
}
|
||||
}
|
||||
|
||||
if (heartbeat.status !== UP) {
|
||||
heartbeat.msg = "Child inaccessible";
|
||||
}
|
||||
} else {
|
||||
// Set status pending if group is empty
|
||||
heartbeat.status = PENDING;
|
||||
heartbeat.msg = "Group empty";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
GroupMonitorType,
|
||||
};
|
||||
|
|
@ -19,6 +19,9 @@ class DingDing extends NotificationProvider {
|
|||
markdown: {
|
||||
title: `[${this.statusToString(heartbeatJSON["status"])}] ${monitorJSON["name"]}`,
|
||||
text: `## [${this.statusToString(heartbeatJSON["status"])}] ${monitorJSON["name"]} \n> ${heartbeatJSON["msg"]}\n> Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`,
|
||||
},
|
||||
"at": {
|
||||
"isAtAll": notification.mentioning === "everyone"
|
||||
}
|
||||
};
|
||||
if (await this.sendToDingDing(notification, params)) {
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
|
||||
class Keep extends NotificationProvider {
|
||||
name = "Keep";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
const okMsg = "Sent Successfully.";
|
||||
|
||||
try {
|
||||
let data = {
|
||||
heartbeat: heartbeatJSON,
|
||||
monitor: monitorJSON,
|
||||
msg,
|
||||
};
|
||||
let config = {
|
||||
headers: {
|
||||
"x-api-key": notification.webhookAPIKey,
|
||||
"content-type": "application/json",
|
||||
},
|
||||
};
|
||||
|
||||
let url = notification.webhookURL;
|
||||
|
||||
if (url.endsWith("/")) {
|
||||
url = url.slice(0, -1);
|
||||
}
|
||||
|
||||
let webhookURL = url + "/alerts/event/uptimekuma";
|
||||
|
||||
await axios.post(webhookURL, data, config);
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Keep;
|
|
@ -0,0 +1,78 @@
|
|||
const NotificationProvider = require("./notification-provider");
|
||||
const axios = require("axios");
|
||||
const { DOWN, UP } = require("../../src/util");
|
||||
|
||||
class SevenIO extends NotificationProvider {
|
||||
name = "SevenIO";
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||
const okMsg = "Sent Successfully.";
|
||||
|
||||
const data = {
|
||||
to: notification.sevenioTo,
|
||||
from: notification.sevenioSender || "Uptime Kuma",
|
||||
text: msg,
|
||||
};
|
||||
|
||||
const config = {
|
||||
baseURL: "https://gateway.seven.io/api/",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-API-Key": notification.sevenioApiKey,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
// testing or certificate expiry notification
|
||||
if (heartbeatJSON == null) {
|
||||
await axios.post("sms", data, config);
|
||||
return okMsg;
|
||||
}
|
||||
|
||||
let address = "";
|
||||
|
||||
switch (monitorJSON["type"]) {
|
||||
case "ping":
|
||||
address = monitorJSON["hostname"];
|
||||
break;
|
||||
case "port":
|
||||
case "dns":
|
||||
case "gamedig":
|
||||
case "steam":
|
||||
address = monitorJSON["hostname"];
|
||||
if (monitorJSON["port"]) {
|
||||
address += ":" + monitorJSON["port"];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (![ "https://", "http://", "" ].includes(monitorJSON["url"])) {
|
||||
address = monitorJSON["url"];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (address !== "") {
|
||||
address = `(${address}) `;
|
||||
}
|
||||
|
||||
// If heartbeatJSON is not null, we go into the normal alerting loop.
|
||||
if (heartbeatJSON["status"] === DOWN) {
|
||||
data.text = `Your service ${monitorJSON["name"]} ${address}went down at ${heartbeatJSON["localDateTime"]} ` +
|
||||
`(${heartbeatJSON["timezone"]}). Error: ${heartbeatJSON["msg"]}`;
|
||||
} else if (heartbeatJSON["status"] === UP) {
|
||||
data.text = `Your service ${monitorJSON["name"]} ${address}went back up at ${heartbeatJSON["localDateTime"]} ` +
|
||||
`(${heartbeatJSON["timezone"]}).`;
|
||||
}
|
||||
await axios.post("sms", data, config);
|
||||
return okMsg;
|
||||
} catch (error) {
|
||||
this.throwGeneralAxiosError(error);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = SevenIO;
|
|
@ -18,6 +18,7 @@ const Gotify = require("./notification-providers/gotify");
|
|||
const GrafanaOncall = require("./notification-providers/grafana-oncall");
|
||||
const HomeAssistant = require("./notification-providers/home-assistant");
|
||||
const HeiiOnCall = require("./notification-providers/heii-oncall");
|
||||
const Keep = require("./notification-providers/keep");
|
||||
const Kook = require("./notification-providers/kook");
|
||||
const Line = require("./notification-providers/line");
|
||||
const LineNotify = require("./notification-providers/linenotify");
|
||||
|
@ -56,6 +57,7 @@ const GoAlert = require("./notification-providers/goalert");
|
|||
const SMSManager = require("./notification-providers/smsmanager");
|
||||
const ServerChan = require("./notification-providers/serverchan");
|
||||
const ZohoCliq = require("./notification-providers/zoho-cliq");
|
||||
const SevenIO = require("./notification-providers/sevenio");
|
||||
const Whapi = require("./notification-providers/whapi");
|
||||
const GtxMessaging = require("./notification-providers/gtx-messaging");
|
||||
const Cellsynt = require("./notification-providers/cellsynt");
|
||||
|
@ -94,6 +96,7 @@ class Notification {
|
|||
new GrafanaOncall(),
|
||||
new HomeAssistant(),
|
||||
new HeiiOnCall(),
|
||||
new Keep(),
|
||||
new Kook(),
|
||||
new Line(),
|
||||
new LineNotify(),
|
||||
|
@ -132,6 +135,7 @@ class Notification {
|
|||
new WeCom(),
|
||||
new GoAlert(),
|
||||
new ZohoCliq(),
|
||||
new SevenIO(),
|
||||
new Whapi(),
|
||||
new GtxMessaging(),
|
||||
new Cellsynt(),
|
||||
|
|
|
@ -113,6 +113,7 @@ class UptimeKumaServer {
|
|||
UptimeKumaServer.monitorTypeList["tailscale-ping"] = new TailscalePing();
|
||||
UptimeKumaServer.monitorTypeList["dns"] = new DnsMonitorType();
|
||||
UptimeKumaServer.monitorTypeList["mqtt"] = new MqttMonitorType();
|
||||
UptimeKumaServer.monitorTypeList["group"] = new GroupMonitorType();
|
||||
|
||||
// Allow all CORS origins (polling) in development
|
||||
let cors = undefined;
|
||||
|
@ -516,3 +517,4 @@ const { RealBrowserMonitorType } = require("./monitor-types/real-browser-monitor
|
|||
const { TailscalePing } = require("./monitor-types/tailscale-ping");
|
||||
const { DnsMonitorType } = require("./monitor-types/dns");
|
||||
const { MqttMonitorType } = require("./monitor-types/mqtt");
|
||||
const { GroupMonitorType } = require("./monitor-types/group");
|
||||
|
|
|
@ -123,6 +123,7 @@ export default {
|
|||
"GrafanaOncall": "Grafana Oncall",
|
||||
"HeiiOnCall": "Heii On-Call",
|
||||
"HomeAssistant": "Home Assistant",
|
||||
"Keep": "Keep",
|
||||
"Kook": "Kook",
|
||||
"line": "LINE Messenger",
|
||||
"LineNotify": "LINE Notify",
|
||||
|
@ -154,6 +155,7 @@ export default {
|
|||
"webhook": "Webhook",
|
||||
"GoAlert": "GoAlert",
|
||||
"ZohoCliq": "ZohoCliq",
|
||||
"SevenIO": "SevenIO",
|
||||
"whapi": "WhatsApp (Whapi)",
|
||||
"gtxmessaging": "GtxMessaging",
|
||||
"Cellsynt": "Cellsynt",
|
||||
|
|
|
@ -2,9 +2,10 @@
|
|||
<div class="mb-3">
|
||||
<label for="WebHookUrl" class="form-label">{{ $t("WebHookUrl") }}<span style="color: red;"><sup>*</sup></span></label>
|
||||
<input id="WebHookUrl" v-model="$parent.notification.webHookUrl" type="text" class="form-control" required>
|
||||
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="secretKey" class="form-label">{{ $t("SecretKey") }}<span style="color: red;"><sup>*</sup></span></label>
|
||||
<input id="secretKey" v-model="$parent.notification.secretKey" type="text" class="form-control" required>
|
||||
<HiddenInput id="secretKey" v-model="$parent.notification.secretKey" :required="true" autocomplete="new-password"></HiddenInput>
|
||||
|
||||
<div class="form-text">
|
||||
<p>{{ $t("For safety, must use secret key") }}</p>
|
||||
|
@ -13,4 +14,24 @@
|
|||
</i18n-t>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="mentioning" class="form-label">{{ $t("Mentioning") }}<span style="color: red;"><sup>*</sup></span></label>
|
||||
<select id="mentioning" v-model="$parent.notification.mentioning" class="form-select" required>
|
||||
<option value="nobody">{{ $t("Don't mention people") }}</option>
|
||||
<option value="everyone">{{ $t("Mention group", { group: "@everyone" }) }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
|
||||
export default {
|
||||
components: { HiddenInput },
|
||||
mounted() {
|
||||
if (typeof this.$parent.notification.mentioning === "undefined") {
|
||||
this.$parent.notification.mentioning = "nobody";
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="webhook-url" class="form-label">{{ $t("Host URL") }}</label>
|
||||
<input
|
||||
id="webhook-url"
|
||||
v-model="$parent.notification.webhookURL"
|
||||
type="url"
|
||||
pattern="https?://.+"
|
||||
class="form-control"
|
||||
required
|
||||
/>
|
||||
<div class="form-text">
|
||||
<i18n-t tag="p" keypath="Read more:">
|
||||
<a href="https://docs.keephq.dev/providers/documentation/uptimekuma-provider" target="_blank">https://docs.keephq.dev/providers/documentation/uptimekuma-provider</a>
|
||||
</i18n-t>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="webhook-apikey" class="form-label">{{
|
||||
$t("API Key")
|
||||
}}</label>
|
||||
<HiddenInput
|
||||
id="webhook-apikey"
|
||||
v-model="$parent.notification.webhookAPIKey"
|
||||
:required="true"
|
||||
></HiddenInput>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HiddenInput,
|
||||
},
|
||||
mounted() {
|
||||
this.$parent.notification.webhookURL ||= "";
|
||||
},
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,31 @@
|
|||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="sevenio-api-key" class="form-label">{{ $t("apiKeySevenIO") }}</label>
|
||||
<HiddenInput id="sevenio-api-key" v-model="$parent.notification.sevenioApiKey" :required="true" autocomplete="new-password"></HiddenInput>
|
||||
<div class="form-text">
|
||||
{{ $t("wayToGetSevenIOApiKey") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="sevenio-sender" class="form-label">{{ $t("senderSevenIO") }}</label>
|
||||
<input id="sevenio-sender" v-model="$parent.notification.sevenioSender" type="text" class="form-control" autocomplete="false" placeholder="Uptime Kuma">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="sevenio-receiver" class="form-label">{{ $t("receiverSevenIO") }}</label>
|
||||
<input id="sevenio-receiver" v-model="$parent.notification.sevenioReceiver" type="number" class="form-control" required autocomplete="false" placeholder="0123456789">
|
||||
<div class="form-text">
|
||||
{{ $t("receiverInfoSevenIO") }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
export default {
|
||||
components: {
|
||||
HiddenInput,
|
||||
},
|
||||
};
|
||||
</script>
|
|
@ -17,6 +17,7 @@ import GrafanaOncall from "./GrafanaOncall.vue";
|
|||
import GtxMessaging from "./GtxMessaging.vue";
|
||||
import HomeAssistant from "./HomeAssistant.vue";
|
||||
import HeiiOnCall from "./HeiiOnCall.vue";
|
||||
import Keep from "./Keep.vue";
|
||||
import Kook from "./Kook.vue";
|
||||
import Line from "./Line.vue";
|
||||
import LineNotify from "./LineNotify.vue";
|
||||
|
@ -55,6 +56,7 @@ import WeCom from "./WeCom.vue";
|
|||
import GoAlert from "./GoAlert.vue";
|
||||
import ZohoCliq from "./ZohoCliq.vue";
|
||||
import Splunk from "./Splunk.vue";
|
||||
import SevenIO from "./SevenIO.vue";
|
||||
import Whapi from "./Whapi.vue";
|
||||
import Cellsynt from "./Cellsynt.vue";
|
||||
|
||||
|
@ -81,6 +83,7 @@ const NotificationFormList = {
|
|||
"GrafanaOncall": GrafanaOncall,
|
||||
"HomeAssistant": HomeAssistant,
|
||||
"HeiiOnCall": HeiiOnCall,
|
||||
"Keep": Keep,
|
||||
"Kook": Kook,
|
||||
"line": Line,
|
||||
"LineNotify": LineNotify,
|
||||
|
@ -119,6 +122,7 @@ const NotificationFormList = {
|
|||
"GoAlert": GoAlert,
|
||||
"ServerChan": ServerChan,
|
||||
"ZohoCliq": ZohoCliq,
|
||||
"SevenIO": SevenIO,
|
||||
"whapi": Whapi,
|
||||
"gtxmessaging": GtxMessaging,
|
||||
"Cellsynt": Cellsynt,
|
||||
|
|
|
@ -63,6 +63,7 @@
|
|||
"Friendly Name": "Friendly Name",
|
||||
"URL": "URL",
|
||||
"Hostname": "Hostname",
|
||||
"Host URL": "Host URL",
|
||||
"locally configured mail transfer agent": "locally configured mail transfer agent",
|
||||
"Either enter the hostname of the server you want to connect to or localhost if you intend to use a locally configured mail transfer agent": "Either enter the hostname of the server you want to connect to or {localhost} if you intend to use a {local_mta}",
|
||||
"Port": "Port",
|
||||
|
@ -667,6 +668,9 @@
|
|||
"WebHookUrl": "WebHookUrl",
|
||||
"SecretKey": "SecretKey",
|
||||
"For safety, must use secret key": "For safety, must use secret key",
|
||||
"Mentioning": "Mentioning",
|
||||
"Don't mention people": "Don't mention people",
|
||||
"Mention group": "Mention {group}",
|
||||
"Device Token": "Device Token",
|
||||
"Platform": "Platform",
|
||||
"Huawei": "Huawei",
|
||||
|
@ -887,6 +891,11 @@
|
|||
"deleteRemoteBrowserMessage": "Are you sure want to delete this Remote Browser for all monitors?",
|
||||
"GrafanaOncallUrl": "Grafana Oncall URL",
|
||||
"Browser Screenshot": "Browser Screenshot",
|
||||
"wayToGetSevenIOApiKey": "Visit the dashboard under app.seven.io > developer > api key > the green add button",
|
||||
"senderSevenIO": "Sending number or name",
|
||||
"receiverSevenIO": "Receiving number",
|
||||
"receiverInfoSevenIO": "If the receiving number is not located in Germany, you have to add the country code in front of the number (e.g. for the country code 1 from the US use 117612121212 instead of 017612121212)",
|
||||
"apiKeySevenIO": "SevenIO API Key",
|
||||
"wayToWriteWhapiRecipient": "The phone number with the international prefix, but without the plus sign at the start ({0}), the Contact ID ({1}) or the Group ID ({2}).",
|
||||
"wayToGetWhapiUrlAndToken": "You can get the API URL and the token by going into your desired channel from {0}",
|
||||
"whapiRecipient": "Phone Number / Contact ID / Group ID",
|
||||
|
|
Loading…
Reference in New Issue