mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-11-27 16:54:04 +00:00
Merge branch 'master' into scroll-into-view
This commit is contained in:
commit
e644a1e36f
17 changed files with 391 additions and 45 deletions
12
.github/FUNDING.yml
vendored
Normal file
12
.github/FUNDING.yml
vendored
Normal file
|
@ -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']
|
|
@ -22,13 +22,13 @@ If you are not sure, feel free to create an empty pull request draft first.
|
||||||
|
|
||||||
### *️⃣ Requires one more reviewer
|
### *️⃣ 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
|
- Add k8s supports
|
||||||
|
|
||||||
### *️⃣ Low Priority
|
### *️⃣ Low Priority
|
||||||
|
|
||||||
It chnaged my current workflow and require further studies.
|
It changed my current workflow and require further studies.
|
||||||
|
|
||||||
- Change my release approach
|
- Change my release approach
|
||||||
|
|
||||||
|
|
19
README.md
19
README.md
|
@ -12,16 +12,16 @@ It is a self-hosted monitoring tool like "Uptime Robot".
|
||||||
|
|
||||||
## ⭐ Features
|
## ⭐ Features
|
||||||
|
|
||||||
* Monitoring uptime for HTTP(s) / TCP / Ping.
|
* Monitoring uptime for HTTP(s) / TCP / Ping / DNS Record.
|
||||||
* Fancy, Reactive, Fast UI/UX.
|
* 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.
|
* 20 seconds interval.
|
||||||
|
|
||||||
## 🔧 How to Install
|
## 🔧 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
|
```bash
|
||||||
curl -o kuma_install.sh https://raw.githubusercontent.com/louislam/uptime-kuma/master/install.sh && sudo bash kuma_install.sh
|
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.
|
Browse to http://localhost:3001 after started.
|
||||||
|
|
||||||
### ☸️ Kubernetes
|
|
||||||
|
|
||||||
See more [here](kubernetes/README.md)
|
|
||||||
|
|
||||||
|
|
||||||
### Advanced Installation
|
### Advanced Installation
|
||||||
|
|
||||||
If you need more options or need to browse via a reserve proxy, please read:
|
If you need more options or need to browse via a reserve proxy, please read:
|
||||||
|
@ -76,7 +71,7 @@ Telegram Notification Sample:
|
||||||
|
|
||||||
## Motivation
|
## 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.
|
* Want to build a fancy UI.
|
||||||
* Learn Vue 3 and vite.js.
|
* Learn Vue 3 and vite.js.
|
||||||
* Show the power of Bootstrap 5.
|
* 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 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.
|
||||||
|
|
13
db/patch7.sql
Normal file
13
db/patch7.sql
Normal file
|
@ -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;
|
7
db/patch8.sql
Normal file
7
db/patch8.sql
Normal file
|
@ -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;
|
144
extra/simple-dns-server.js
Normal file
144
extra/simple-dns-server.js
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
package-lock.json
generated
13
package-lock.json
generated
|
@ -53,6 +53,7 @@
|
||||||
"@vitejs/plugin-vue": "^1.4.0",
|
"@vitejs/plugin-vue": "^1.4.0",
|
||||||
"@vue/compiler-sfc": "^3.2.2",
|
"@vue/compiler-sfc": "^3.2.2",
|
||||||
"core-js": "^3.16.1",
|
"core-js": "^3.16.1",
|
||||||
|
"dns2": "^2.0.1",
|
||||||
"eslint": "^7.32.0",
|
"eslint": "^7.32.0",
|
||||||
"eslint-plugin-vue": "^7.16.0",
|
"eslint-plugin-vue": "^7.16.0",
|
||||||
"sass": "^1.37.5",
|
"sass": "^1.37.5",
|
||||||
|
@ -2457,6 +2458,12 @@
|
||||||
"node": ">=8"
|
"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": {
|
"node_modules/doctrine": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
|
||||||
|
@ -9441,6 +9448,12 @@
|
||||||
"path-type": "^4.0.0"
|
"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": {
|
"doctrine": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
|
||||||
|
|
|
@ -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-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-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-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": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
||||||
|
@ -79,6 +80,7 @@
|
||||||
"@vitejs/plugin-vue": "^1.4.0",
|
"@vitejs/plugin-vue": "^1.4.0",
|
||||||
"@vue/compiler-sfc": "^3.2.2",
|
"@vue/compiler-sfc": "^3.2.2",
|
||||||
"core-js": "^3.16.1",
|
"core-js": "^3.16.1",
|
||||||
|
"dns2": "^2.0.1",
|
||||||
"eslint": "^7.32.0",
|
"eslint": "^7.32.0",
|
||||||
"eslint-plugin-vue": "^7.16.0",
|
"eslint-plugin-vue": "^7.16.0",
|
||||||
"sass": "^1.37.5",
|
"sass": "^1.37.5",
|
||||||
|
|
|
@ -6,7 +6,7 @@ class Database {
|
||||||
|
|
||||||
static templatePath = "./db/kuma.db"
|
static templatePath = "./db/kuma.db"
|
||||||
static path = "./data/kuma.db";
|
static path = "./data/kuma.db";
|
||||||
static latestVersion = 6;
|
static latestVersion = 8;
|
||||||
static noReject = true;
|
static noReject = true;
|
||||||
static sqliteInstance = null;
|
static sqliteInstance = null;
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ dayjs.extend(timezone)
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
const { Prometheus } = require("../prometheus");
|
const { Prometheus } = require("../prometheus");
|
||||||
const { debug, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util");
|
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 { R } = require("redbean-node");
|
||||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||||
const { Notification } = require("../notification")
|
const { Notification } = require("../notification")
|
||||||
|
@ -48,6 +48,9 @@ class Monitor extends BeanModel {
|
||||||
upsideDown: this.isUpsideDown(),
|
upsideDown: this.isUpsideDown(),
|
||||||
maxredirects: this.maxredirects,
|
maxredirects: this.maxredirects,
|
||||||
accepted_statuscodes: this.getAcceptedStatuscodes(),
|
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,
|
notificationIDList,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -174,6 +177,46 @@ class Monitor extends BeanModel {
|
||||||
bean.ping = await ping(this.hostname);
|
bean.ping = await ping(this.hostname);
|
||||||
bean.msg = ""
|
bean.msg = ""
|
||||||
bean.status = UP;
|
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()) {
|
if (this.isUpsideDown()) {
|
||||||
|
|
|
@ -291,6 +291,8 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString();
|
||||||
bean.upsideDown = monitor.upsideDown;
|
bean.upsideDown = monitor.upsideDown;
|
||||||
bean.maxredirects = monitor.maxredirects;
|
bean.maxredirects = monitor.maxredirects;
|
||||||
bean.accepted_statuscodes_json = JSON.stringify(monitor.accepted_statuscodes);
|
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)
|
await R.store(bean)
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ const { R } = require("redbean-node");
|
||||||
const { debug } = require("../src/util");
|
const { debug } = require("../src/util");
|
||||||
const passwordHash = require("./password-hash");
|
const passwordHash = require("./password-hash");
|
||||||
const dayjs = require("dayjs");
|
const dayjs = require("dayjs");
|
||||||
|
const { Resolver } = require("dns");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Init or reset JWT secret
|
* 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) {
|
exports.setting = async function (key) {
|
||||||
let value = await R.getCell("SELECT `value` FROM setting WHERE `key` = ? ", [
|
let value = await R.getCell("SELECT `value` FROM setting WHERE `key` = ? ", [
|
||||||
key,
|
key,
|
||||||
|
|
|
@ -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.",
|
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?",
|
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?",
|
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?",
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,4 +13,7 @@ export default {
|
||||||
pauseDashboardHome: "Pause",
|
pauseDashboardHome: "Pause",
|
||||||
deleteMonitorMsg: "Are you sure want to delete this monitor?",
|
deleteMonitorMsg: "Are you sure want to delete this monitor?",
|
||||||
deleteNotificationMsg: "Are you sure want to delete this notification for all monitors?",
|
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?",
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,4 +99,8 @@ export default {
|
||||||
"Certificate Info": "憑證詳細資料",
|
"Certificate Info": "憑證詳細資料",
|
||||||
deleteMonitorMsg: "是否確定刪除這個監測器",
|
deleteMonitorMsg: "是否確定刪除這個監測器",
|
||||||
deleteNotificationMsg: "是否確定刪除這個通知設定?如監測器啟用了這個通知,將會收不到通知。",
|
deleteNotificationMsg: "是否確定刪除這個通知設定?如監測器啟用了這個通知,將會收不到通知。",
|
||||||
|
"Resolver Server": "DNS 伺服器",
|
||||||
|
"Resource Record Type": "DNS 記錄類型",
|
||||||
|
resoverserverDescription: "預設值為 Cloudflare DNS 伺服器,你可以轉用其他 DNS 伺服器。",
|
||||||
|
rrtypeDescription: "請選擇 DNS 記錄類型",
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,10 @@
|
||||||
<br>
|
<br>
|
||||||
<span>{{ $t("Keyword") }}:</span> <span class="keyword">{{ monitor.keyword }}</span>
|
<span>{{ $t("Keyword") }}:</span> <span class="keyword">{{ monitor.keyword }}</span>
|
||||||
</span>
|
</span>
|
||||||
|
<span v-if="monitor.type === 'dns'">[{{ monitor.dns_resolve_type }}] {{ monitor.hostname }}
|
||||||
|
<br>
|
||||||
|
<span>{{ $t("Last Result") }}:</span> <span class="keyword">{{ monitor.dns_last_result }}</span>
|
||||||
|
</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="functions">
|
<div class="functions">
|
||||||
|
@ -161,8 +165,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Confirm ref="confirmPause" @yes="pauseMonitor">
|
<Confirm ref="confirmPause" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="pauseMonitor">
|
||||||
Are you sure want to pause?
|
{{ $t("pauseMonitorMsg") }}
|
||||||
</Confirm>
|
</Confirm>
|
||||||
|
|
||||||
<Confirm ref="confirmDelete" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="deleteMonitor">
|
<Confirm ref="confirmDelete" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="deleteMonitor">
|
||||||
|
|
|
@ -23,6 +23,9 @@
|
||||||
<option value="keyword">
|
<option value="keyword">
|
||||||
HTTP(s) - {{ $t("Keyword") }}
|
HTTP(s) - {{ $t("Keyword") }}
|
||||||
</option>
|
</option>
|
||||||
|
<option value="dns">
|
||||||
|
DNS
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -40,20 +43,56 @@
|
||||||
<label for="keyword" class="form-label">{{ $t("Keyword") }}</label>
|
<label for="keyword" class="form-label">{{ $t("Keyword") }}</label>
|
||||||
<input id="keyword" v-model="monitor.keyword" type="text" class="form-control" required>
|
<input id="keyword" v-model="monitor.keyword" type="text" class="form-control" required>
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
{{ $t("keywordDescription")}}
|
{{ $t("keywordDescription") }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="monitor.type === 'port' || monitor.type === 'ping' " class="my-3">
|
<!-- TCP Port / Ping / DNS only -->
|
||||||
|
<div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' " class="my-3">
|
||||||
<label for="hostname" class="form-label">{{ $t("Hostname") }}</label>
|
<label for="hostname" class="form-label">{{ $t("Hostname") }}</label>
|
||||||
<input id="hostname" v-model="monitor.hostname" type="text" class="form-control" required>
|
<input id="hostname" v-model="monitor.hostname" type="text" class="form-control" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- For TCP Port Type -->
|
||||||
<div v-if="monitor.type === 'port' " class="my-3">
|
<div v-if="monitor.type === 'port' " class="my-3">
|
||||||
<label for="port" class="form-label">{{ $t("Port") }}</label>
|
<label for="port" class="form-label">{{ $t("Port") }}</label>
|
||||||
<input id="port" v-model="monitor.port" type="number" class="form-control" required min="0" max="65535" step="1">
|
<input id="port" v-model="monitor.port" type="number" class="form-control" required min="0" max="65535" step="1">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- For DNS Type -->
|
||||||
|
<template v-if="monitor.type === 'dns'">
|
||||||
|
<div class="my-3">
|
||||||
|
<label for="dns_resolve_server" class="form-label">{{ $t("Resolver Server") }}</label>
|
||||||
|
<input id="dns_resolve_server" v-model="monitor.dns_resolve_server" type="text" class="form-control" :pattern="ipRegex" required>
|
||||||
|
<div class="form-text">
|
||||||
|
{{ $t("resoverserverDescription") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="my-3">
|
||||||
|
<label for="dns_resolve_type" class="form-label">{{ $t("Resource Record Type") }}</label>
|
||||||
|
|
||||||
|
<!-- :allow-empty="false" is not working, set a default value instead https://github.com/shentao/vue-multiselect/issues/336 -->
|
||||||
|
<VueMultiselect
|
||||||
|
id="dns_resolve_type"
|
||||||
|
v-model="monitor.dns_resolve_type"
|
||||||
|
:options="dnsresolvetypeOptions"
|
||||||
|
:multiple="false"
|
||||||
|
:close-on-select="true"
|
||||||
|
:clear-on-select="false"
|
||||||
|
:preserve-search="false"
|
||||||
|
placeholder="Pick a RR-Type..."
|
||||||
|
:preselect-first="false"
|
||||||
|
:max-height="500"
|
||||||
|
:taggable="false"
|
||||||
|
></VueMultiselect>
|
||||||
|
|
||||||
|
<div class="form-text">
|
||||||
|
{{ $t("rrtypeDescription") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<label for="interval" class="form-label">{{ $t("Heartbeat Interval") }} ({{ $t("checkEverySecond", [ monitor.interval ]) }})</label>
|
<label for="interval" class="form-label">{{ $t("Heartbeat Interval") }} ({{ $t("checkEverySecond", [ monitor.interval ]) }})</label>
|
||||||
<input id="interval" v-model="monitor.interval" type="number" class="form-control" required min="20" step="1">
|
<input id="interval" v-model="monitor.interval" type="number" class="form-control" required min="20" step="1">
|
||||||
|
@ -86,35 +125,38 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="my-3">
|
<!-- HTTP / Keyword only -->
|
||||||
<label for="maxRedirects" class="form-label">{{ $t("Max. Redirects") }}</label>
|
<template v-if="monitor.type === 'http' || monitor.type === 'keyword' ">
|
||||||
<input id="maxRedirects" v-model="monitor.maxredirects" type="number" class="form-control" required min="0" step="1">
|
<div class="my-3">
|
||||||
<div class="form-text">
|
<label for="maxRedirects" class="form-label">{{ $t("Max. Redirects") }}</label>
|
||||||
{{ $t("maxRedirectDescription") }}
|
<input id="maxRedirects" v-model="monitor.maxredirects" type="number" class="form-control" required min="0" step="1">
|
||||||
|
<div class="form-text">
|
||||||
|
{{ $t("maxRedirectDescription") }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="my-3">
|
<div class="my-3">
|
||||||
<label for="acceptedStatusCodes" class="form-label">{{ $t("Accepted Status Codes") }}</label>
|
<label for="acceptedStatusCodes" class="form-label">{{ $t("Accepted Status Codes") }}</label>
|
||||||
|
|
||||||
<VueMultiselect
|
<VueMultiselect
|
||||||
id="acceptedStatusCodes"
|
id="acceptedStatusCodes"
|
||||||
v-model="monitor.accepted_statuscodes"
|
v-model="monitor.accepted_statuscodes"
|
||||||
:options="acceptedStatusCodeOptions"
|
:options="acceptedStatusCodeOptions"
|
||||||
:multiple="true"
|
:multiple="true"
|
||||||
:close-on-select="false"
|
:close-on-select="false"
|
||||||
:clear-on-select="false"
|
:clear-on-select="false"
|
||||||
:preserve-search="true"
|
:preserve-search="true"
|
||||||
placeholder="Pick Accepted Status Codes..."
|
placeholder="Pick Accepted Status Codes..."
|
||||||
:preselect-first="false"
|
:preselect-first="false"
|
||||||
:max-height="600"
|
:max-height="600"
|
||||||
:taggable="true"
|
:taggable="true"
|
||||||
></VueMultiselect>
|
></VueMultiselect>
|
||||||
|
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
{{ $t("acceptedStatusCodesDescription") }}
|
{{ $t("acceptedStatusCodesDescription") }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
|
|
||||||
<div class="mt-5 mb-1">
|
<div class="mt-5 mb-1">
|
||||||
<button class="btn btn-primary" type="submit" :disabled="processing">{{ $t("Save") }}</button>
|
<button class="btn btn-primary" type="submit" :disabled="processing">{{ $t("Save") }}</button>
|
||||||
|
@ -155,6 +197,7 @@
|
||||||
import NotificationDialog from "../components/NotificationDialog.vue";
|
import NotificationDialog from "../components/NotificationDialog.vue";
|
||||||
import { useToast } from "vue-toastification"
|
import { useToast } from "vue-toastification"
|
||||||
import VueMultiselect from "vue-multiselect"
|
import VueMultiselect from "vue-multiselect"
|
||||||
|
import { isDev } from "../util.ts";
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -168,12 +211,27 @@ export default {
|
||||||
processing: false,
|
processing: false,
|
||||||
monitor: {
|
monitor: {
|
||||||
notificationIDList: {},
|
notificationIDList: {},
|
||||||
|
// Do not add default value here, please check init() method
|
||||||
},
|
},
|
||||||
acceptedStatusCodeOptions: [],
|
acceptedStatusCodeOptions: [],
|
||||||
|
dnsresolvetypeOptions: [],
|
||||||
|
|
||||||
|
// Source: https://digitalfortress.tech/tips/top-15-commonly-used-regex/
|
||||||
|
ipRegexPattern: "((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
|
||||||
|
ipRegex() {
|
||||||
|
|
||||||
|
// Allow to test with simple dns server with port (127.0.0.1:5300)
|
||||||
|
if (! isDev) {
|
||||||
|
return this.ipRegexPattern;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
pageName() {
|
pageName() {
|
||||||
return this.$t((this.isAdd) ? "Add New Monitor" : "Edit");
|
return this.$t((this.isAdd) ? "Add New Monitor" : "Edit");
|
||||||
},
|
},
|
||||||
|
@ -200,11 +258,25 @@ export default {
|
||||||
"500-599",
|
"500-599",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let dnsresolvetypeOptions = [
|
||||||
|
"A",
|
||||||
|
"AAAA",
|
||||||
|
"CAA",
|
||||||
|
"CNAME",
|
||||||
|
"MX",
|
||||||
|
"NS",
|
||||||
|
"PTR",
|
||||||
|
"SOA",
|
||||||
|
"SRV",
|
||||||
|
"TXT",
|
||||||
|
];
|
||||||
|
|
||||||
for (let i = 100; i <= 999; i++) {
|
for (let i = 100; i <= 999; i++) {
|
||||||
acceptedStatusCodeOptions.push(i.toString());
|
acceptedStatusCodeOptions.push(i.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.acceptedStatusCodeOptions = acceptedStatusCodeOptions;
|
this.acceptedStatusCodeOptions = acceptedStatusCodeOptions;
|
||||||
|
this.dnsresolvetypeOptions = dnsresolvetypeOptions;
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
init() {
|
init() {
|
||||||
|
@ -221,6 +293,8 @@ export default {
|
||||||
upsideDown: false,
|
upsideDown: false,
|
||||||
maxredirects: 10,
|
maxredirects: 10,
|
||||||
accepted_statuscodes: ["200-299"],
|
accepted_statuscodes: ["200-299"],
|
||||||
|
dns_resolve_type: "A",
|
||||||
|
dns_resolve_server: "1.1.1.1",
|
||||||
}
|
}
|
||||||
} else if (this.isEdit) {
|
} else if (this.isEdit) {
|
||||||
this.$root.getSocket().emit("getMonitor", this.$route.params.id, (res) => {
|
this.$root.getSocket().emit("getMonitor", this.$route.params.id, (res) => {
|
||||||
|
|
Loading…
Reference in a new issue