diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 000000000..6e53fa086
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,12 @@
+# These are supported funding model platforms
+
+#github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+#patreon: # Replace with a single Patreon username
+open_collective: uptime-kuma # Replace with a single Open Collective username
+#ko_fi: # Replace with a single Ko-fi username
+#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+#liberapay: # Replace with a single Liberapay username
+#issuehunt: # Replace with a single IssueHunt username
+#otechie: # Replace with a single Otechie username
+#custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 5bbf343a1..0734285c7 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -22,13 +22,13 @@ If you are not sure, feel free to create an empty pull request draft first.
### *️⃣ Requires one more reviewer
-I do not have such knowledge to test it
+I do not have such knowledge to test it.
- Add k8s supports
### *️⃣ Low Priority
-It chnaged my current workflow and require further studies.
+It changed my current workflow and require further studies.
- Change my release approach
diff --git a/README.md b/README.md
index 025510f52..d5f0d317e 100644
--- a/README.md
+++ b/README.md
@@ -12,16 +12,16 @@ It is a self-hosted monitoring tool like "Uptime Robot".
## ⭐ Features
-* Monitoring uptime for HTTP(s) / TCP / Ping.
+* Monitoring uptime for HTTP(s) / TCP / Ping / DNS Record.
* Fancy, Reactive, Fast UI/UX.
-* Notifications via Webhook, Telegram, Discord, Gotify, Slack, Pushover, Email (SMTP) and more by Apprise.
+* Notifications via Telegram, Discord, Gotify, Slack, Pushover, Email (SMTP), and [70+ notification services, click here for the full list](https://github.com/louislam/uptime-kuma/issues/284).
* 20 seconds interval.
## 🔧 How to Install
-### 🚀 Installer via cli
+### 🚀 Installer via CLI
-Interactive cli installer, supports Docker or without Docker.
+Interactive CLI installer, supports Docker or without Docker.
```bash
curl -o kuma_install.sh https://raw.githubusercontent.com/louislam/uptime-kuma/master/install.sh && sudo bash kuma_install.sh
@@ -36,11 +36,6 @@ docker run -d --restart=always -p 3001:3001 -v uptime-kuma:/app/data --name upti
Browse to http://localhost:3001 after started.
-### ☸️ Kubernetes
-
-See more [here](kubernetes/README.md)
-
-
### Advanced Installation
If you need more options or need to browse via a reserve proxy, please read:
@@ -76,7 +71,7 @@ Telegram Notification Sample:
## Motivation
-* I was looking for a self-hosted monitoring tool like "Uptime Robot", but it is hard to find a suitable one. One of the close one is statping. Unfortunately, it is not stable and unmaintained.
+* I was looking for a self-hosted monitoring tool like "Uptime Robot", but it is hard to find a suitable one. One of the close ones is statping. Unfortunately, it is not stable and unmaintained.
* Want to build a fancy UI.
* Learn Vue 3 and vite.js.
* Show the power of Bootstrap 5.
@@ -89,6 +84,6 @@ If you love this project, please consider giving me a ⭐.
If you want to report a bug or request a new feature. Free feel to open a new issue.
-If you want to modify Uptime Kuma, this guideline maybe useful for you: https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md
+If you want to modify Uptime Kuma, this guideline may be useful for you: https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md
-English proofreading is needed too, because my grammar is not that great sadly. Feel free to correct my grammar in this Readme, source code or wiki.
+English proofreading is needed too because my grammar is not that great sadly. Feel free to correct my grammar in this readme, source code, or wiki.
diff --git a/db/patch7.sql b/db/patch7.sql
new file mode 100644
index 000000000..4085daf3f
--- /dev/null
+++ b/db/patch7.sql
@@ -0,0 +1,13 @@
+-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
+BEGIN TRANSACTION;
+
+ALTER TABLE monitor
+ ADD dns_resolve_type VARCHAR(5);
+
+ALTER TABLE monitor
+ ADD dns_resolve_server VARCHAR(255);
+
+ALTER TABLE monitor
+ ADD dns_last_result VARCHAR(255);
+
+COMMIT;
diff --git a/db/patch8.sql b/db/patch8.sql
new file mode 100644
index 000000000..d63a59476
--- /dev/null
+++ b/db/patch8.sql
@@ -0,0 +1,7 @@
+-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
+BEGIN TRANSACTION;
+
+ALTER TABLE monitor
+ ADD dns_last_result VARCHAR(255);
+
+COMMIT;
diff --git a/extra/simple-dns-server.js b/extra/simple-dns-server.js
new file mode 100644
index 000000000..5e5745f04
--- /dev/null
+++ b/extra/simple-dns-server.js
@@ -0,0 +1,144 @@
+/*
+ * Simple DNS Server
+ * For testing DNS monitoring type, dev only
+ */
+const dns2 = require("dns2");
+
+const { Packet } = dns2;
+
+const server = dns2.createServer({
+ udp: true
+});
+
+server.on("request", (request, send, rinfo) => {
+ for (let question of request.questions) {
+ console.log(question.name, type(question.type), question.class);
+
+ const response = Packet.createResponseFromRequest(request);
+
+ if (question.name === "existing.com") {
+
+ if (question.type === Packet.TYPE.A) {
+ response.answers.push({
+ name: question.name,
+ type: question.type,
+ class: question.class,
+ ttl: 300,
+ address: "1.2.3.4"
+ });
+ } if (question.type === Packet.TYPE.AAAA) {
+ response.answers.push({
+ name: question.name,
+ type: question.type,
+ class: question.class,
+ ttl: 300,
+ address: "fe80::::1234:5678:abcd:ef00",
+ });
+ } else if (question.type === Packet.TYPE.CNAME) {
+ response.answers.push({
+ name: question.name,
+ type: question.type,
+ class: question.class,
+ ttl: 300,
+ domain: "cname1.existing.com",
+ });
+ } else if (question.type === Packet.TYPE.MX) {
+ response.answers.push({
+ name: question.name,
+ type: question.type,
+ class: question.class,
+ ttl: 300,
+ exchange: "mx1.existing.com",
+ priority: 5
+ });
+ } else if (question.type === Packet.TYPE.NS) {
+ response.answers.push({
+ name: question.name,
+ type: question.type,
+ class: question.class,
+ ttl: 300,
+ ns: "ns1.existing.com",
+ });
+ } else if (question.type === Packet.TYPE.SOA) {
+ response.answers.push({
+ name: question.name,
+ type: question.type,
+ class: question.class,
+ ttl: 300,
+ primary: "existing.com",
+ admin: "admin@existing.com",
+ serial: 2021082701,
+ refresh: 300,
+ retry: 3,
+ expiration: 10,
+ minimum: 10,
+ });
+ } else if (question.type === Packet.TYPE.SRV) {
+ response.answers.push({
+ name: question.name,
+ type: question.type,
+ class: question.class,
+ ttl: 300,
+ priority: 5,
+ weight: 5,
+ port: 8080,
+ target: "srv1.existing.com",
+ });
+ } else if (question.type === Packet.TYPE.TXT) {
+ response.answers.push({
+ name: question.name,
+ type: question.type,
+ class: question.class,
+ ttl: 300,
+ data: "#v=spf1 include:_spf.existing.com ~all",
+ });
+ } else if (question.type === Packet.TYPE.CAA) {
+ response.answers.push({
+ name: question.name,
+ type: question.type,
+ class: question.class,
+ ttl: 300,
+ flags: 0,
+ tag: "issue",
+ value: "ca.existing.com",
+ });
+ }
+
+ }
+
+ if (question.name === "4.3.2.1.in-addr.arpa") {
+ if (question.type === Packet.TYPE.PTR) {
+ response.answers.push({
+ name: question.name,
+ type: question.type,
+ class: question.class,
+ ttl: 300,
+ domain: "ptr1.existing.com",
+ });
+ }
+ }
+
+ send(response);
+ }
+});
+
+server.on("listening", () => {
+ console.log("Listening");
+ console.log(server.addresses());
+});
+
+server.on("close", () => {
+ console.log("server closed");
+});
+
+server.listen({
+ udp: 5300
+});
+
+function type(code) {
+ for (let name in Packet.TYPE) {
+ if (Packet.TYPE[name] === code) {
+ return name;
+ }
+ }
+}
diff --git a/package-lock.json b/package-lock.json
index cc1feebc5..61bab41eb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -53,6 +53,7 @@
"@vitejs/plugin-vue": "^1.4.0",
"@vue/compiler-sfc": "^3.2.2",
"core-js": "^3.16.1",
+ "dns2": "^2.0.1",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^7.16.0",
"sass": "^1.37.5",
@@ -2457,6 +2458,12 @@
"node": ">=8"
}
},
+ "node_modules/dns2": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/dns2/-/dns2-2.0.1.tgz",
+ "integrity": "sha512-jHRTCcS2h/MEQjhcCnOWGENtz5A4RrLoK1YFqlHCejGfK5zYu99C8cxVwTsIY7JUqolhDN8zuGlyqnbEe6azqg==",
+ "dev": true
+ },
"node_modules/doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@@ -9441,6 +9448,12 @@
"path-type": "^4.0.0"
}
},
+ "dns2": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/dns2/-/dns2-2.0.1.tgz",
+ "integrity": "sha512-jHRTCcS2h/MEQjhcCnOWGENtz5A4RrLoK1YFqlHCejGfK5zYu99C8cxVwTsIY7JUqolhDN8zuGlyqnbEe6azqg==",
+ "dev": true
+ },
"doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
diff --git a/package.json b/package.json
index 2d20c4094..24e67373f 100644
--- a/package.json
+++ b/package.json
@@ -32,7 +32,8 @@
"test-install-script-alpine3": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/alpine3.dockerfile .",
"test-install-script-ubuntu": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/ubuntu.dockerfile .",
"test-install-script-ubuntu1604": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/ubuntu1604.dockerfile .",
- "test-install-script-debian": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/debian.dockerfile ."
+ "test-install-script-debian": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/debian.dockerfile .",
+ "simple-dns-server": "node extra/simple-dns-server.js"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.36",
@@ -79,6 +80,7 @@
"@vitejs/plugin-vue": "^1.4.0",
"@vue/compiler-sfc": "^3.2.2",
"core-js": "^3.16.1",
+ "dns2": "^2.0.1",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^7.16.0",
"sass": "^1.37.5",
diff --git a/server/database.js b/server/database.js
index 5b923e8a9..7ce81be11 100644
--- a/server/database.js
+++ b/server/database.js
@@ -6,7 +6,7 @@ class Database {
static templatePath = "./db/kuma.db"
static path = "./data/kuma.db";
- static latestVersion = 6;
+ static latestVersion = 8;
static noReject = true;
static sqliteInstance = null;
diff --git a/server/model/monitor.js b/server/model/monitor.js
index 17ab27795..6d0d812bd 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -7,7 +7,7 @@ dayjs.extend(timezone)
const axios = require("axios");
const { Prometheus } = require("../prometheus");
const { debug, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util");
-const { tcping, ping, checkCertificate, checkStatusCode } = require("../util-server");
+const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode } = require("../util-server");
const { R } = require("redbean-node");
const { BeanModel } = require("redbean-node/dist/bean-model");
const { Notification } = require("../notification")
@@ -48,6 +48,9 @@ class Monitor extends BeanModel {
upsideDown: this.isUpsideDown(),
maxredirects: this.maxredirects,
accepted_statuscodes: this.getAcceptedStatuscodes(),
+ dns_resolve_type: this.dns_resolve_type,
+ dns_resolve_server: this.dns_resolve_server,
+ dns_last_result: this.dns_last_result,
notificationIDList,
};
}
@@ -174,6 +177,46 @@ class Monitor extends BeanModel {
bean.ping = await ping(this.hostname);
bean.msg = ""
bean.status = UP;
+ } else if (this.type === "dns") {
+ let startTime = dayjs().valueOf();
+ let dnsMessage = "";
+
+ let dnsRes = await dnsResolve(this.hostname, this.dns_resolve_server, this.dns_resolve_type);
+ bean.ping = dayjs().valueOf() - startTime;
+
+ if (this.dns_resolve_type == "A" || this.dns_resolve_type == "AAAA" || this.dns_resolve_type == "TXT") {
+ dnsMessage += "Records: ";
+ dnsMessage += dnsRes.join(" | ");
+ } else if (this.dns_resolve_type == "CNAME" || this.dns_resolve_type == "PTR") {
+ dnsMessage = dnsRes[0];
+ } else if (this.dns_resolve_type == "CAA") {
+ dnsMessage = dnsRes[0].issue;
+ } else if (this.dns_resolve_type == "MX") {
+ dnsRes.forEach(record => {
+ dnsMessage += `Hostname: ${record.exchange} - Priority: ${record.priority} | `;
+ });
+ dnsMessage = dnsMessage.slice(0, -2)
+ } else if (this.dns_resolve_type == "NS") {
+ dnsMessage += "Servers: ";
+ dnsMessage += dnsRes.join(" | ");
+ } else if (this.dns_resolve_type == "SOA") {
+ dnsMessage += `NS-Name: ${dnsRes.nsname} | Hostmaster: ${dnsRes.hostmaster} | Serial: ${dnsRes.serial} | Refresh: ${dnsRes.refresh} | Retry: ${dnsRes.retry} | Expire: ${dnsRes.expire} | MinTTL: ${dnsRes.minttl}`;
+ } else if (this.dns_resolve_type == "SRV") {
+ dnsRes.forEach(record => {
+ dnsMessage += `Name: ${record.name} | Port: ${record.port} | Priority: ${record.priority} | Weight: ${record.weight} | `;
+ });
+ dnsMessage = dnsMessage.slice(0, -2)
+ }
+
+ if (this.dnsLastResult !== dnsMessage) {
+ R.exec("UPDATE `monitor` SET dns_last_result = ? WHERE id = ? ", [
+ dnsMessage,
+ this.id
+ ]);
+ }
+
+ bean.msg = dnsMessage;
+ bean.status = UP;
}
if (this.isUpsideDown()) {
diff --git a/server/server.js b/server/server.js
index 34406ccc7..d4fe668b3 100644
--- a/server/server.js
+++ b/server/server.js
@@ -291,6 +291,8 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString();
bean.upsideDown = monitor.upsideDown;
bean.maxredirects = monitor.maxredirects;
bean.accepted_statuscodes_json = JSON.stringify(monitor.accepted_statuscodes);
+ bean.dns_resolve_type = monitor.dns_resolve_type;
+ bean.dns_resolve_server = monitor.dns_resolve_server;
await R.store(bean)
diff --git a/server/util-server.js b/server/util-server.js
index 8a2f03879..a30bcfec0 100644
--- a/server/util-server.js
+++ b/server/util-server.js
@@ -4,6 +4,7 @@ const { R } = require("redbean-node");
const { debug } = require("../src/util");
const passwordHash = require("./password-hash");
const dayjs = require("dayjs");
+const { Resolver } = require("dns");
/**
* Init or reset JWT secret
@@ -76,6 +77,30 @@ exports.pingAsync = function (hostname, ipv6 = false) {
});
}
+exports.dnsResolve = function (hostname, resolver_server, rrtype) {
+ const resolver = new Resolver();
+ resolver.setServers([resolver_server]);
+ return new Promise((resolve, reject) => {
+ if (rrtype == "PTR") {
+ resolver.reverse(hostname, (err, records) => {
+ if (err) {
+ reject(err);
+ } else {
+ resolve(records);
+ }
+ });
+ } else {
+ resolver.resolve(hostname, rrtype, (err, records) => {
+ if (err) {
+ reject(err);
+ } else {
+ resolve(records);
+ }
+ });
+ }
+ })
+}
+
exports.setting = async function (key) {
let value = await R.getCell("SELECT `value` FROM setting WHERE `key` = ? ", [
key,
diff --git a/src/languages/de-DE.js b/src/languages/de-DE.js
index b2bb6081d..ba852cf32 100644
--- a/src/languages/de-DE.js
+++ b/src/languages/de-DE.js
@@ -99,4 +99,9 @@ export default {
keywordDescription: "Suche nach einen Schlüsselwort in einer schlichten HTML oder JSON Ausgabe. Bitte beachte, es wird in der Groß-/Kleinschreibung unterschieden.",
deleteMonitorMsg: "Bist du sicher das du den Monitor löschen möchtest?",
deleteNotificationMsg: "Möchtest du diese Benachrichtigung wirklich für alle Monitore löschen?",
+ resoverserverDescription: "Cloudflare ist der Standardserver, dieser kann jederzeit geändern werden.",
+ "Resolver Server": "Auflösungsserver",
+ rrtypeDescription: "Wähle den RR-Typ aus, welchen du überwachen möchtest.",
+ "Last Result": "Letztes Ergebnis",
+ pauseMonitorMsg: "Bist du sicher das du den Monitor pausieren möchtest?",
}
diff --git a/src/languages/en.js b/src/languages/en.js
index 05e3fc929..75c25dd57 100644
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -13,4 +13,7 @@ export default {
pauseDashboardHome: "Pause",
deleteMonitorMsg: "Are you sure want to delete this monitor?",
deleteNotificationMsg: "Are you sure want to delete this notification for all monitors?",
+ resoverserverDescription: "Cloudflare is the default server, you can change the resolver server anytime.",
+ rrtypeDescription: "Select the RR-Type you want to monitor",
+ pauseMonitorMsg: "Are you sure want to pause?",
}
diff --git a/src/languages/zh-HK.js b/src/languages/zh-HK.js
index c61434d45..d0cd334a6 100644
--- a/src/languages/zh-HK.js
+++ b/src/languages/zh-HK.js
@@ -99,4 +99,8 @@ export default {
"Certificate Info": "憑證詳細資料",
deleteMonitorMsg: "是否確定刪除這個監測器",
deleteNotificationMsg: "是否確定刪除這個通知設定?如監測器啟用了這個通知,將會收不到通知。",
+ "Resolver Server": "DNS 伺服器",
+ "Resource Record Type": "DNS 記錄類型",
+ resoverserverDescription: "預設值為 Cloudflare DNS 伺服器,你可以轉用其他 DNS 伺服器。",
+ rrtypeDescription: "請選擇 DNS 記錄類型",
}
diff --git a/src/pages/Details.vue b/src/pages/Details.vue
index 3d49d6064..ce4f5229c 100644
--- a/src/pages/Details.vue
+++ b/src/pages/Details.vue
@@ -10,6 +10,10 @@
{{ $t("Keyword") }}: {{ monitor.keyword }}
+ [{{ monitor.dns_resolve_type }}] {{ monitor.hostname }}
+
+ {{ $t("Last Result") }}: {{ monitor.dns_last_result }}
+