Merge branch 'master' into scroll-into-view

This commit is contained in:
LouisLam 2021-08-30 00:46:26 +08:00
commit e644a1e36f
17 changed files with 391 additions and 45 deletions

12
.github/FUNDING.yml vendored Normal file
View 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']

View file

@ -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

View file

@ -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.
@ -91,4 +86,4 @@ If you want to report a bug or request a new feature. Free feel to open a new is
If you want to modify Uptime Kuma, this guideline may be 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
View 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
View 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
View 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
View file

@ -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",

View file

@ -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",

View file

@ -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;

View file

@ -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()) {

View file

@ -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)

View file

@ -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,

View file

@ -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?",
} }

View file

@ -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?",
} }

View file

@ -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 記錄類型",
} }

View file

@ -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">

View file

@ -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>
@ -44,16 +47,52 @@
</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,7 +125,9 @@
</div> </div>
</div> </div>
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="my-3"> <!-- HTTP / Keyword only -->
<template v-if="monitor.type === 'http' || monitor.type === 'keyword' ">
<div class="my-3">
<label for="maxRedirects" class="form-label">{{ $t("Max. Redirects") }}</label> <label for="maxRedirects" class="form-label">{{ $t("Max. Redirects") }}</label>
<input id="maxRedirects" v-model="monitor.maxredirects" type="number" class="form-control" required min="0" step="1"> <input id="maxRedirects" v-model="monitor.maxredirects" type="number" class="form-control" required min="0" step="1">
<div class="form-text"> <div class="form-text">
@ -94,7 +135,7 @@
</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
@ -115,6 +156,7 @@
{{ $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) => {