From b0259b559249c979698b9f59891399920bf81070 Mon Sep 17 00:00:00 2001
From: c0derMo <jaydeveloper@outlook.de>
Date: Thu, 13 Jan 2022 16:17:07 +0000
Subject: [PATCH 01/99] Added docker container monitor

---
 db/patch-add-docker-columns.sql | 10 ++++++++++
 server/database.js              |  1 +
 server/model/monitor.js         | 23 +++++++++++++++++++++++
 src/pages/EditMonitor.vue       | 19 +++++++++++++++++++
 4 files changed, 53 insertions(+)
 create mode 100644 db/patch-add-docker-columns.sql

diff --git a/db/patch-add-docker-columns.sql b/db/patch-add-docker-columns.sql
new file mode 100644
index 00000000..fdde4170
--- /dev/null
+++ b/db/patch-add-docker-columns.sql
@@ -0,0 +1,10 @@
+-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
+BEGIN TRANSACTION;
+
+ALTER TABLE monitor
+	ADD docker_daemon VARCHAR(255);
+
+ALTER TABLE monitor
+	ADD docker_container VARCHAR(255);
+
+COMMIT;
diff --git a/server/database.js b/server/database.js
index afcace70..536acd19 100644
--- a/server/database.js
+++ b/server/database.js
@@ -53,6 +53,7 @@ class Database {
         "patch-2fa-invalidate-used-token.sql": true,
         "patch-notification_sent_history.sql": true,
         "patch-monitor-basic-auth.sql": true,
+        "patch-add-docker-columns.sql": true
     }
 
     /**
diff --git a/server/model/monitor.js b/server/model/monitor.js
index c4441d63..5683352f 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -77,6 +77,8 @@ class Monitor extends BeanModel {
             dns_resolve_server: this.dns_resolve_server,
             dns_last_result: this.dns_last_result,
             pushToken: this.pushToken,
+            docker_container: this.docker_container,
+            docker_daemon: this.docker_daemon,
             notificationIDList,
             tags: tags,
         };
@@ -347,6 +349,27 @@ class Monitor extends BeanModel {
                         throw new Error("Server not found on Steam");
                     }
 
+                } else if (this.type === "docker") {
+                    debug(`[${this.name}] Prepare Options for axios`);
+
+                    const options = {
+                        url: `/containers/${this.docker_container}/json`,
+                        headers: {
+                            "Accept": "*/*",
+                            "User-Agent": "Uptime-Kuma/" + version,
+                        },
+                        socketPath: this.docker_daemon,
+                        httpsAgent: new https.Agent({
+                            maxCachedSessions: 0,      // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
+                            rejectUnauthorized: ! this.getIgnoreTls(),
+                        }),
+                    };
+
+                    debug(`[${this.name}] Axios Request`);
+                    let res = await axios.request(options);
+                    if (res.data.State.Running) {
+                        bean.status = UP;
+                    }
                 } else {
                     bean.msg = "Unknown Monitor Type";
                     bean.status = PENDING;
diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index 4b6a920c..8b02a75f 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -32,6 +32,9 @@
                                     <option value="steam">
                                         Steam Game Server
                                     </option>
+                                    <option value="docker">
+                                        Docker Container
+                                    </option>
                                 </select>
                             </div>
 
@@ -115,6 +118,20 @@
                                 </div>
                             </template>
 
+                            <!-- Docker Container Name / ID -->
+                            <!-- For Docker Type -->
+                            <div v-if="monitor.type === 'docker'" class="my-3">
+                                <label for="docker_container" class="form-label">{{ $t("Container Name / ID") }}</label>
+                                <input id="docker_container" v-model="monitor.docker_container" type="text" class="form-control" required>
+                            </div>
+
+                            <!-- Docker Daemon -->
+                            <!-- For Docker Type -->
+                            <div v-if="monitor.type === 'docker'" class="my-3">
+                                <label for="docker_daemon" class="form-label">{{ $t("Docker Daemon") }}</label>
+                                <input id="docker_daemon" v-model="monitor.docker_daemon" type="text" class="form-control" required>
+                            </div>
+
                             <!-- Interval -->
                             <div class="my-3">
                                 <label for="interval" class="form-label">{{ $t("Heartbeat Interval") }} ({{ $t("checkEverySecond", [ monitor.interval ]) }})</label>
@@ -439,6 +456,8 @@ export default {
                     accepted_statuscodes: ["200-299"],
                     dns_resolve_type: "A",
                     dns_resolve_server: "1.1.1.1",
+                    docker_container: "",
+                    docker_daemon: "/var/run/docker.sock"
                 };
 
                 for (let i = 0; i < this.$root.notificationList.length; i++) {

From c5cc42272f0a72dbbf7c971173ad54fe6a524bf5 Mon Sep 17 00:00:00 2001
From: c0derMo <jaydeveloper@outlook.de>
Date: Thu, 13 Jan 2022 18:28:45 +0000
Subject: [PATCH 02/99] Fixing the editing of docker container & adding english
 translation

---
 server/model/monitor.js | 1 +
 server/server.js        | 2 ++
 src/languages/en.js     | 4 +++-
 3 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/server/model/monitor.js b/server/model/monitor.js
index 5683352f..5e32a89d 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -369,6 +369,7 @@ class Monitor extends BeanModel {
                     let res = await axios.request(options);
                     if (res.data.State.Running) {
                         bean.status = UP;
+                        bean.msg = "";
                     }
                 } else {
                     bean.msg = "Unknown Monitor Type";
diff --git a/server/server.js b/server/server.js
index 868bbd5e..7495cfb8 100644
--- a/server/server.js
+++ b/server/server.js
@@ -588,6 +588,8 @@ exports.entryPage = "dashboard";
                 bean.dns_resolve_type = monitor.dns_resolve_type;
                 bean.dns_resolve_server = monitor.dns_resolve_server;
                 bean.pushToken = monitor.pushToken;
+                bean.docker_container = monitor.docker_container;
+                bean.docker_daemon = monitor.docker_daemon;
 
                 await R.store(bean);
 
diff --git a/src/languages/en.js b/src/languages/en.js
index 47513466..1d56e139 100644
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -351,7 +351,7 @@ export default {
     serwersmsAPIPassword: "API Password",
     serwersmsPhoneNumber: "Phone number",
     serwersmsSenderName: "SMS Sender Name (registered via customer portal)",
-    "stackfield": "Stackfield",
+    stackfield: "Stackfield",
     smtpDkimSettings: "DKIM Settings",
     smtpDkimDesc: "Please refer to the Nodemailer DKIM {0} for usage.",
     documentation: "documentation",
@@ -361,4 +361,6 @@ export default {
     smtpDkimHashAlgo: "Hash Algorithm (Optional)",
     smtpDkimheaderFieldNames: "Header Keys to sign (Optional)",
     smtpDkimskipFields: "Header Keys not to sign (Optional)",
+    "Container Name / ID": "Container Name / ID",
+    "Docker Daemon": "Docker Daemon",
 };

From 9619d31a05752d878de80d84679d0fec7fa2e117 Mon Sep 17 00:00:00 2001
From: c0derMo <jaydeveloper@outlook.de>
Date: Thu, 13 Jan 2022 18:33:01 +0000
Subject: [PATCH 03/99] Adding docker container ability to readme

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 7f88db5f..f2434b2d 100644
--- a/README.md
+++ b/README.md
@@ -23,7 +23,7 @@ VPS is sponsored by Uptime Kuma sponsors on [Open Collective](https://opencollec
 
 ## ⭐ Features
 
-* Monitoring uptime for HTTP(s) / TCP / HTTP(s) Keyword / Ping / DNS Record / Push / Steam Game Server.
+* Monitoring uptime for HTTP(s) / TCP / HTTP(s) Keyword / Ping / DNS Record / Push / Steam Game Server / Docker Containers.
 * Fancy, Reactive, Fast UI/UX.
 * 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/tree/master/src/components/notifications).
 * 20 second intervals.

From 4818bb67d60075b67435922bc3d00236e0bc23ac Mon Sep 17 00:00:00 2001
From: c0derMo <jaydeveloper@outlook.de>
Date: Fri, 14 Jan 2022 09:09:37 +0000
Subject: [PATCH 04/99] Added trailing comma, fixed spelling & translation

---
 server/database.js        | 2 +-
 server/model/monitor.js   | 2 +-
 src/languages/en.js       | 1 +
 src/pages/EditMonitor.vue | 4 ++--
 4 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/server/database.js b/server/database.js
index 536acd19..69551bac 100644
--- a/server/database.js
+++ b/server/database.js
@@ -53,7 +53,7 @@ class Database {
         "patch-2fa-invalidate-used-token.sql": true,
         "patch-notification_sent_history.sql": true,
         "patch-monitor-basic-auth.sql": true,
-        "patch-add-docker-columns.sql": true
+        "patch-add-docker-columns.sql": true,
     }
 
     /**
diff --git a/server/model/monitor.js b/server/model/monitor.js
index 5e32a89d..b75500ec 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -350,7 +350,7 @@ class Monitor extends BeanModel {
                     }
 
                 } else if (this.type === "docker") {
-                    debug(`[${this.name}] Prepare Options for axios`);
+                    debug(`[${this.name}] Prepare Options for Axios`);
 
                     const options = {
                         url: `/containers/${this.docker_container}/json`,
diff --git a/src/languages/en.js b/src/languages/en.js
index 1d56e139..ae9fa526 100644
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -363,4 +363,5 @@ export default {
     smtpDkimskipFields: "Header Keys not to sign (Optional)",
     "Container Name / ID": "Container Name / ID",
     "Docker Daemon": "Docker Daemon",
+    "Docker Container": "Docker Container",
 };
diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index 8b02a75f..86c35ef0 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -33,7 +33,7 @@
                                         Steam Game Server
                                     </option>
                                     <option value="docker">
-                                        Docker Container
+                                        {{ $t("Docker Container") }}
                                     </option>
                                 </select>
                             </div>
@@ -457,7 +457,7 @@ export default {
                     dns_resolve_type: "A",
                     dns_resolve_server: "1.1.1.1",
                     docker_container: "",
-                    docker_daemon: "/var/run/docker.sock"
+                    docker_daemon: "/var/run/docker.sock",
                 };
 
                 for (let i = 0; i < this.$root.notificationList.length; i++) {

From 29df70949d453dc4675eda05d7f107669e1fb3e1 Mon Sep 17 00:00:00 2001
From: c0derMo <jaydeveloper@outlook.de>
Date: Sat, 22 Jan 2022 01:57:37 +0000
Subject: [PATCH 05/99] Add ability to connect to daemon via http / tcp for
 windows compatibility

---
 db/patch-add-docker-columns.sql |  3 +++
 server/model/monitor.js         |  8 +++++++-
 server/server.js                |  1 +
 src/languages/en.js             |  3 +++
 src/pages/EditMonitor.vue       | 15 +++++++++++++++
 5 files changed, 29 insertions(+), 1 deletion(-)

diff --git a/db/patch-add-docker-columns.sql b/db/patch-add-docker-columns.sql
index fdde4170..56475667 100644
--- a/db/patch-add-docker-columns.sql
+++ b/db/patch-add-docker-columns.sql
@@ -7,4 +7,7 @@ ALTER TABLE monitor
 ALTER TABLE monitor
 	ADD docker_container VARCHAR(255);
 
+ALTER TABLE monitor
+	ADD docker_type VARCHAR(255);
+
 COMMIT;
diff --git a/server/model/monitor.js b/server/model/monitor.js
index b75500ec..d8a4be23 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -79,6 +79,7 @@ class Monitor extends BeanModel {
             pushToken: this.pushToken,
             docker_container: this.docker_container,
             docker_daemon: this.docker_daemon,
+            docker_type: this.docker_type,
             notificationIDList,
             tags: tags,
         };
@@ -358,13 +359,18 @@ class Monitor extends BeanModel {
                             "Accept": "*/*",
                             "User-Agent": "Uptime-Kuma/" + version,
                         },
-                        socketPath: this.docker_daemon,
                         httpsAgent: new https.Agent({
                             maxCachedSessions: 0,      // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
                             rejectUnauthorized: ! this.getIgnoreTls(),
                         }),
                     };
 
+                    if (this.docker_type === "socket") {
+                        options.socketPath = this.docker_daemon;
+                    } else if (this.docker_type === "tcp") {
+                        options.baseURL = this.docker_daemon;
+                    }
+
                     debug(`[${this.name}] Axios Request`);
                     let res = await axios.request(options);
                     if (res.data.State.Running) {
diff --git a/server/server.js b/server/server.js
index 7495cfb8..ac68769d 100644
--- a/server/server.js
+++ b/server/server.js
@@ -590,6 +590,7 @@ exports.entryPage = "dashboard";
                 bean.pushToken = monitor.pushToken;
                 bean.docker_container = monitor.docker_container;
                 bean.docker_daemon = monitor.docker_daemon;
+                bean.docker_type = monitor.docker_type;
 
                 await R.store(bean);
 
diff --git a/src/languages/en.js b/src/languages/en.js
index ae9fa526..ade50373 100644
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -364,4 +364,7 @@ export default {
     "Container Name / ID": "Container Name / ID",
     "Docker Daemon": "Docker Daemon",
     "Docker Container": "Docker Container",
+    "Docker Type": "Connection Type",
+    docker_socket: "Socket",
+    docker_tcp: "TCP / HTTP",
 };
diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index 86c35ef0..b80b9a26 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -125,6 +125,20 @@
                                 <input id="docker_container" v-model="monitor.docker_container" type="text" class="form-control" required>
                             </div>
 
+                            <!-- Docker Connection Type -->
+                            <!-- For Docker Type -->
+                            <div v-if="monitor.type === 'docker'" class="my-3">
+                                <label for="docker_type" class="form-label">{{ $t("Docker Type") }}</label>
+                                <select id="docker_type" v-model="monitor.docker_type" class="form-select">
+                                    <option value="socket">
+                                        {{ $t("docker_socket") }}
+                                    </option>
+                                    <option value="tcp">
+                                        {{ $t("docker_tcp") }}
+                                    </option>
+                                </select>
+                            </div>
+
                             <!-- Docker Daemon -->
                             <!-- For Docker Type -->
                             <div v-if="monitor.type === 'docker'" class="my-3">
@@ -458,6 +472,7 @@ export default {
                     dns_resolve_server: "1.1.1.1",
                     docker_container: "",
                     docker_daemon: "/var/run/docker.sock",
+                    docker_type: "socket",
                 };
 
                 for (let i = 0; i < this.$root.notificationList.length; i++) {

From 1ac904d6d6260a34d08a29249fb7cc15236ee787 Mon Sep 17 00:00:00 2001
From: OidaTiftla <chm.stephan@outlook.com>
Date: Sun, 23 Jan 2022 15:22:57 +0100
Subject: [PATCH 06/99] Introduce resend interval if down

---
 package-lock.json         |  4 ++--
 package.json              |  8 ++++----
 server/model/monitor.js   | 17 +++++++++++++++++
 server/server.js          |  7 +++++++
 src/pages/EditMonitor.vue |  8 ++++++++
 5 files changed, 38 insertions(+), 6 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index fc21a63f..5253c3af 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
     "name": "uptime-kuma",
-    "version": "1.11.3",
+    "version": "1.11.4",
     "lockfileVersion": 2,
     "requires": true,
     "packages": {
         "": {
             "name": "uptime-kuma",
-            "version": "1.11.3",
+            "version": "1.11.4",
             "license": "MIT",
             "dependencies": {
                 "@fortawesome/fontawesome-svg-core": "~1.2.36",
diff --git a/package.json b/package.json
index 048a5e0a..cd522a31 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
     "name": "uptime-kuma",
-    "version": "1.11.3",
+    "version": "1.11.4",
     "license": "MIT",
     "repository": {
         "type": "git",
@@ -30,13 +30,13 @@
         "build-docker": "npm run build && npm run build-docker-debian && npm run build-docker-alpine",
         "build-docker-alpine-base": "docker buildx build -f docker/alpine-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-alpine . --push",
         "build-docker-debian-base": "docker buildx build -f docker/debian-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-debian . --push",
-        "build-docker-alpine": "docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:1.11.3-alpine --target release . --push",
-        "build-docker-debian": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.11.3 -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:1.11.3-debian --target release . --push",
+        "build-docker-alpine": "docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:1.11.4-alpine --target release . --push",
+        "build-docker-debian": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.11.4 -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:1.11.4-debian --target release . --push",
         "build-docker-nightly": "npm run build && docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push",
         "build-docker-nightly-alpine": "docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly-alpine --target nightly . --push",
         "build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain",
         "upload-artifacts": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg VERSION --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain",
-        "setup": "git checkout 1.11.3 && npm ci --production && npm run download-dist",
+        "setup": "git checkout 1.11.4 && npm ci --production && npm run download-dist",
         "download-dist": "node extra/download-dist.js",
         "update-version": "node extra/update-version.js",
         "mark-as-nightly": "node extra/mark-as-nightly.js",
diff --git a/server/model/monitor.js b/server/model/monitor.js
index c4441d63..eaba61ad 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -68,6 +68,7 @@ class Monitor extends BeanModel {
             type: this.type,
             interval: this.interval,
             retryInterval: this.retryInterval,
+            resendInterval: this.resendInterval,
             keyword: this.keyword,
             ignoreTls: this.getIgnoreTls(),
             upsideDown: this.isUpsideDown(),
@@ -135,6 +136,7 @@ class Monitor extends BeanModel {
             bean.monitor_id = this.id;
             bean.time = R.isoDateTime(dayjs.utc());
             bean.status = DOWN;
+            bean.lastNotifiedTime = previousBeat?.lastNotifiedTime || null; // after first update lastNotifiedTime will be undefined
 
             if (this.isUpsideDown()) {
                 bean.status = flipStatus(bean.status);
@@ -390,12 +392,27 @@ class Monitor extends BeanModel {
                 debug(`[${this.name}] sendNotification`);
                 await Monitor.sendNotification(isFirstBeat, this, bean);
 
+                // Set last notified time to now
+                bean.lastNotifiedTime = dayjs().valueOf();
+
                 // Clear Status Page Cache
                 debug(`[${this.name}] apicache clear`);
                 apicache.clear();
 
             } else {
                 bean.important = false;
+
+                if (bean.status === DOWN && this.resendInterval > 0) {
+                    timeSinceLastNotified = dayjs().valueOf() - (bean.lastNotifiedTime || 0);
+                    if (timeSinceLastNotified >= this.resendInterval) {
+                        // Send notification again, because we are still DOWN
+                        debug(`[${this.name}] sendNotification`);
+                        await Monitor.sendNotification(isFirstBeat, this, bean);
+
+                        // Set last notified time to now
+                        bean.lastNotifiedTime = dayjs().valueOf();
+                    }
+                }
             }
 
             if (bean.status === UP) {
diff --git a/server/server.js b/server/server.js
index 153cac4f..5a9cf944 100644
--- a/server/server.js
+++ b/server/server.js
@@ -588,6 +588,7 @@ exports.entryPage = "dashboard";
                 bean.basic_auth_pass = monitor.basic_auth_pass;
                 bean.interval = monitor.interval;
                 bean.retryInterval = monitor.retryInterval;
+                bean.resendInterval = monitor.resendInterval;
                 bean.hostname = monitor.hostname;
                 bean.maxretries = monitor.maxretries;
                 bean.port = monitor.port;
@@ -1082,6 +1083,7 @@ exports.entryPage = "dashboard";
                 let monitorListData = backupData.monitorList;
 
                 let version17x = compareVersions.compare(backupData.version, "1.7.0", ">=");
+                let version1114 = compareVersions.compare(backupData.version, "1.11.4", ">=");
 
                 // If the import option is "overwrite" it'll clear most of the tables, except "settings" and "user"
                 if (importHandle == "overwrite") {
@@ -1131,6 +1133,7 @@ exports.entryPage = "dashboard";
 
                             // Define default values
                             let retryInterval = 0;
+                            let resendInterval = 0;
 
                             /*
                             Only replace the default value with the backup file data for the specific version, where it appears the first time
@@ -1139,6 +1142,9 @@ exports.entryPage = "dashboard";
                             if (version17x) {
                                 retryInterval = monitorListData[i].retryInterval;
                             }
+                            if (version1114) {
+                                resendInterval = monitorListData[i].resendInterval;
+                            }
 
                             // --- End ---
 
@@ -1154,6 +1160,7 @@ exports.entryPage = "dashboard";
                                 basic_auth_pass: monitorListData[i].basic_auth_pass,
                                 interval: monitorListData[i].interval,
                                 retryInterval: retryInterval,
+                                resendInterval: resendInterval,
                                 hostname: monitorListData[i].hostname,
                                 maxretries: monitorListData[i].maxretries,
                                 port: monitorListData[i].port,
diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index 4b6a920c..b95c1098 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -137,6 +137,14 @@
                                 <input id="retry-interval" v-model="monitor.retryInterval" type="number" class="form-control" required min="20" step="1">
                             </div>
 
+                            <div class="my-3">
+                                <label for="resend-interval" class="form-label">
+                                    {{ $t("Notification resend Interval if Down") }}
+                                    <span>({{ $t("resendEverySecond", [ monitor.resendInterval ]) }})</span>
+                                </label>
+                                <input id="resend-interval" v-model="monitor.resendInterval" type="number" class="form-control" required min="20" step="1">
+                            </div>
+
                             <h2 v-if="monitor.type !== 'push'" class="mt-5 mb-2">{{ $t("Advanced") }}</h2>
 
                             <div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="my-3 form-check">

From b69a8b8493e095842bcf7daceaea21110106146a Mon Sep 17 00:00:00 2001
From: OidaTiftla <oidatiftla@oidatiftla.de>
Date: Sun, 23 Jan 2022 17:35:53 +0100
Subject: [PATCH 07/99] Fix formatting

Co-authored-by: Adam Stachowicz <saibamenppl@gmail.com>
---
 server/server.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/server/server.js b/server/server.js
index 5a9cf944..0ad361ad 100644
--- a/server/server.js
+++ b/server/server.js
@@ -1142,6 +1142,7 @@ exports.entryPage = "dashboard";
                             if (version17x) {
                                 retryInterval = monitorListData[i].retryInterval;
                             }
+
                             if (version1114) {
                                 resendInterval = monitorListData[i].resendInterval;
                             }

From 65fc71e4858ae61b7e1fb639ca335b8ad4f22ca4 Mon Sep 17 00:00:00 2001
From: OidaTiftla <oidatiftla@oidatiftla.de>
Date: Sun, 23 Jan 2022 17:34:39 +0100
Subject: [PATCH 08/99] Revert version change

---
 package-lock.json | 4 ++--
 package.json      | 8 ++++----
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 5253c3af..fc21a63f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
     "name": "uptime-kuma",
-    "version": "1.11.4",
+    "version": "1.11.3",
     "lockfileVersion": 2,
     "requires": true,
     "packages": {
         "": {
             "name": "uptime-kuma",
-            "version": "1.11.4",
+            "version": "1.11.3",
             "license": "MIT",
             "dependencies": {
                 "@fortawesome/fontawesome-svg-core": "~1.2.36",
diff --git a/package.json b/package.json
index cd522a31..048a5e0a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
     "name": "uptime-kuma",
-    "version": "1.11.4",
+    "version": "1.11.3",
     "license": "MIT",
     "repository": {
         "type": "git",
@@ -30,13 +30,13 @@
         "build-docker": "npm run build && npm run build-docker-debian && npm run build-docker-alpine",
         "build-docker-alpine-base": "docker buildx build -f docker/alpine-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-alpine . --push",
         "build-docker-debian-base": "docker buildx build -f docker/debian-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-debian . --push",
-        "build-docker-alpine": "docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:1.11.4-alpine --target release . --push",
-        "build-docker-debian": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.11.4 -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:1.11.4-debian --target release . --push",
+        "build-docker-alpine": "docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:1.11.3-alpine --target release . --push",
+        "build-docker-debian": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.11.3 -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:1.11.3-debian --target release . --push",
         "build-docker-nightly": "npm run build && docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push",
         "build-docker-nightly-alpine": "docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly-alpine --target nightly . --push",
         "build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain",
         "upload-artifacts": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg VERSION --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain",
-        "setup": "git checkout 1.11.4 && npm ci --production && npm run download-dist",
+        "setup": "git checkout 1.11.3 && npm ci --production && npm run download-dist",
         "download-dist": "node extra/download-dist.js",
         "update-version": "node extra/update-version.js",
         "mark-as-nightly": "node extra/mark-as-nightly.js",

From 11e9eee09d45996d476168d8c646962eb6104bd1 Mon Sep 17 00:00:00 2001
From: OidaTiftla <oidatiftla@oidatiftla.de>
Date: Sun, 23 Jan 2022 17:48:09 +0100
Subject: [PATCH 09/99] Change seconds to minutes

---
 server/model/monitor.js   | 2 +-
 src/languages/en.js       | 1 +
 src/pages/EditMonitor.vue | 3 ++-
 3 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/server/model/monitor.js b/server/model/monitor.js
index eaba61ad..f4803355 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -403,7 +403,7 @@ class Monitor extends BeanModel {
                 bean.important = false;
 
                 if (bean.status === DOWN && this.resendInterval > 0) {
-                    timeSinceLastNotified = dayjs().valueOf() - (bean.lastNotifiedTime || 0);
+                    timeSinceLastNotified = (dayjs().valueOf() - (bean.lastNotifiedTime || 0)) / 60; // divide by 60 to convert from seconds to minutes
                     if (timeSinceLastNotified >= this.resendInterval) {
                         // Send notification again, because we are still DOWN
                         debug(`[${this.name}] sendNotification`);
diff --git a/src/languages/en.js b/src/languages/en.js
index 47513466..21e215f7 100644
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -2,6 +2,7 @@ export default {
     languageName: "English",
     checkEverySecond: "Check every {0} seconds",
     retryCheckEverySecond: "Retry every {0} seconds",
+    resendEveryMinute: "Resend every {0} minutes if DOWN",
     retriesDescription: "Maximum retries before the service is marked as down and a notification is sent",
     ignoreTLSError: "Ignore TLS/SSL error for HTTPS websites",
     upsideDownModeDescription: "Flip the status upside down. If the service is reachable, it is DOWN.",
diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index b95c1098..3b4cbcf1 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -140,7 +140,7 @@
                             <div class="my-3">
                                 <label for="resend-interval" class="form-label">
                                     {{ $t("Notification resend Interval if Down") }}
-                                    <span>({{ $t("resendEverySecond", [ monitor.resendInterval ]) }})</span>
+                                    <span>({{ $t("resendEveryMinute", [ monitor.resendInterval ]) }})</span>
                                 </label>
                                 <input id="resend-interval" v-model="monitor.resendInterval" type="number" class="form-control" required min="20" step="1">
                             </div>
@@ -439,6 +439,7 @@ export default {
                     method: "GET",
                     interval: 60,
                     retryInterval: this.interval,
+                    resendInterval: 0,
                     maxretries: 0,
                     notificationIDList: {},
                     ignoreTls: false,

From f931e709e638f0720309d1c59cbf17fe34b622a1 Mon Sep 17 00:00:00 2001
From: OidaTiftla <oidatiftla@oidatiftla.de>
Date: Mon, 24 Jan 2022 09:18:12 +0100
Subject: [PATCH 10/99] Add database patch

---
 db/patch-monitor-add-resend-interval.sql | 7 +++++++
 server/database.js                       | 1 +
 2 files changed, 8 insertions(+)
 create mode 100644 db/patch-monitor-add-resend-interval.sql

diff --git a/db/patch-monitor-add-resend-interval.sql b/db/patch-monitor-add-resend-interval.sql
new file mode 100644
index 00000000..e8bb08b8
--- /dev/null
+++ b/db/patch-monitor-add-resend-interval.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 resend_interval INTEGER default 0 not null;
+
+COMMIT;
diff --git a/server/database.js b/server/database.js
index afcace70..ce4d5089 100644
--- a/server/database.js
+++ b/server/database.js
@@ -53,6 +53,7 @@ class Database {
         "patch-2fa-invalidate-used-token.sql": true,
         "patch-notification_sent_history.sql": true,
         "patch-monitor-basic-auth.sql": true,
+        "patch-monitor-add-resend-interval.sql": true,
     }
 
     /**

From 8c4ab9d652d931c44ffbc434c4d8599ee2aa5cd3 Mon Sep 17 00:00:00 2001
From: OidaTiftla <oidatiftla@oidatiftla.de>
Date: Mon, 24 Jan 2022 09:18:22 +0100
Subject: [PATCH 11/99] Simplify

---
 src/languages/en.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/languages/en.js b/src/languages/en.js
index 21e215f7..33ad0a42 100644
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -2,7 +2,7 @@ export default {
     languageName: "English",
     checkEverySecond: "Check every {0} seconds",
     retryCheckEverySecond: "Retry every {0} seconds",
-    resendEveryMinute: "Resend every {0} minutes if DOWN",
+    resendEveryMinute: "Resend every {0} minutes",
     retriesDescription: "Maximum retries before the service is marked as down and a notification is sent",
     ignoreTLSError: "Ignore TLS/SSL error for HTTPS websites",
     upsideDownModeDescription: "Flip the status upside down. If the service is reachable, it is DOWN.",

From 30ce53f57c3ada92b700c14cba6be822f687ee55 Mon Sep 17 00:00:00 2001
From: OidaTiftla <oidatiftla@oidatiftla.de>
Date: Mon, 24 Jan 2022 09:18:38 +0100
Subject: [PATCH 12/99] Fix min value of resend interval

---
 src/pages/EditMonitor.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index 3b4cbcf1..a297c54b 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -142,7 +142,7 @@
                                     {{ $t("Notification resend Interval if Down") }}
                                     <span>({{ $t("resendEveryMinute", [ monitor.resendInterval ]) }})</span>
                                 </label>
-                                <input id="resend-interval" v-model="monitor.resendInterval" type="number" class="form-control" required min="20" step="1">
+                                <input id="resend-interval" v-model="monitor.resendInterval" type="number" class="form-control" required min="0" step="1">
                             </div>
 
                             <h2 v-if="monitor.type !== 'push'" class="mt-5 mb-2">{{ $t("Advanced") }}</h2>

From f390a8caf1fea00348a3245b4c79d4315c125f3a Mon Sep 17 00:00:00 2001
From: OidaTiftla <oidatiftla@oidatiftla.de>
Date: Mon, 24 Jan 2022 21:59:25 +0100
Subject: [PATCH 13/99] Fix missing DB patch and use DATETIME as column format

---
 db/patch-heartbeat-add-last-notified-time.sql |  7 +++++++
 server/database.js                            |  1 +
 server/model/monitor.js                       | 10 +++++-----
 3 files changed, 13 insertions(+), 5 deletions(-)
 create mode 100644 db/patch-heartbeat-add-last-notified-time.sql

diff --git a/db/patch-heartbeat-add-last-notified-time.sql b/db/patch-heartbeat-add-last-notified-time.sql
new file mode 100644
index 00000000..af9c21c0
--- /dev/null
+++ b/db/patch-heartbeat-add-last-notified-time.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 heartbeat
+    ADD last_notified_time DATETIME default null;
+
+COMMIT;
diff --git a/server/database.js b/server/database.js
index ce4d5089..0aae8ffc 100644
--- a/server/database.js
+++ b/server/database.js
@@ -54,6 +54,7 @@ class Database {
         "patch-notification_sent_history.sql": true,
         "patch-monitor-basic-auth.sql": true,
         "patch-monitor-add-resend-interval.sql": true,
+        "patch-heartbeat-add-last-notified-time.sql": true,
     }
 
     /**
diff --git a/server/model/monitor.js b/server/model/monitor.js
index f4803355..85a0e944 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -136,7 +136,7 @@ class Monitor extends BeanModel {
             bean.monitor_id = this.id;
             bean.time = R.isoDateTime(dayjs.utc());
             bean.status = DOWN;
-            bean.lastNotifiedTime = previousBeat?.lastNotifiedTime || null; // after first update lastNotifiedTime will be undefined
+            bean.lastNotifiedTime = previousBeat?.lastNotifiedTime;
 
             if (this.isUpsideDown()) {
                 bean.status = flipStatus(bean.status);
@@ -393,7 +393,7 @@ class Monitor extends BeanModel {
                 await Monitor.sendNotification(isFirstBeat, this, bean);
 
                 // Set last notified time to now
-                bean.lastNotifiedTime = dayjs().valueOf();
+                bean.lastNotifiedTime = R.isoDateTime(dayjs.utc());
 
                 // Clear Status Page Cache
                 debug(`[${this.name}] apicache clear`);
@@ -403,14 +403,14 @@ class Monitor extends BeanModel {
                 bean.important = false;
 
                 if (bean.status === DOWN && this.resendInterval > 0) {
-                    timeSinceLastNotified = (dayjs().valueOf() - (bean.lastNotifiedTime || 0)) / 60; // divide by 60 to convert from seconds to minutes
+                    let timeSinceLastNotified = (dayjs.utc().valueOf() - (bean.lastNotifiedTime == null ? 0 : dayjs.utc(bean.lastNotifiedTime).valueOf())) / 1000 / 60; // divide by 1000 to convert from milliseconds to seconds and divide by 60 to convert from seconds to minutes
                     if (timeSinceLastNotified >= this.resendInterval) {
                         // Send notification again, because we are still DOWN
-                        debug(`[${this.name}] sendNotification`);
+                        debug(`[${this.name}] sendNotification again: lastNotifiedTime: ${bean.lastNotifiedTime} | current time: ${R.isoDateTime(dayjs.utc())}`);
                         await Monitor.sendNotification(isFirstBeat, this, bean);
 
                         // Set last notified time to now
-                        bean.lastNotifiedTime = dayjs().valueOf();
+                        bean.lastNotifiedTime = R.isoDateTime(dayjs.utc());
                     }
                 }
             }

From 855b12f435ca87059c2797b8695418947fc9b73e Mon Sep 17 00:00:00 2001
From: OidaTiftla <oidatiftla@oidatiftla.de>
Date: Mon, 24 Jan 2022 22:20:38 +0100
Subject: [PATCH 14/99] Add text for resend disabled

---
 src/languages/en.js       | 1 +
 src/pages/EditMonitor.vue | 3 ++-
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/languages/en.js b/src/languages/en.js
index 33ad0a42..17a58543 100644
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -3,6 +3,7 @@ export default {
     checkEverySecond: "Check every {0} seconds",
     retryCheckEverySecond: "Retry every {0} seconds",
     resendEveryMinute: "Resend every {0} minutes",
+    resendDisabled: "Resend disabled",
     retriesDescription: "Maximum retries before the service is marked as down and a notification is sent",
     ignoreTLSError: "Ignore TLS/SSL error for HTTPS websites",
     upsideDownModeDescription: "Flip the status upside down. If the service is reachable, it is DOWN.",
diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index a297c54b..a7bc4f78 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -140,7 +140,8 @@
                             <div class="my-3">
                                 <label for="resend-interval" class="form-label">
                                     {{ $t("Notification resend Interval if Down") }}
-                                    <span>({{ $t("resendEveryMinute", [ monitor.resendInterval ]) }})</span>
+                                    <span v-if="monitor.resendInterval > 0">({{ $t("resendEveryMinute", [ monitor.resendInterval ]) }})</span>
+                                    <span v-else>({{ $t("resendDisabled") }})</span>
                                 </label>
                                 <input id="resend-interval" v-model="monitor.resendInterval" type="number" class="form-control" required min="0" step="1">
                             </div>

From d446a57d42613490c6bd5a6bec075b289ff3caef Mon Sep 17 00:00:00 2001
From: OidaTiftla <oidatiftla@oidatiftla.de>
Date: Mon, 24 Jan 2022 22:20:48 +0100
Subject: [PATCH 15/99] Add german translation

---
 src/languages/de-DE.js | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/languages/de-DE.js b/src/languages/de-DE.js
index 48cdd2e3..9286a09b 100644
--- a/src/languages/de-DE.js
+++ b/src/languages/de-DE.js
@@ -164,6 +164,8 @@ export default {
     "Search...": "Suchen...",
     "Heartbeat Retry Interval": "Heartbeat-Wiederholungsintervall",
     retryCheckEverySecond: "Versuche alle {0} Sekunden",
+    resendEveryMinute: "Erneut versenden alle {0} Minuten",
+    resendDisabled: "Erneut versenden deaktiviert",
     "Import Backup": "Backup importieren",
     "Export Backup": "Backup exportieren",
     "Avg. Ping": "Durchschn. Ping",

From d8013f31e8906aeb0188725353392581621cd121 Mon Sep 17 00:00:00 2001
From: OidaTiftla <oidatiftla@oidatiftla.de>
Date: Sun, 27 Mar 2022 21:24:41 +0200
Subject: [PATCH 16/99] Update version after merging new master branch

---
 server/server.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/server/server.js b/server/server.js
index e10df8cc..36b8590f 100644
--- a/server/server.js
+++ b/server/server.js
@@ -1087,7 +1087,7 @@ exports.entryPage = "dashboard";
                 let monitorListData = backupData.monitorList;
 
                 let version17x = compareVersions.compare(backupData.version, "1.7.0", ">=");
-                let version1114 = compareVersions.compare(backupData.version, "1.11.4", ">=");
+                let version1132 = compareVersions.compare(backupData.version, "1.13.2", ">=");
 
                 // If the import option is "overwrite" it'll clear most of the tables, except "settings" and "user"
                 if (importHandle == "overwrite") {
@@ -1147,7 +1147,7 @@ exports.entryPage = "dashboard";
                                 retryInterval = monitorListData[i].retryInterval;
                             }
 
-                            if (version1114) {
+                            if (version1132) {
                                 resendInterval = monitorListData[i].resendInterval;
                             }
 

From 84a0b24448fc06f791bf4555fb2db8cd384c815b Mon Sep 17 00:00:00 2001
From: Moritz R <c0derMo@users.noreply.github.com>
Date: Sun, 3 Apr 2022 17:15:21 +0200
Subject: [PATCH 17/99] Update server/model/monitor.js

As per recommendation of @Computroniks

Co-authored-by: Matthew Nickson <mnickson@sidingsmedia.com>
---
 server/model/monitor.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/server/model/monitor.js b/server/model/monitor.js
index d8a4be23..c9b697d0 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -377,6 +377,7 @@ class Monitor extends BeanModel {
                         bean.status = UP;
                         bean.msg = "";
                     }
+                    
                 } else {
                     bean.msg = "Unknown Monitor Type";
                     bean.status = PENDING;

From 60f8ab7285fc0b3c1dfca5c8857807ba270e9956 Mon Sep 17 00:00:00 2001
From: OidaTiftla <oidatiftla@oidatiftla.de>
Date: Thu, 21 Apr 2022 12:09:59 +0200
Subject: [PATCH 18/99] Use new logging mechanism

---
 server/model/monitor.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/server/model/monitor.js b/server/model/monitor.js
index a7e0b82f..0ac2e33e 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -473,7 +473,7 @@ class Monitor extends BeanModel {
                     let timeSinceLastNotified = (dayjs.utc().valueOf() - (bean.lastNotifiedTime == null ? 0 : dayjs.utc(bean.lastNotifiedTime).valueOf())) / 1000 / 60; // divide by 1000 to convert from milliseconds to seconds and divide by 60 to convert from seconds to minutes
                     if (timeSinceLastNotified >= this.resendInterval) {
                         // Send notification again, because we are still DOWN
-                        debug(`[${this.name}] sendNotification again: lastNotifiedTime: ${bean.lastNotifiedTime} | current time: ${R.isoDateTime(dayjs.utc())}`);
+                        log.debug("monitor", `[${this.name}] sendNotification again: lastNotifiedTime: ${bean.lastNotifiedTime} | current time: ${R.isoDateTime(dayjs.utc())}`);
                         await Monitor.sendNotification(isFirstBeat, this, bean);
 
                         // Set last notified time to now

From 19933bbd99d7e11dba97e61183051dc876b9581e Mon Sep 17 00:00:00 2001
From: OidaTiftla <oidatiftla@oidatiftla.de>
Date: Thu, 21 Apr 2022 12:18:15 +0200
Subject: [PATCH 19/99] Improve backwards compatibility

---
 server/server.js | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

diff --git a/server/server.js b/server/server.js
index d53fe696..58c08f9d 100644
--- a/server/server.js
+++ b/server/server.js
@@ -1168,7 +1168,6 @@ try {
                 let monitorListData = backupData.monitorList;
 
                 let version17x = compareVersions.compare(backupData.version, "1.7.0", ">=");
-                let version1132 = compareVersions.compare(backupData.version, "1.13.2", ">=");
 
                 // If the import option is "overwrite" it'll clear most of the tables, except "settings" and "user"
                 if (importHandle == "overwrite") {
@@ -1237,7 +1236,6 @@ try {
 
                             // Define default values
                             let retryInterval = 0;
-                            let resendInterval = 0;
 
                             /*
                             Only replace the default value with the backup file data for the specific version, where it appears the first time
@@ -1247,10 +1245,6 @@ try {
                                 retryInterval = monitorListData[i].retryInterval;
                             }
 
-                            if (version1132) {
-                                resendInterval = monitorListData[i].resendInterval;
-                            }
-
                             // --- End ---
 
                             let monitor = {
@@ -1265,7 +1259,7 @@ try {
                                 basic_auth_pass: monitorListData[i].basic_auth_pass,
                                 interval: monitorListData[i].interval,
                                 retryInterval: retryInterval,
-                                resendInterval: resendInterval,
+                                resendInterval: monitorListData[i].resendInterval || 0,
                                 hostname: monitorListData[i].hostname,
                                 maxretries: monitorListData[i].maxretries,
                                 port: monitorListData[i].port,

From d6b591a513cb928c9173dab0b9042922a4b3df49 Mon Sep 17 00:00:00 2001
From: OidaTiftla <chm.stephan@outlook.com>
Date: Thu, 21 Apr 2022 17:45:58 +0200
Subject: [PATCH 20/99] Make comment more readable

Co-authored-by: Matthew Nickson <mnickson@sidingsmedia.com>
---
 server/model/monitor.js | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/server/model/monitor.js b/server/model/monitor.js
index 0ac2e33e..1383153e 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -470,7 +470,8 @@ class Monitor extends BeanModel {
                 bean.important = false;
 
                 if (bean.status === DOWN && this.resendInterval > 0) {
-                    let timeSinceLastNotified = (dayjs.utc().valueOf() - (bean.lastNotifiedTime == null ? 0 : dayjs.utc(bean.lastNotifiedTime).valueOf())) / 1000 / 60; // divide by 1000 to convert from milliseconds to seconds and divide by 60 to convert from seconds to minutes
+                    // divide by 1000 to convert from milliseconds to seconds and divide by 60 to convert from seconds to minutes
+                    let timeSinceLastNotified = (dayjs.utc().valueOf() - (bean.lastNotifiedTime == null ? 0 : dayjs.utc(bean.lastNotifiedTime).valueOf())) / 1000 / 60; 
                     if (timeSinceLastNotified >= this.resendInterval) {
                         // Send notification again, because we are still DOWN
                         log.debug("monitor", `[${this.name}] sendNotification again: lastNotifiedTime: ${bean.lastNotifiedTime} | current time: ${R.isoDateTime(dayjs.utc())}`);

From 052fde5a24daa70855082c4ed9ba362c3785e463 Mon Sep 17 00:00:00 2001
From: OidaTiftla <chm.stephan@outlook.com>
Date: Thu, 21 Apr 2022 17:56:38 +0200
Subject: [PATCH 21/99] Fix casing of text label

Co-authored-by: Matthew Nickson <mnickson@sidingsmedia.com>
---
 src/pages/EditMonitor.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index 4338b4ea..661a89c4 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -172,7 +172,7 @@
 
                             <div class="my-3">
                                 <label for="resend-interval" class="form-label">
-                                    {{ $t("Notification resend Interval if Down") }}
+                                    {{ $t("Notification resend interval if down") }}
                                     <span v-if="monitor.resendInterval > 0">({{ $t("resendEveryMinute", [ monitor.resendInterval ]) }})</span>
                                     <span v-else>({{ $t("resendDisabled") }})</span>
                                 </label>

From c7ec9a07e248a730095e725c870f8396dfaa2296 Mon Sep 17 00:00:00 2001
From: OidaTiftla <oidatiftla@oidatiftla.de>
Date: Thu, 21 Apr 2022 17:59:38 +0200
Subject: [PATCH 22/99] Add translation for text label

---
 src/languages/de-DE.js | 1 +
 src/languages/en.js    | 1 +
 2 files changed, 2 insertions(+)

diff --git a/src/languages/de-DE.js b/src/languages/de-DE.js
index ae28bf5b..5af4c8a1 100644
--- a/src/languages/de-DE.js
+++ b/src/languages/de-DE.js
@@ -163,6 +163,7 @@ export default {
     Pink: "Pink",
     "Search...": "Suchen...",
     "Heartbeat Retry Interval": "Überprüfungsintervall",
+    "Notification resend interval if down": "Benachrichtigung erneut versenden wenn Inaktiv",
     retryCheckEverySecond: "Alle {0} Sekunden neu versuchen",
     resendEveryMinute: "Erneut versenden alle {0} Minuten",
     resendDisabled: "Erneut versenden deaktiviert",
diff --git a/src/languages/en.js b/src/languages/en.js
index 7726d12f..a3a375f3 100644
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -74,6 +74,7 @@ export default {
     "Heartbeat Interval": "Heartbeat Interval",
     Retries: "Retries",
     "Heartbeat Retry Interval": "Heartbeat Retry Interval",
+    "Notification resend interval if down": "Notification resend interval if down",
     Advanced: "Advanced",
     "Upside Down Mode": "Upside Down Mode",
     "Max. Redirects": "Max. Redirects",

From 7ed8ae9f7cc35e24c29bab087c5324d764bf67dc Mon Sep 17 00:00:00 2001
From: OidaTiftla <oidatiftla@oidatiftla.de>
Date: Thu, 21 Apr 2022 18:23:32 +0200
Subject: [PATCH 23/99] Fix trailing space warning

---
 server/model/monitor.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/server/model/monitor.js b/server/model/monitor.js
index 1383153e..84b211b8 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -471,7 +471,7 @@ class Monitor extends BeanModel {
 
                 if (bean.status === DOWN && this.resendInterval > 0) {
                     // divide by 1000 to convert from milliseconds to seconds and divide by 60 to convert from seconds to minutes
-                    let timeSinceLastNotified = (dayjs.utc().valueOf() - (bean.lastNotifiedTime == null ? 0 : dayjs.utc(bean.lastNotifiedTime).valueOf())) / 1000 / 60; 
+                    let timeSinceLastNotified = (dayjs.utc().valueOf() - (bean.lastNotifiedTime == null ? 0 : dayjs.utc(bean.lastNotifiedTime).valueOf())) / 1000 / 60;
                     if (timeSinceLastNotified >= this.resendInterval) {
                         // Send notification again, because we are still DOWN
                         log.debug("monitor", `[${this.name}] sendNotification again: lastNotifiedTime: ${bean.lastNotifiedTime} | current time: ${R.isoDateTime(dayjs.utc())}`);

From 98ee9caf2cdc54a2f5edb864906d620e84196317 Mon Sep 17 00:00:00 2001
From: OidaTiftla <chm.stephan@outlook.com>
Date: Thu, 5 May 2022 15:55:33 +0200
Subject: [PATCH 24/99] Add variable for currentTime

Co-authored-by: Adam Stachowicz <saibamenppl@gmail.com>
---
 server/model/monitor.js | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/server/model/monitor.js b/server/model/monitor.js
index 15181af6..f29f6de5 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -492,11 +492,12 @@ class Monitor extends BeanModel {
                     let timeSinceLastNotified = (dayjs.utc().valueOf() - (bean.lastNotifiedTime == null ? 0 : dayjs.utc(bean.lastNotifiedTime).valueOf())) / 1000 / 60;
                     if (timeSinceLastNotified >= this.resendInterval) {
                         // Send notification again, because we are still DOWN
-                        log.debug("monitor", `[${this.name}] sendNotification again: lastNotifiedTime: ${bean.lastNotifiedTime} | current time: ${R.isoDateTime(dayjs.utc())}`);
+                        const currentTime = R.isoDateTime(dayjs.utc());
+                        log.debug("monitor", `[${this.name}] sendNotification again: lastNotifiedTime: ${bean.lastNotifiedTime} | current time: ${currentTime}`);
                         await Monitor.sendNotification(isFirstBeat, this, bean);
 
                         // Set last notified time to now
-                        bean.lastNotifiedTime = R.isoDateTime(dayjs.utc());
+                        bean.lastNotifiedTime = currentTime;
                     }
                 }
             }

From 93050208bbe9eb1c5678bf609c18e47953fb8485 Mon Sep 17 00:00:00 2001
From: OidaTiftla <oidatiftla@oidatiftla.de>
Date: Thu, 5 May 2022 16:01:10 +0200
Subject: [PATCH 25/99] Merge database changes into single patch file

---
 db/patch-heartbeat-add-last-notified-time.sql | 7 -------
 db/patch-monitor-add-resend-interval.sql      | 3 +++
 server/database.js                            | 1 -
 3 files changed, 3 insertions(+), 8 deletions(-)
 delete mode 100644 db/patch-heartbeat-add-last-notified-time.sql

diff --git a/db/patch-heartbeat-add-last-notified-time.sql b/db/patch-heartbeat-add-last-notified-time.sql
deleted file mode 100644
index af9c21c0..00000000
--- a/db/patch-heartbeat-add-last-notified-time.sql
+++ /dev/null
@@ -1,7 +0,0 @@
--- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
-BEGIN TRANSACTION;
-
-ALTER TABLE heartbeat
-    ADD last_notified_time DATETIME default null;
-
-COMMIT;
diff --git a/db/patch-monitor-add-resend-interval.sql b/db/patch-monitor-add-resend-interval.sql
index e8bb08b8..c31dd7a2 100644
--- a/db/patch-monitor-add-resend-interval.sql
+++ b/db/patch-monitor-add-resend-interval.sql
@@ -4,4 +4,7 @@ BEGIN TRANSACTION;
 ALTER TABLE monitor
     ADD resend_interval INTEGER default 0 not null;
 
+ALTER TABLE heartbeat
+    ADD last_notified_time DATETIME default null;
+
 COMMIT;
diff --git a/server/database.js b/server/database.js
index 8e3b188b..5dbfd676 100644
--- a/server/database.js
+++ b/server/database.js
@@ -59,7 +59,6 @@ class Database {
         "patch-status-page-footer-css.sql": true,
         "patch-added-mqtt-monitor.sql": true,
         "patch-monitor-add-resend-interval.sql": true,
-        "patch-heartbeat-add-last-notified-time.sql": true,
     };
 
     /**

From 398ecb76667710731bc065f35b78706125fc8077 Mon Sep 17 00:00:00 2001
From: Sascha Kruse <sascha.kruse@frederix-hotspot.de>
Date: Thu, 12 May 2022 11:48:38 +0200
Subject: [PATCH 26/99] add radius check

---
 db/patch-add-radius-monitor.sql | 18 ++++++++++++++++
 package.json                    |  1 +
 server/database.js              |  1 +
 server/model/monitor.js         | 33 +++++++++++++++++++++++++++--
 server/server.js                |  5 +++++
 server/util-server.js           | 30 ++++++++++++++++++++++++++
 src/languages/en.js             |  6 ++++++
 src/pages/EditMonitor.vue       | 37 +++++++++++++++++++++++++++++++--
 8 files changed, 127 insertions(+), 4 deletions(-)
 create mode 100644 db/patch-add-radius-monitor.sql

diff --git a/db/patch-add-radius-monitor.sql b/db/patch-add-radius-monitor.sql
new file mode 100644
index 00000000..1fd5b44f
--- /dev/null
+++ b/db/patch-add-radius-monitor.sql
@@ -0,0 +1,18 @@
+BEGIN TRANSACTION;
+
+ALTER TABLE monitor
+    ADD radius_username VARCHAR(255);
+
+ALTER TABLE monitor
+    ADD radius_password VARCHAR(255);
+
+ALTER TABLE monitor
+    ADD radius_calling_station_id VARCHAR(50);
+
+ALTER TABLE monitor
+    ADD radius_called_station_id VARCHAR(50);
+
+ALTER TABLE monitor
+    ADD radius_secret VARCHAR(255);
+
+COMMIT
diff --git a/package.json b/package.json
index e9b7003b..304a466e 100644
--- a/package.json
+++ b/package.json
@@ -93,6 +93,7 @@
         "limiter": "^2.1.0",
         "mqtt": "^4.2.8",
         "node-cloudflared-tunnel": "~1.0.9",
+        "node-radius-client": "^1.0.0",
         "nodemailer": "~6.6.5",
         "notp": "~2.0.3",
         "password-hash": "~1.2.2",
diff --git a/server/database.js b/server/database.js
index b17e7f4e..4ce509f3 100644
--- a/server/database.js
+++ b/server/database.js
@@ -58,6 +58,7 @@ class Database {
         "patch-monitor-expiry-notification.sql": true,
         "patch-status-page-footer-css.sql": true,
         "patch-added-mqtt-monitor.sql": true,
+        "patch-add-radius-monitor.sql": true,
     };
 
     /**
diff --git a/server/model/monitor.js b/server/model/monitor.js
index f2d16524..0b8fade4 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 { log, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util");
-const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mqttAsync } = require("../util-server");
+const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, radius, setting, mqttAsync } = require("../util-server");
 const { R } = require("redbean-node");
 const { BeanModel } = require("redbean-node/dist/bean-model");
 const { Notification } = require("../notification");
@@ -87,7 +87,12 @@ class Monitor extends BeanModel {
             mqttUsername: this.mqttUsername,
             mqttPassword: this.mqttPassword,
             mqttTopic: this.mqttTopic,
-            mqttSuccessMessage: this.mqttSuccessMessage
+            mqttSuccessMessage: this.mqttSuccessMessage,
+            radiusUsername: this.radiusUsername,
+            radiusPassword: this.radiusPassword,
+            radiusCalledStationId: this.radiusCalledStationId,
+            radiusCallingStationId: this.radiusCallingStationId,
+            radiusSecret: this.radiusSecret
         };
 
         if (includeSensitiveData) {
@@ -435,6 +440,30 @@ class Monitor extends BeanModel {
                         interval: this.interval,
                     });
                     bean.status = UP;
+                } else if (this.type === "radius") {
+                    let startTime = dayjs().valueOf();
+                    try {
+                        const resp = await radius(
+                            this.hostname,
+                            this.radiusUsername,
+                            this.radiusPassword,
+                            this.radiusCalledStationId,
+                            this.radiusCallingStationId,
+                            this.radiusSecret
+                        );
+                        if (resp.code) {
+                            bean.msg = resp.code;
+                        }
+                        bean.status = UP;
+                    } catch (error) {
+                        bean.status = DOWN;
+                        if (error.response?.code) {
+                            bean.msg = error.response.code;
+                        } else {
+                            bean.msg = error.message;
+                        }
+                    }
+                    bean.ping = dayjs().valueOf() - startTime;
                 } else {
                     bean.msg = "Unknown Monitor Type";
                     bean.status = PENDING;
diff --git a/server/server.js b/server/server.js
index 79cb2102..55b10816 100644
--- a/server/server.js
+++ b/server/server.js
@@ -674,6 +674,11 @@ try {
                 bean.mqttPassword = monitor.mqttPassword;
                 bean.mqttTopic = monitor.mqttTopic;
                 bean.mqttSuccessMessage = monitor.mqttSuccessMessage;
+                bean.radiusUsername = monitor.radiusUsername;
+                bean.radiusPassword = monitor.radiusPassword;
+                bean.radiusCalledStationId = monitor.radiusCalledStationId;
+                bean.radiusCallingStationId = monitor.radiusCallingStationId;
+                bean.radiusSecret = monitor.radiusSecret;
 
                 await R.store(bean);
 
diff --git a/server/util-server.js b/server/util-server.js
index 54974e14..5dd81e00 100644
--- a/server/util-server.js
+++ b/server/util-server.js
@@ -10,6 +10,12 @@ const chardet = require("chardet");
 const mqtt = require("mqtt");
 const chroma = require("chroma-js");
 const { badgeConstants } = require("./config");
+const radiusClient = require("node-radius-client");
+const {
+    dictionaries: {
+        rfc2865: { file, attributes },
+    },
+} = require("node-radius-utils");
 
 // From ping-lite
 exports.WIN = /^win/.test(process.platform);
@@ -203,6 +209,30 @@ exports.dnsResolve = function (hostname, resolverServer, rrtype) {
     });
 };
 
+exports.radius = function (
+    hostname,
+    username,
+    password,
+    calledStationId,
+    callingStationId,
+    secret,
+) {
+    const client = new radiusClient({
+        host: hostname,
+        dictionaries: [ file ],
+    });
+
+    return client.accessRequest({
+        secret: secret,
+        attributes: [
+            [ attributes.USER_NAME, username ],
+            [ attributes.USER_PASSWORD, password ],
+            [ attributes.CALLING_STATION_ID, callingStationId ],
+            [ attributes.CALLED_STATION_ID, calledStationId ],
+        ],
+    });
+};
+
 /**
  * Retrieve value of setting based on key
  * @param {string} key Key of setting to retrieve
diff --git a/src/languages/en.js b/src/languages/en.js
index ab73ce35..13826453 100644
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -464,4 +464,10 @@ export default {
     "Domain Names": "Domain Names",
     signedInDisp: "Signed in as {0}",
     signedInDispDisabled: "Auth Disabled.",
+    RadiusSecret: "Radius Secret",
+    RadiusSecretDescription: "Shared Secret between client and server",
+    RadiusCalledStationId: "Called Station Id",
+    RadiusCalledStationIdDescription: "Identifier of the called device",
+    RadiusCallingStationId: "Calling Station Id",
+    RadiusCallingStationIdDescription: "Identifier of the calling device",
 };
diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index 43f34527..e2beaca1 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -35,6 +35,9 @@
                                     <option value="mqtt">
                                         MQTT
                                     </option>
+                                    <option value="radius">
+                                        Radius
+                                    </option>
                                 </select>
                             </div>
 
@@ -70,8 +73,8 @@
                             </div>
 
                             <!-- Hostname -->
-                            <!-- TCP Port / Ping / DNS / Steam / MQTT only -->
-                            <div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' || monitor.type === 'steam' || monitor.type === 'mqtt'" class="my-3">
+                            <!-- TCP Port / Ping / DNS / Steam / MQTT / Radius only -->
+                            <div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' || monitor.type === 'steam' || monitor.type === 'mqtt' || monitor.type === 'radius'" class="my-3">
                                 <label for="hostname" class="form-label">{{ $t("Hostname") }}</label>
                                 <input id="hostname" v-model="monitor.hostname" type="text" class="form-control" :pattern="`${ipRegexPattern}|${hostnameRegexPattern}`" required>
                             </div>
@@ -148,6 +151,36 @@
                                 </div>
                             </template>
 
+                            <template v-if="monitor.type === 'radius'">
+                                <div class="my-3">
+                                    <label for="radius_username" class="form-label">Radius {{ $t("Username") }}</label>
+                                    <input id="radius_username" v-model="monitor.radiusUsername" type="text" class="form-control" required />
+                                </div>
+
+                                <div class="my-3">
+                                    <label for="radius_password" class="form-label">Radius {{ $t("Password") }}</label>
+                                    <input id="radius_password" v-model="monitor.radiusPassword" type="password" class="form-control" required />
+                                </div>
+
+                                <div class="my-3">
+                                    <label for="radius_secret" class="form-label">{{ $t("RadiusSecret") }}</label>
+                                    <input id="radius_secret" v-model="monitor.radiusSecret" type="password" class="form-control" required />
+                                    <div class="form-text"> {{ $t( "RadiusSecretDescription") }} </div>
+                                </div>
+
+                                <div class="my-3">
+                                    <label for="radius_called_station_id" class="form-label">{{ $t("RadiusCalledStationId") }}</label>
+                                    <input id="radius_called_station_id" v-model="monitor.radiusCalledStationId" type="text" class="form-control" required />
+                                    <div class="form-text"> {{ $t( "RadiusCalledStationIdDescription") }} </div>
+                                </div>
+
+                                <div class="my-3">
+                                    <label for="radius_calling_station_id" class="form-label">{{ $t("RadiusCallingStationId") }}</label>
+                                    <input id="radius_calling_station_id" v-model="monitor.radiusCallingStationId" type="text" class="form-control" required />
+                                    <div class="form-text"> {{ $t( "RadiusCallingStationIdDescription") }} </div>
+                                </div>
+                            </template>
+
                             <!-- Interval -->
                             <div class="my-3">
                                 <label for="interval" class="form-label">{{ $t("Heartbeat Interval") }} ({{ $t("checkEverySecond", [ monitor.interval ]) }})</label>

From 42d68edab07881e4d9ffe88349c8de2a0db85b1f Mon Sep 17 00:00:00 2001
From: Sascha Kruse <knopwob@knopwob.de>
Date: Wed, 18 May 2022 15:55:36 +0200
Subject: [PATCH 27/99] (style) add trailing comma

Co-authored-by: Adam Stachowicz <saibamenppl@gmail.com>
---
 server/model/monitor.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/server/model/monitor.js b/server/model/monitor.js
index 0b8fade4..71b4255c 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -92,7 +92,7 @@ class Monitor extends BeanModel {
             radiusPassword: this.radiusPassword,
             radiusCalledStationId: this.radiusCalledStationId,
             radiusCallingStationId: this.radiusCallingStationId,
-            radiusSecret: this.radiusSecret
+            radiusSecret: this.radiusSecret,
         };
 
         if (includeSensitiveData) {

From 32cfd411f87ed98bd3247a7751b2f4791fbd388e Mon Sep 17 00:00:00 2001
From: c0derMo <jaydeveloper@outlook.de>
Date: Thu, 19 May 2022 12:35:55 +0000
Subject: [PATCH 28/99] Fixed style & code errors

---
 server/model/monitor.js   | 4 ++--
 src/pages/EditMonitor.vue | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/server/model/monitor.js b/server/model/monitor.js
index c7e2cd31..2bc40b5f 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -429,7 +429,7 @@ class Monitor extends BeanModel {
                         throw new Error("Server not found on Steam");
                     }
                 } else if (this.type === "docker") {
-                    debug(`[${this.name}] Prepare Options for Axios`);
+                    log.debug(`[${this.name}] Prepare Options for Axios`);
 
                     const options = {
                         url: `/containers/${this.docker_container}/json`,
@@ -449,7 +449,7 @@ class Monitor extends BeanModel {
                         options.baseURL = this.docker_daemon;
                     }
 
-                    debug(`[${this.name}] Axios Request`);
+                    log.debug(`[${this.name}] Axios Request`);
                     let res = await axios.request(options);
                     if (res.data.State.Running) {
                         bean.status = UP;
diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index 56c9ac92..1d156883 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -120,7 +120,7 @@
                                     </div>
                                 </div>
                             </template>
-                            
+
                             <!-- Docker Container Name / ID -->
                             <!-- For Docker Type -->
                             <div v-if="monitor.type === 'docker'" class="my-3">

From 5f6347d277695f8af5a68b186b373809f7a03272 Mon Sep 17 00:00:00 2001
From: Daeho Ro <lamanus@outlook.kr>
Date: Sun, 12 Jun 2022 04:02:44 +0900
Subject: [PATCH 29/99] pull request for adding alertnow notification

---
 server/notification-providers/alertnow.js |  50 ++++++++++
 server/notification.js                    |  68 ++++++-------
 src/components/notifications/AlertNow.vue |  13 +++
 src/components/notifications/index.js     | 112 +++++++++++-----------
 4 files changed, 155 insertions(+), 88 deletions(-)
 create mode 100644 server/notification-providers/alertnow.js
 create mode 100644 src/components/notifications/AlertNow.vue

diff --git a/server/notification-providers/alertnow.js b/server/notification-providers/alertnow.js
new file mode 100644
index 00000000..d778b01d
--- /dev/null
+++ b/server/notification-providers/alertnow.js
@@ -0,0 +1,50 @@
+const NotificationProvider = require("./notification-provider");
+const axios = require("axios");
+const { setting } = require("../util-server");
+const { getMonitorRelativeURL, UP, DOWN } = require("../../src/util");
+
+class AlertNow extends NotificationProvider {
+
+    name = "AlertNow";
+
+    async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
+        let okMsg = "Sent Successfully.";
+        try {
+            let textMsg = "";
+            let status = "open";
+            let eventType = "ERROR";
+            let eventId = new Date().toISOString().slice(0, 10).replace(/-/g, "");
+
+            if (heartbeatJSON && heartbeatJSON.status === UP) {
+                textMsg = `[${heartbeatJSON.name}] ✅ Application is back online`;
+                status = "close";
+                eventType = "INFO";
+                eventId += `_${heartbeatJSON.name.replace(/\s/g, "")}`;
+            } else if (heartbeatJSON && heartbeatJSON.status === DOWN) {
+                textMsg = `[${heartbeatJSON.name}] 🔴 Application went down`;
+            }
+
+            textMsg += ` - ${msg}`;
+
+            const baseURL = await setting("primaryBaseURL");
+            if (baseURL && monitorJSON) {
+                textMsg += ` >> ${baseURL + getMonitorRelativeURL(monitorJSON.id)}`;
+            }
+
+            const data = {
+                "summary": textMsg,
+                "status": status,
+                "event_type": eventType,
+                "event_id": eventId,
+            };
+
+            await axios.post(notification.alertNowWebhookURL, data);
+            return okMsg;
+        } catch (error) {
+            this.throwGeneralAxiosError(error);
+        }
+
+    }
+}
+
+module.exports = AlertNow;
diff --git a/server/notification.js b/server/notification.js
index c457ed14..a3b3a70b 100644
--- a/server/notification.js
+++ b/server/notification.js
@@ -1,40 +1,41 @@
 const { R } = require("redbean-node");
+const { log } = require("../src/util");
+const Alerta = require("./notification-providers/alerta");
+const AlertNow = require("./notification-providers/alertnow");
+const AliyunSms = require("./notification-providers/aliyun-sms");
 const Apprise = require("./notification-providers/apprise");
+const Bark = require("./notification-providers/bark");
+const ClickSendSMS = require("./notification-providers/clicksendsms");
+const DingDing = require("./notification-providers/dingding");
 const Discord = require("./notification-providers/discord");
+const Feishu = require("./notification-providers/feishu");
+const GoogleChat = require("./notification-providers/google-chat");
+const Gorush = require("./notification-providers/gorush");
 const Gotify = require("./notification-providers/gotify");
-const Ntfy = require("./notification-providers/ntfy");
 const Line = require("./notification-providers/line");
 const LunaSea = require("./notification-providers/lunasea");
-const Mattermost = require("./notification-providers/mattermost");
 const Matrix = require("./notification-providers/matrix");
+const Mattermost = require("./notification-providers/mattermost");
+const Ntfy = require("./notification-providers/ntfy");
 const Octopush = require("./notification-providers/octopush");
+const OneBot = require("./notification-providers/onebot");
+const PagerDuty = require("./notification-providers/pagerduty");
 const PromoSMS = require("./notification-providers/promosms");
-const ClickSendSMS = require("./notification-providers/clicksendsms");
 const Pushbullet = require("./notification-providers/pushbullet");
+const PushDeer = require("./notification-providers/pushdeer");
 const Pushover = require("./notification-providers/pushover");
 const Pushy = require("./notification-providers/pushy");
-const TechulusPush = require("./notification-providers/techulus-push");
 const RocketChat = require("./notification-providers/rocket-chat");
+const SerwerSMS = require("./notification-providers/serwersms");
 const Signal = require("./notification-providers/signal");
 const Slack = require("./notification-providers/slack");
 const SMTP = require("./notification-providers/smtp");
+const Stackfield = require("./notification-providers/stackfield");
 const Teams = require("./notification-providers/teams");
+const TechulusPush = require("./notification-providers/techulus-push");
 const Telegram = require("./notification-providers/telegram");
 const Webhook = require("./notification-providers/webhook");
-const Feishu = require("./notification-providers/feishu");
-const AliyunSms = require("./notification-providers/aliyun-sms");
-const DingDing = require("./notification-providers/dingding");
-const Bark = require("./notification-providers/bark");
-const { log } = require("../src/util");
-const SerwerSMS = require("./notification-providers/serwersms");
-const Stackfield = require("./notification-providers/stackfield");
 const WeCom = require("./notification-providers/wecom");
-const GoogleChat = require("./notification-providers/google-chat");
-const PagerDuty = require("./notification-providers/pagerduty");
-const Gorush = require("./notification-providers/gorush");
-const Alerta = require("./notification-providers/alerta");
-const OneBot = require("./notification-providers/onebot");
-const PushDeer = require("./notification-providers/pushdeer");
 
 class Notification {
 
@@ -47,41 +48,42 @@ class Notification {
         this.providerList = {};
 
         const list = [
-            new Apprise(),
+            new Alerta(),
+            new AlertNow(),
             new AliyunSms(),
+            new Apprise(),
+            new Bark(),
+            new ClickSendSMS(),
             new DingDing(),
             new Discord(),
-            new Teams(),
+            new Feishu(),
+            new GoogleChat(),
+            new Gorush(),
             new Gotify(),
-            new Ntfy(),
             new Line(),
             new LunaSea(),
-            new Feishu(),
-            new Mattermost(),
             new Matrix(),
+            new Mattermost(),
+            new Ntfy(),
             new Octopush(),
+            new OneBot(),
+            new PagerDuty(),
             new PromoSMS(),
-            new ClickSendSMS(),
             new Pushbullet(),
+            new PushDeer(),
             new Pushover(),
             new Pushy(),
-            new TechulusPush(),
             new RocketChat(),
+            new SerwerSMS(),
             new Signal(),
             new Slack(),
             new SMTP(),
+            new Stackfield(),
+            new Teams(),
+            new TechulusPush(),
             new Telegram(),
             new Webhook(),
-            new Bark(),
-            new SerwerSMS(),
-            new Stackfield(),
             new WeCom(),
-            new GoogleChat(),
-            new PagerDuty(),
-            new Gorush(),
-            new Alerta(),
-            new OneBot(),
-            new PushDeer(),
         ];
 
         for (let item of list) {
diff --git a/src/components/notifications/AlertNow.vue b/src/components/notifications/AlertNow.vue
new file mode 100644
index 00000000..93acc959
--- /dev/null
+++ b/src/components/notifications/AlertNow.vue
@@ -0,0 +1,13 @@
+<template>
+    <div class="mb-3">
+        <label for="alertnow-webhook-url" class="form-label">{{ $t("Webhook URL") }}<span style="color: red;"><sup>*</sup></span></label>
+        <input id="alertnow-webhook-url" v-model="$parent.notification.alertNowWebhookURL" type="text" class="form-control" required>
+
+        <div class="form-text">
+            <span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}
+            <i18n-t tag="p" keypath="aboutWebhooks" style="margin-top: 8px;">
+                <a href="https://service.opsnow.com/docs/alertnow/en/user-guide-alertnow-en.html#standard" target="_blank">{{ $t("here") }}</a>
+            </i18n-t>
+        </div>
+    </div>
+</template>
diff --git a/src/components/notifications/index.js b/src/components/notifications/index.js
index 18c316a5..e5cbe8ce 100644
--- a/src/components/notifications/index.js
+++ b/src/components/notifications/index.js
@@ -1,38 +1,39 @@
-import STMP from "./SMTP.vue";
-import Telegram from "./Telegram.vue";
+import Alerta from "./Alerta.vue";
+import AlertNow from "./AlertNow.vue";
+import AliyunSMS from "./AliyunSms.vue";
+import Apprise from "./Apprise.vue";
+import Bark from "./Bark.vue";
+import ClickSendSMS from "./ClickSendSMS.vue";
+import DingDing from "./DingDing.vue";
 import Discord from "./Discord.vue";
-import Webhook from "./Webhook.vue";
-import Signal from "./Signal.vue";
+import Feishu from "./Feishu.vue";
+import GoogleChat from "./GoogleChat.vue";
+import Gorush from "./Gorush.vue";
 import Gotify from "./Gotify.vue";
+import Line from "./Line.vue";
+import LunaSea from "./LunaSea.vue";
+import Matrix from "./Matrix.vue";
+import Mattermost from "./Mattermost.vue";
 import Ntfy from "./Ntfy.vue";
-import Slack from "./Slack.vue";
-import RocketChat from "./RocketChat.vue";
-import Teams from "./Teams.vue";
+import Octopush from "./Octopush.vue";
+import OneBot from "./OneBot.vue";
+import PagerDuty from "./PagerDuty.vue";
+import PromoSMS from "./PromoSMS.vue";
+import Pushbullet from "./Pushbullet.vue";
+import PushDeer from "./PushDeer.vue";
 import Pushover from "./Pushover.vue";
 import Pushy from "./Pushy.vue";
-import TechulusPush from "./TechulusPush.vue";
-import Octopush from "./Octopush.vue";
-import PromoSMS from "./PromoSMS.vue";
-import ClickSendSMS from "./ClickSendSMS.vue";
-import LunaSea from "./LunaSea.vue";
-import Feishu from "./Feishu.vue";
-import Apprise from "./Apprise.vue";
-import Pushbullet from "./Pushbullet.vue";
-import Line from "./Line.vue";
-import Mattermost from "./Mattermost.vue";
-import Matrix from "./Matrix.vue";
-import AliyunSMS from "./AliyunSms.vue";
-import DingDing from "./DingDing.vue";
-import Bark from "./Bark.vue";
+import RocketChat from "./RocketChat.vue";
 import SerwerSMS from "./SerwerSMS.vue";
+import Signal from "./Signal.vue";
+import Slack from "./Slack.vue";
 import Stackfield from "./Stackfield.vue";
+import STMP from "./SMTP.vue";
+import Teams from "./Teams.vue";
+import TechulusPush from "./TechulusPush.vue";
+import Telegram from "./Telegram.vue";
+import Webhook from "./Webhook.vue";
 import WeCom from "./WeCom.vue";
-import GoogleChat from "./GoogleChat.vue";
-import PagerDuty from "./PagerDuty.vue";
-import Gorush from "./Gorush.vue";
-import Alerta from "./Alerta.vue";
-import OneBot from "./OneBot.vue";
-import PushDeer from "./PushDeer.vue";
 
 /**
  * Manage all notification form.
@@ -40,41 +41,42 @@ import PushDeer from "./PushDeer.vue";
  * @type { Record<string, any> }
  */
 const NotificationFormList = {
-    "telegram": Telegram,
-    "webhook": Webhook,
-    "smtp": STMP,
-    "discord": Discord,
-    "teams": Teams,
-    "signal": Signal,
-    "gotify": Gotify,
-    "ntfy": Ntfy,
-    "slack": Slack,
-    "rocket.chat": RocketChat,
-    "pushover": Pushover,
-    "pushy": Pushy,
-    "PushByTechulus": TechulusPush,
-    "octopush": Octopush,
-    "promosms": PromoSMS,
-    "clicksendsms": ClickSendSMS,
-    "lunasea": LunaSea,
-    "Feishu": Feishu,
+    "alerta": Alerta,
+    "AlertNow": AlertNow,
     "AliyunSMS": AliyunSMS,
     "apprise": Apprise,
-    "pushbullet": Pushbullet,
-    "line": Line,
-    "mattermost": Mattermost,
-    "matrix": Matrix,
-    "DingDing": DingDing,
     "Bark": Bark,
-    "serwersms": SerwerSMS,
-    "stackfield": Stackfield,
-    "WeCom": WeCom,
+    "clicksendsms": ClickSendSMS,
+    "DingDing": DingDing,
+    "discord": Discord,
+    "Feishu": Feishu,
     "GoogleChat": GoogleChat,
-    "PagerDuty": PagerDuty,
     "gorush": Gorush,
-    "alerta": Alerta,
+    "gotify": Gotify,
+    "line": Line,
+    "lunasea": LunaSea,
+    "matrix": Matrix,
+    "mattermost": Mattermost,
+    "ntfy": Ntfy,
+    "octopush": Octopush,
     "OneBot": OneBot,
+    "PagerDuty": PagerDuty,
+    "promosms": PromoSMS,
+    "pushbullet": Pushbullet,
+    "PushByTechulus": TechulusPush,
     "PushDeer": PushDeer,
+    "pushover": Pushover,
+    "pushy": Pushy,
+    "rocket.chat": RocketChat,
+    "serwersms": SerwerSMS,
+    "signal": Signal,
+    "slack": Slack,
+    "smtp": STMP,
+    "stackfield": Stackfield,
+    "teams": Teams,
+    "telegram": Telegram,
+    "webhook": Webhook,
+    "WeCom": WeCom,
 };
 
 export default NotificationFormList;

From 817c941489efabe35ac08ed6a6e9158e424617fe Mon Sep 17 00:00:00 2001
From: Super Manito <68613938+SuperManito@users.noreply.github.com>
Date: Sun, 12 Jun 2022 22:30:42 +0800
Subject: [PATCH 30/99] Add Bark Notification Parameters

---
 server/notification-providers/bark.js |  15 +++-
 src/components/notifications/Bark.vue | 106 ++++++++++++++++++++++++++
 src/languages/bg-BG.js                |   2 +
 src/languages/de-DE.js                |   2 +
 src/languages/en.js                   |   2 +
 src/languages/ko-KR.js                |   2 +
 src/languages/nl-NL.js                |   2 +
 src/languages/pl.js                   |   2 +
 src/languages/th-TH.js                |   2 +
 src/languages/tr-TR.js                |   2 +
 src/languages/vi-VN.js                |   2 +
 src/languages/zh-CN.js                |   2 +
 src/languages/zh-TW.js                |   2 +
 13 files changed, 140 insertions(+), 3 deletions(-)

diff --git a/server/notification-providers/bark.js b/server/notification-providers/bark.js
index 092511d8..a2c4966a 100644
--- a/server/notification-providers/bark.js
+++ b/server/notification-providers/bark.js
@@ -12,9 +12,7 @@ const { default: axios } = require("axios");
 
 // bark is an APN bridge that sends notifications to Apple devices.
 
-const barkNotificationGroup = "UptimeKuma";
 const barkNotificationAvatar = "https://github.com/louislam/uptime-kuma/raw/master/public/icon.png";
-const barkNotificationSound = "telegraph";
 const successMessage = "Successes!";
 
 class Bark extends NotificationProvider {
@@ -53,10 +51,21 @@ class Bark extends NotificationProvider {
     appendAdditionalParameters(postUrl) {
         // grouping all our notifications
         postUrl += "?group=" + barkNotificationGroup;
+        if (notification.barkGroup != null) {
+            postUrl += "&group=" + notification.barkGroup;
+        } else {
+            postUrl += "&group=" + "UptimeKuma";
+            // default group
+        }
         // set icon to uptime kuma icon, 11kb should be fine
         postUrl += "&icon=" + barkNotificationAvatar;
         // picked a sound, this should follow system's mute status when arrival
-        postUrl += "&sound=" + barkNotificationSound;
+        if (notification.barkSound != null) {
+            postUrl += "&sound=" + notification.barkSound;
+        } else {
+            postUrl += "&sound=" + "telegraph";
+            // default sound
+        }
         return postUrl;
     }
 
diff --git a/src/components/notifications/Bark.vue b/src/components/notifications/Bark.vue
index 014450de..70e4322d 100644
--- a/src/components/notifications/Bark.vue
+++ b/src/components/notifications/Bark.vue
@@ -12,4 +12,110 @@
             >{{ $t("here") }}</a>
         </i18n-t>
     </div>
+    <div class="mb-3">
+        <label for="Bark Group" class="form-label">{{ $t("Bark Group") }}</label>
+        <input id="Bark Group" v-model="$parent.notification.barkGroup" type="text" class="form-control" required>
+    </div>
+    <div class="mb-3">
+        <label for="Bark Sound" class="form-label">{{ $t("Bark Sound") }}</label>
+        
+        <select id="Bark Sound" v-model="$parent.notification.barkSound" class="form-select" required>
+            <option value="alarm">
+                alarm
+            </option>    
+            <option value="anticipate">
+                anticipate
+            </option>    
+            <option value="bell">
+                bell
+            </option>    
+            <option value="birdsong">
+                birdsong
+            </option>    
+            <option value="bloom">
+                bloom
+            </option>    
+            <option value="calypso">
+                calypso
+            </option>    
+            <option value="chime">
+                chime
+            </option>    
+            <option value="choo">
+                choo
+            </option>    
+            <option value="descent">
+                descent
+            </option>    
+            <option value="electronic">
+                electronic
+            </option>    
+            <option value="fanfare">
+                fanfare
+            </option>    
+            <option value="glass">
+                glass
+            </option>    
+            <option value="gotosleep">
+                gotosleep
+            </option>    
+            <option value="healthnotification">
+                healthnotification
+            </option>    
+            <option value="horn">
+                horn
+            </option>    
+            <option value="ladder">
+                ladder
+            </option>    
+            <option value="mailsent">
+                mailsent
+            </option>    
+            <option value="minuet">
+                minuet
+            </option>    
+            <option value="multiwayinvitation">
+                multiwayinvitation
+            </option>    
+            <option value="newmail">
+                newmail
+            </option>    
+            <option value="newsflash">
+                newsflash
+            </option>    
+            <option value="noir">
+                noir
+            </option>    
+            <option value="paymentsuccess">
+                paymentsuccess
+            </option>    
+            <option value="shake">
+                shake
+            </option>    
+            <option value="sherwoodforest">
+                sherwoodforest
+            </option>    
+            <option value="silence">
+                silence
+            </option>    
+            <option value="spell">
+                spell
+            </option>    
+            <option value="suspense">
+                suspense
+            </option>    
+            <option value="telegraph">
+                telegraph
+            </option>    
+            <option value="tiptoes">
+                tiptoes
+            </option>    
+            <option value="typewriters">
+                typewriters
+            </option>    
+            <option value="update">
+                update
+            </option>    
+        </select>
+    </div>
 </template>
diff --git a/src/languages/bg-BG.js b/src/languages/bg-BG.js
index 6297062a..23b1b726 100644
--- a/src/languages/bg-BG.js
+++ b/src/languages/bg-BG.js
@@ -389,6 +389,8 @@ export default {
     SignName: "Знак име",
     "Sms template must contain parameters: ": "SMS шаблонът трябва да съдържа следните параметри: ",
     "Bark Endpoint": "Bark крайна точка",
+    "Bark Group": "Bark група",
+    "Bark Sound": "Bark Звънене",
     WebHookUrl: "URL адрес на уеб кука",
     SecretKey: "Таен ключ",
     "For safety, must use secret key": "За сигурност, трябва да се използва таен ключ",
diff --git a/src/languages/de-DE.js b/src/languages/de-DE.js
index e679937c..aeba230f 100644
--- a/src/languages/de-DE.js
+++ b/src/languages/de-DE.js
@@ -389,6 +389,8 @@ export default {
     SignName: "Signaturname",
     "Sms template must contain parameters: ": "SMS Vorlage muss folgende Parameter enthalten: ",
     "Bark Endpoint": "Bark Endpunkt",
+    "Bark Group": "Bark Gruppe",
+    "Bark Sound": "Bark Klingelton",
     WebHookUrl: "Webhook URL",
     SecretKey: "Geheimer Schlüssel",
     "For safety, must use secret key": "Zur Sicherheit muss ein geheimer Schlüssel verwendet werden",
diff --git a/src/languages/en.js b/src/languages/en.js
index aa6737dd..4b8f782f 100644
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -406,6 +406,8 @@ export default {
     SignName: "SignName",
     "Sms template must contain parameters: ": "Sms template must contain parameters: ",
     "Bark Endpoint": "Bark Endpoint",
+    "Bark Group": "Bark Group",
+    "Bark Sound": "Bark Sound",
     WebHookUrl: "WebHookUrl",
     SecretKey: "SecretKey",
     "For safety, must use secret key": "For safety, must use secret key",
diff --git a/src/languages/ko-KR.js b/src/languages/ko-KR.js
index da034167..0ed7a6f2 100644
--- a/src/languages/ko-KR.js
+++ b/src/languages/ko-KR.js
@@ -406,6 +406,8 @@ export default {
     SignName: "SignName",
     "Sms template must contain parameters: ": "Sms 템플릿은 다음과 같은 파라미터가 포함되어야 해요:",
     "Bark Endpoint": "Bark Endpoint",
+    "Bark Group": "Bark Group",
+    "Bark Sound": "Bark Sound",
     WebHookUrl: "웹훅 URL",
     SecretKey: "Secret Key",
     "For safety, must use secret key": "안전을 위해 꼭 Secret Key를 사용하세요.",
diff --git a/src/languages/nl-NL.js b/src/languages/nl-NL.js
index 96424a5f..93bae56d 100644
--- a/src/languages/nl-NL.js
+++ b/src/languages/nl-NL.js
@@ -397,6 +397,8 @@ export default {
     SignName: "SignName",
     "Sms template must contain parameters: ": "Sms sjabloon moet de volgende parameters bevatten: ",
     "Bark Endpoint": "Bark Endpoint",
+    "Bark Group": "Bark Group",
+    "Bark Sound": "Bark Sound",
     WebHookUrl: "WebHookUrl",
     SecretKey: "SecretKey",
     "For safety, must use secret key": "Voor de veiligheid moet je de secret key gebruiken",
diff --git a/src/languages/pl.js b/src/languages/pl.js
index ab2480d3..57a5cbe6 100644
--- a/src/languages/pl.js
+++ b/src/languages/pl.js
@@ -396,6 +396,8 @@ export default {
     SignName: "Podpis",
     "Sms template must contain parameters: ": "Szablon sms musi posiadać parametry: ",
     "Bark Endpoint": "Punkt końcowy Bark",
+    "Bark Group": "grupa Bark",
+    "Bark Sound": "Dzwonek Bark",
     WebHookUrl: "WebHookUrl",
     SecretKey: "Tajny klucz",
     "For safety, must use secret key": "Ze względów bezpieczeństwa musisz użyć tajnego klucza",
diff --git a/src/languages/th-TH.js b/src/languages/th-TH.js
index 70138ff4..1773de7a 100644
--- a/src/languages/th-TH.js
+++ b/src/languages/th-TH.js
@@ -396,6 +396,8 @@ export default {
     SignName: "ป้ายชื่อ",
     "Sms template must contain parameters: ": "เทมเพลต SMS ต้องมีพารามิเตอร์ : ",
     "Bark Endpoint": "Bark Endpoint",
+    "Bark Group": "Bark Group",
+    "Bark Sound": "Bark Sound",
     WebHookUrl: "WebHookUrl",
     SecretKey: "SecretKey",
     "For safety, must use secret key": "เพื่อความปลอดภัย จำเป็นต้องตั้งค่ากุญแจการเข้าถึง",
diff --git a/src/languages/tr-TR.js b/src/languages/tr-TR.js
index 4904bdb7..bce1f0fd 100644
--- a/src/languages/tr-TR.js
+++ b/src/languages/tr-TR.js
@@ -397,6 +397,8 @@ export default {
     SignName: "SignName",
     "Sms template must contain parameters: ": "Sms şablonu parametreleri içermelidir:",
     "Bark Endpoint": "Bark Endpoint",
+    "Bark Group": "Bark Group",
+    "Bark Sound": "Bark Sound",
     WebHookUrl: "WebHookUrl",
     SecretKey: "SecretKey",
     "For safety, must use secret key": "Güvenlik için gizli anahtar kullanılmalıdır",
diff --git a/src/languages/vi-VN.js b/src/languages/vi-VN.js
index 9005c393..9d8da69a 100644
--- a/src/languages/vi-VN.js
+++ b/src/languages/vi-VN.js
@@ -396,6 +396,8 @@ export default {
     SignName: "SignName",
     "Sms template must contain parameters: ": "Sms template must contain parameters: ",
     "Bark Endpoint": "Bark Endpoint",
+    "Bark Group": "Bark Group",
+    "Bark Sound": "Bark Sound",
     WebHookUrl: "WebHookUrl",
     SecretKey: "SecretKey",
     "For safety, must use secret key": "Để an toàn, hãy dùng secret key",
diff --git a/src/languages/zh-CN.js b/src/languages/zh-CN.js
index 428d56bb..003fdd7a 100644
--- a/src/languages/zh-CN.js
+++ b/src/languages/zh-CN.js
@@ -402,6 +402,8 @@ export default {
     TemplateCode: "TemplateCode",
     SignName: "SignName",
     "Bark Endpoint": "Bark 接入点",
+    "Bark Group": "Bark 群组",
+    "Bark Sound": "Bark 铃声",
     "Device Token": "Apple Device Token",
     Platform: "平台",
     iOS: "iOS",
diff --git a/src/languages/zh-TW.js b/src/languages/zh-TW.js
index ff849adb..1118d100 100644
--- a/src/languages/zh-TW.js
+++ b/src/languages/zh-TW.js
@@ -396,6 +396,8 @@ export default {
     SignName: "SignName",
     "Sms template must contain parameters: ": "Sms 範本必須包含參數:",
     "Bark Endpoint": "Bark 端點",
+    "Bark Group": "Bark 群組",
+    "Bark Sound": "Bark 鈴聲",
     WebHookUrl: "WebHookUrl",
     SecretKey: "SecretKey",
     "For safety, must use secret key": "為了安全起見,必須使用秘密金鑰",

From a41023ca2a05f1015021d30352b9e4f752778320 Mon Sep 17 00:00:00 2001
From: Super Manito <68613938+SuperManito@users.noreply.github.com>
Date: Sun, 12 Jun 2022 22:41:24 +0800
Subject: [PATCH 31/99] Update

---
 server/notification-providers/bark.js | 1 -
 1 file changed, 1 deletion(-)

diff --git a/server/notification-providers/bark.js b/server/notification-providers/bark.js
index a2c4966a..b9113a74 100644
--- a/server/notification-providers/bark.js
+++ b/server/notification-providers/bark.js
@@ -50,7 +50,6 @@ class Bark extends NotificationProvider {
      */
     appendAdditionalParameters(postUrl) {
         // grouping all our notifications
-        postUrl += "?group=" + barkNotificationGroup;
         if (notification.barkGroup != null) {
             postUrl += "&group=" + notification.barkGroup;
         } else {

From 404923b7c82cdc633c637f364f0f2ccbb878d346 Mon Sep 17 00:00:00 2001
From: Super Manito <68613938+SuperManito@users.noreply.github.com>
Date: Sun, 12 Jun 2022 22:49:04 +0800
Subject: [PATCH 32/99] bugfix

---
 server/notification-providers/bark.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/server/notification-providers/bark.js b/server/notification-providers/bark.js
index b9113a74..f9215c8a 100644
--- a/server/notification-providers/bark.js
+++ b/server/notification-providers/bark.js
@@ -48,7 +48,7 @@ class Bark extends NotificationProvider {
      * @param {string} postUrl URL to append parameters to
      * @returns {string}
      */
-    appendAdditionalParameters(postUrl) {
+    appendAdditionalParameters(notification, postUrl) {
         // grouping all our notifications
         if (notification.barkGroup != null) {
             postUrl += "&group=" + notification.barkGroup;

From a23ab9d1de03e0825f6c1206c141fcec98a41a8d Mon Sep 17 00:00:00 2001
From: Super Manito <68613938+SuperManito@users.noreply.github.com>
Date: Sun, 12 Jun 2022 23:18:32 +0800
Subject: [PATCH 33/99] Update

---
 src/components/notifications/Bark.vue | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/components/notifications/Bark.vue b/src/components/notifications/Bark.vue
index 70e4322d..34d35db1 100644
--- a/src/components/notifications/Bark.vue
+++ b/src/components/notifications/Bark.vue
@@ -18,7 +18,6 @@
     </div>
     <div class="mb-3">
         <label for="Bark Sound" class="form-label">{{ $t("Bark Sound") }}</label>
-        
         <select id="Bark Sound" v-model="$parent.notification.barkSound" class="form-select" required>
             <option value="alarm">
                 alarm

From f442507cab4e4c9c3a8d03c83810d36183ed41f4 Mon Sep 17 00:00:00 2001
From: Super Manito <68613938+SuperManito@users.noreply.github.com>
Date: Mon, 13 Jun 2022 00:16:34 +0800
Subject: [PATCH 34/99] Update

---
 src/components/notifications/Bark.vue | 128 +++++++-------------------
 1 file changed, 32 insertions(+), 96 deletions(-)

diff --git a/src/components/notifications/Bark.vue b/src/components/notifications/Bark.vue
index 34d35db1..5ff01b1a 100644
--- a/src/components/notifications/Bark.vue
+++ b/src/components/notifications/Bark.vue
@@ -19,102 +19,38 @@
     <div class="mb-3">
         <label for="Bark Sound" class="form-label">{{ $t("Bark Sound") }}</label>
         <select id="Bark Sound" v-model="$parent.notification.barkSound" class="form-select" required>
-            <option value="alarm">
-                alarm
-            </option>    
-            <option value="anticipate">
-                anticipate
-            </option>    
-            <option value="bell">
-                bell
-            </option>    
-            <option value="birdsong">
-                birdsong
-            </option>    
-            <option value="bloom">
-                bloom
-            </option>    
-            <option value="calypso">
-                calypso
-            </option>    
-            <option value="chime">
-                chime
-            </option>    
-            <option value="choo">
-                choo
-            </option>    
-            <option value="descent">
-                descent
-            </option>    
-            <option value="electronic">
-                electronic
-            </option>    
-            <option value="fanfare">
-                fanfare
-            </option>    
-            <option value="glass">
-                glass
-            </option>    
-            <option value="gotosleep">
-                gotosleep
-            </option>    
-            <option value="healthnotification">
-                healthnotification
-            </option>    
-            <option value="horn">
-                horn
-            </option>    
-            <option value="ladder">
-                ladder
-            </option>    
-            <option value="mailsent">
-                mailsent
-            </option>    
-            <option value="minuet">
-                minuet
-            </option>    
-            <option value="multiwayinvitation">
-                multiwayinvitation
-            </option>    
-            <option value="newmail">
-                newmail
-            </option>    
-            <option value="newsflash">
-                newsflash
-            </option>    
-            <option value="noir">
-                noir
-            </option>    
-            <option value="paymentsuccess">
-                paymentsuccess
-            </option>    
-            <option value="shake">
-                shake
-            </option>    
-            <option value="sherwoodforest">
-                sherwoodforest
-            </option>    
-            <option value="silence">
-                silence
-            </option>    
-            <option value="spell">
-                spell
-            </option>    
-            <option value="suspense">
-                suspense
-            </option>    
-            <option value="telegraph">
-                telegraph
-            </option>    
-            <option value="tiptoes">
-                tiptoes
-            </option>    
-            <option value="typewriters">
-                typewriters
-            </option>    
-            <option value="update">
-                update
-            </option>    
+            <option value="alarm">alarm</option>
+            <option value="anticipate">anticipate</option>
+            <option value="bell">bell</option>
+            <option value="birdsong">birdsong</option>
+            <option value="bloom">bloom</option>
+            <option value="calypso">calypso</option>
+            <option value="chime">chime</option>
+            <option value="choo">choo</option>
+            <option value="descent">descent</option>
+            <option value="electronic">electronic</option>
+            <option value="fanfare">fanfare</option>
+            <option value="glass">glass</option>
+            <option value="gotosleep">gotosleep</option>
+            <option value="healthnotification">healthnotification</option>
+            <option value="horn">horn</option>
+            <option value="ladder">ladder</option>
+            <option value="mailsent">mailsent</option>
+            <option value="minuet">minuet</option>
+            <option value="multiwayinvitation">multiwayinvitation</option>
+            <option value="newmail">newmail</option>
+            <option value="newsflash">newsflash</option>
+            <option value="noir">noir</option>
+            <option value="paymentsuccess">paymentsuccess</option>
+            <option value="shake">shake</option>
+            <option value="sherwoodforest">sherwoodforest</option>
+            <option value="silence">silence</option>
+            <option value="spell">spell</option>
+            <option value="suspense">suspense</option>
+            <option value="telegraph">telegraph</option>
+            <option value="tiptoes">tiptoes</option>
+            <option value="typewriters">typewriters</option>
+            <option value="update">update</option>
         </select>
     </div>
 </template>

From 5f347b10ba88114a5c15757e4f86c96a3672e479 Mon Sep 17 00:00:00 2001
From: Super Manito <68613938+SuperManito@users.noreply.github.com>
Date: Mon, 13 Jun 2022 01:15:38 +0800
Subject: [PATCH 35/99] Update

---
 src/components/notifications/Bark.vue | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/src/components/notifications/Bark.vue b/src/components/notifications/Bark.vue
index 5ff01b1a..6cac73d3 100644
--- a/src/components/notifications/Bark.vue
+++ b/src/components/notifications/Bark.vue
@@ -2,9 +2,6 @@
     <div class="mb-3">
         <label for="Bark Endpoint" class="form-label">{{ $t("Bark Endpoint") }}<span style="color: red;"><sup>*</sup></span></label>
         <input id="Bark Endpoint" v-model="$parent.notification.barkEndpoint" type="text" class="form-control" required>
-        <div class="form-text">
-            <p><span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}</p>
-        </div>
         <i18n-t tag="div" keypath="wayToGetTeamsURL" class="form-text">
             <a
                 href="https://github.com/Finb/Bark"

From 774fe58ddca362a7d5c6fdfed8e39b0d8fdbc983 Mon Sep 17 00:00:00 2001
From: Super Manito <68613938+SuperManito@users.noreply.github.com>
Date: Mon, 13 Jun 2022 01:30:27 +0800
Subject: [PATCH 36/99] Update

---
 src/languages/bg-BG.js | 2 --
 src/languages/de-DE.js | 2 --
 src/languages/ko-KR.js | 2 --
 src/languages/nl-NL.js | 2 --
 src/languages/pl.js    | 2 --
 src/languages/th-TH.js | 2 --
 src/languages/tr-TR.js | 2 --
 src/languages/vi-VN.js | 2 --
 8 files changed, 16 deletions(-)

diff --git a/src/languages/bg-BG.js b/src/languages/bg-BG.js
index 23b1b726..6297062a 100644
--- a/src/languages/bg-BG.js
+++ b/src/languages/bg-BG.js
@@ -389,8 +389,6 @@ export default {
     SignName: "Знак име",
     "Sms template must contain parameters: ": "SMS шаблонът трябва да съдържа следните параметри: ",
     "Bark Endpoint": "Bark крайна точка",
-    "Bark Group": "Bark група",
-    "Bark Sound": "Bark Звънене",
     WebHookUrl: "URL адрес на уеб кука",
     SecretKey: "Таен ключ",
     "For safety, must use secret key": "За сигурност, трябва да се използва таен ключ",
diff --git a/src/languages/de-DE.js b/src/languages/de-DE.js
index aeba230f..e679937c 100644
--- a/src/languages/de-DE.js
+++ b/src/languages/de-DE.js
@@ -389,8 +389,6 @@ export default {
     SignName: "Signaturname",
     "Sms template must contain parameters: ": "SMS Vorlage muss folgende Parameter enthalten: ",
     "Bark Endpoint": "Bark Endpunkt",
-    "Bark Group": "Bark Gruppe",
-    "Bark Sound": "Bark Klingelton",
     WebHookUrl: "Webhook URL",
     SecretKey: "Geheimer Schlüssel",
     "For safety, must use secret key": "Zur Sicherheit muss ein geheimer Schlüssel verwendet werden",
diff --git a/src/languages/ko-KR.js b/src/languages/ko-KR.js
index 0ed7a6f2..da034167 100644
--- a/src/languages/ko-KR.js
+++ b/src/languages/ko-KR.js
@@ -406,8 +406,6 @@ export default {
     SignName: "SignName",
     "Sms template must contain parameters: ": "Sms 템플릿은 다음과 같은 파라미터가 포함되어야 해요:",
     "Bark Endpoint": "Bark Endpoint",
-    "Bark Group": "Bark Group",
-    "Bark Sound": "Bark Sound",
     WebHookUrl: "웹훅 URL",
     SecretKey: "Secret Key",
     "For safety, must use secret key": "안전을 위해 꼭 Secret Key를 사용하세요.",
diff --git a/src/languages/nl-NL.js b/src/languages/nl-NL.js
index 93bae56d..96424a5f 100644
--- a/src/languages/nl-NL.js
+++ b/src/languages/nl-NL.js
@@ -397,8 +397,6 @@ export default {
     SignName: "SignName",
     "Sms template must contain parameters: ": "Sms sjabloon moet de volgende parameters bevatten: ",
     "Bark Endpoint": "Bark Endpoint",
-    "Bark Group": "Bark Group",
-    "Bark Sound": "Bark Sound",
     WebHookUrl: "WebHookUrl",
     SecretKey: "SecretKey",
     "For safety, must use secret key": "Voor de veiligheid moet je de secret key gebruiken",
diff --git a/src/languages/pl.js b/src/languages/pl.js
index 57a5cbe6..ab2480d3 100644
--- a/src/languages/pl.js
+++ b/src/languages/pl.js
@@ -396,8 +396,6 @@ export default {
     SignName: "Podpis",
     "Sms template must contain parameters: ": "Szablon sms musi posiadać parametry: ",
     "Bark Endpoint": "Punkt końcowy Bark",
-    "Bark Group": "grupa Bark",
-    "Bark Sound": "Dzwonek Bark",
     WebHookUrl: "WebHookUrl",
     SecretKey: "Tajny klucz",
     "For safety, must use secret key": "Ze względów bezpieczeństwa musisz użyć tajnego klucza",
diff --git a/src/languages/th-TH.js b/src/languages/th-TH.js
index 1773de7a..70138ff4 100644
--- a/src/languages/th-TH.js
+++ b/src/languages/th-TH.js
@@ -396,8 +396,6 @@ export default {
     SignName: "ป้ายชื่อ",
     "Sms template must contain parameters: ": "เทมเพลต SMS ต้องมีพารามิเตอร์ : ",
     "Bark Endpoint": "Bark Endpoint",
-    "Bark Group": "Bark Group",
-    "Bark Sound": "Bark Sound",
     WebHookUrl: "WebHookUrl",
     SecretKey: "SecretKey",
     "For safety, must use secret key": "เพื่อความปลอดภัย จำเป็นต้องตั้งค่ากุญแจการเข้าถึง",
diff --git a/src/languages/tr-TR.js b/src/languages/tr-TR.js
index bce1f0fd..4904bdb7 100644
--- a/src/languages/tr-TR.js
+++ b/src/languages/tr-TR.js
@@ -397,8 +397,6 @@ export default {
     SignName: "SignName",
     "Sms template must contain parameters: ": "Sms şablonu parametreleri içermelidir:",
     "Bark Endpoint": "Bark Endpoint",
-    "Bark Group": "Bark Group",
-    "Bark Sound": "Bark Sound",
     WebHookUrl: "WebHookUrl",
     SecretKey: "SecretKey",
     "For safety, must use secret key": "Güvenlik için gizli anahtar kullanılmalıdır",
diff --git a/src/languages/vi-VN.js b/src/languages/vi-VN.js
index 9d8da69a..9005c393 100644
--- a/src/languages/vi-VN.js
+++ b/src/languages/vi-VN.js
@@ -396,8 +396,6 @@ export default {
     SignName: "SignName",
     "Sms template must contain parameters: ": "Sms template must contain parameters: ",
     "Bark Endpoint": "Bark Endpoint",
-    "Bark Group": "Bark Group",
-    "Bark Sound": "Bark Sound",
     WebHookUrl: "WebHookUrl",
     SecretKey: "SecretKey",
     "For safety, must use secret key": "Để an toàn, hãy dùng secret key",

From 252709ff494d6e16f5689d05a069e19dcb4a9aeb Mon Sep 17 00:00:00 2001
From: Super Manito <68613938+SuperManito@users.noreply.github.com>
Date: Mon, 13 Jun 2022 17:06:05 +0800
Subject: [PATCH 37/99] Update server/notification-providers/bark.js

Co-authored-by: Adam Stachowicz <saibamenppl@gmail.com>
---
 server/notification-providers/bark.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/server/notification-providers/bark.js b/server/notification-providers/bark.js
index f9215c8a..8d579724 100644
--- a/server/notification-providers/bark.js
+++ b/server/notification-providers/bark.js
@@ -53,8 +53,8 @@ class Bark extends NotificationProvider {
         if (notification.barkGroup != null) {
             postUrl += "&group=" + notification.barkGroup;
         } else {
-            postUrl += "&group=" + "UptimeKuma";
             // default group
+            postUrl += "&group=" + "UptimeKuma";
         }
         // set icon to uptime kuma icon, 11kb should be fine
         postUrl += "&icon=" + barkNotificationAvatar;

From 55a6e5af425a1be2df58680aeaed4d726614050a Mon Sep 17 00:00:00 2001
From: Super Manito <68613938+SuperManito@users.noreply.github.com>
Date: Mon, 13 Jun 2022 17:06:12 +0800
Subject: [PATCH 38/99] Update server/notification-providers/bark.js

Co-authored-by: Adam Stachowicz <saibamenppl@gmail.com>
---
 server/notification-providers/bark.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/server/notification-providers/bark.js b/server/notification-providers/bark.js
index 8d579724..21ee9b13 100644
--- a/server/notification-providers/bark.js
+++ b/server/notification-providers/bark.js
@@ -62,8 +62,8 @@ class Bark extends NotificationProvider {
         if (notification.barkSound != null) {
             postUrl += "&sound=" + notification.barkSound;
         } else {
-            postUrl += "&sound=" + "telegraph";
             // default sound
+            postUrl += "&sound=" + "telegraph";
         }
         return postUrl;
     }

From 1c4ddaeddf2c7d2f78ce881c73ae575a3cbfdb3a Mon Sep 17 00:00:00 2001
From: Super Manito <68613938+SuperManito@users.noreply.github.com>
Date: Mon, 13 Jun 2022 18:17:47 +0800
Subject: [PATCH 39/99] Update

---
 server/notification-providers/bark.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/server/notification-providers/bark.js b/server/notification-providers/bark.js
index 21ee9b13..6b22ae49 100644
--- a/server/notification-providers/bark.js
+++ b/server/notification-providers/bark.js
@@ -53,7 +53,7 @@ class Bark extends NotificationProvider {
         if (notification.barkGroup != null) {
             postUrl += "&group=" + notification.barkGroup;
         } else {
-            // default group
+            // default group name
             postUrl += "&group=" + "UptimeKuma";
         }
         // set icon to uptime kuma icon, 11kb should be fine
@@ -62,7 +62,7 @@ class Bark extends NotificationProvider {
         if (notification.barkSound != null) {
             postUrl += "&sound=" + notification.barkSound;
         } else {
-            // default sound
+            // default app sound
             postUrl += "&sound=" + "telegraph";
         }
         return postUrl;

From 54b9698a05e21649ec78f64ec5187b41d885f9d9 Mon Sep 17 00:00:00 2001
From: Super Manito <68613938+SuperManito@users.noreply.github.com>
Date: Mon, 13 Jun 2022 21:44:10 +0800
Subject: [PATCH 40/99] Update

---
 server/notification-providers/bark.js | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/server/notification-providers/bark.js b/server/notification-providers/bark.js
index 6b22ae49..3258e7c5 100644
--- a/server/notification-providers/bark.js
+++ b/server/notification-providers/bark.js
@@ -49,20 +49,20 @@ class Bark extends NotificationProvider {
      * @returns {string}
      */
     appendAdditionalParameters(notification, postUrl) {
+        // set icon to uptime kuma icon, 11kb should be fine
+        postUrl += "&icon=" + barkNotificationAvatar;
         // grouping all our notifications
         if (notification.barkGroup != null) {
             postUrl += "&group=" + notification.barkGroup;
         } else {
-            // default group name
+            // default name
             postUrl += "&group=" + "UptimeKuma";
         }
-        // set icon to uptime kuma icon, 11kb should be fine
-        postUrl += "&icon=" + barkNotificationAvatar;
         // picked a sound, this should follow system's mute status when arrival
         if (notification.barkSound != null) {
             postUrl += "&sound=" + notification.barkSound;
         } else {
-            // default app sound
+            // default sound
             postUrl += "&sound=" + "telegraph";
         }
         return postUrl;

From ac27e6e2af5dd8140b20c6f36a51a4af608b5a69 Mon Sep 17 00:00:00 2001
From: OidaTiftla <oidatiftla@oidatiftla.de>
Date: Wed, 15 Jun 2022 16:56:26 +0200
Subject: [PATCH 41/99] Rename feature to: Resend Notification if Down X times
 consequently

Co-authored-by: Louis Lam <louislam@users.noreply.github.com>
---
 db/patch-monitor-add-resend-interval.sql |  2 +-
 server/model/monitor.js                  | 20 +++++++++-----------
 src/languages/de-DE.js                   |  4 ++--
 src/languages/en.js                      |  4 ++--
 src/pages/EditMonitor.vue                |  4 ++--
 5 files changed, 16 insertions(+), 18 deletions(-)

diff --git a/db/patch-monitor-add-resend-interval.sql b/db/patch-monitor-add-resend-interval.sql
index c31dd7a2..8e28bf69 100644
--- a/db/patch-monitor-add-resend-interval.sql
+++ b/db/patch-monitor-add-resend-interval.sql
@@ -5,6 +5,6 @@ ALTER TABLE monitor
     ADD resend_interval INTEGER default 0 not null;
 
 ALTER TABLE heartbeat
-    ADD last_notified_time DATETIME default null;
+    ADD down_count INTEGER default 0 not null;
 
 COMMIT;
diff --git a/server/model/monitor.js b/server/model/monitor.js
index e1d02766..b3435f24 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -206,7 +206,7 @@ class Monitor extends BeanModel {
             bean.monitor_id = this.id;
             bean.time = R.isoDateTimeMillis(dayjs.utc());
             bean.status = DOWN;
-            bean.lastNotifiedTime = previousBeat?.lastNotifiedTime;
+            bean.downCount = previousBeat?.downCount || 0;
 
             if (this.isUpsideDown()) {
                 bean.status = flipStatus(bean.status);
@@ -523,8 +523,8 @@ class Monitor extends BeanModel {
                 log.debug("monitor", `[${this.name}] sendNotification`);
                 await Monitor.sendNotification(isFirstBeat, this, bean);
 
-                // Set last notified time to now
-                bean.lastNotifiedTime = R.isoDateTime(dayjs.utc());
+                // Reset down count
+                bean.downCount = 0;
 
                 // Clear Status Page Cache
                 log.debug("monitor", `[${this.name}] apicache clear`);
@@ -534,16 +534,14 @@ class Monitor extends BeanModel {
                 bean.important = false;
 
                 if (bean.status === DOWN && this.resendInterval > 0) {
-                    // divide by 1000 to convert from milliseconds to seconds and divide by 60 to convert from seconds to minutes
-                    let timeSinceLastNotified = (dayjs.utc().valueOf() - (bean.lastNotifiedTime == null ? 0 : dayjs.utc(bean.lastNotifiedTime).valueOf())) / 1000 / 60;
-                    if (timeSinceLastNotified >= this.resendInterval) {
+                    ++bean.downCount;
+                    if (bean.downCount >= this.resendInterval) {
                         // Send notification again, because we are still DOWN
-                        const currentTime = R.isoDateTime(dayjs.utc());
-                        log.debug("monitor", `[${this.name}] sendNotification again: lastNotifiedTime: ${bean.lastNotifiedTime} | current time: ${currentTime}`);
+                        log.debug("monitor", `[${this.name}] sendNotification again: Down Count: ${bean.downCount} | Resend Interval: ${this.resendInterval}`);
                         await Monitor.sendNotification(isFirstBeat, this, bean);
 
-                        // Set last notified time to now
-                        bean.lastNotifiedTime = currentTime;
+                        // Reset down count
+                        bean.downCount = 0;
                     }
                 }
             }
@@ -556,7 +554,7 @@ class Monitor extends BeanModel {
                 }
                 log.warn("monitor", `Monitor #${this.id} '${this.name}': Pending: ${bean.msg} | Max retries: ${this.maxretries} | Retry: ${retries} | Retry Interval: ${beatInterval} seconds | Type: ${this.type}`);
             } else {
-                log.warn("monitor", `Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Interval: ${beatInterval} seconds | Type: ${this.type}`);
+                log.warn("monitor", `Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Interval: ${beatInterval} seconds | Type: ${this.type} | Down Count: ${bean.downCount} | Resend Interval: ${this.resendInterval}`);
             }
 
             log.debug("monitor", `[${this.name}] Send to socket`);
diff --git a/src/languages/de-DE.js b/src/languages/de-DE.js
index f9f1e301..c4ef0b26 100644
--- a/src/languages/de-DE.js
+++ b/src/languages/de-DE.js
@@ -162,9 +162,9 @@ export default {
     Pink: "Pink",
     "Search...": "Suchen...",
     "Heartbeat Retry Interval": "Überprüfungsintervall",
-    "Notification resend interval if down": "Benachrichtigung erneut versenden wenn Inaktiv",
+    "Resend Notification if Down X times consequently": "Benachrichtigung erneut senden, wenn Inaktiv X mal hintereinander",
     retryCheckEverySecond: "Alle {0} Sekunden neu versuchen",
-    resendEveryMinute: "Erneut versenden alle {0} Minuten",
+    resendEveryXTimes: "Erneut versenden alle {0} mal",
     resendDisabled: "Erneut versenden deaktiviert",
     "Import Backup": "Backup importieren",
     "Export Backup": "Backup exportieren",
diff --git a/src/languages/en.js b/src/languages/en.js
index c3c3b740..49354a26 100644
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -2,7 +2,7 @@ export default {
     languageName: "English",
     checkEverySecond: "Check every {0} seconds",
     retryCheckEverySecond: "Retry every {0} seconds",
-    resendEveryMinute: "Resend every {0} minutes",
+    resendEveryXTimes: "Resend every {0} times",
     resendDisabled: "Resend disabled",
     retriesDescription: "Maximum retries before the service is marked as down and a notification is sent",
     ignoreTLSError: "Ignore TLS/SSL error for HTTPS websites",
@@ -74,7 +74,7 @@ export default {
     "Heartbeat Interval": "Heartbeat Interval",
     Retries: "Retries",
     "Heartbeat Retry Interval": "Heartbeat Retry Interval",
-    "Notification resend interval if down": "Notification resend interval if down",
+    "Resend Notification if Down X times consequently": "Resend Notification if Down X times consequently",
     Advanced: "Advanced",
     "Upside Down Mode": "Upside Down Mode",
     "Max. Redirects": "Max. Redirects",
diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index 55924952..87bf1996 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -204,8 +204,8 @@
 
                             <div class="my-3">
                                 <label for="resend-interval" class="form-label">
-                                    {{ $t("Notification resend interval if down") }}
-                                    <span v-if="monitor.resendInterval > 0">({{ $t("resendEveryMinute", [ monitor.resendInterval ]) }})</span>
+                                    {{ $t("Resend Notification if Down X times consequently") }}
+                                    <span v-if="monitor.resendInterval > 0">({{ $t("resendEveryXTimes", [ monitor.resendInterval ]) }})</span>
                                     <span v-else>({{ $t("resendDisabled") }})</span>
                                 </label>
                                 <input id="resend-interval" v-model="monitor.resendInterval" type="number" class="form-control" required min="0" step="1">

From 945288f0c01c45b771d81da711768d8987754baa Mon Sep 17 00:00:00 2001
From: Christopher Pickering <christopher.pickering@keemail.me>
Date: Wed, 15 Jun 2022 12:12:47 -0500
Subject: [PATCH 42/99] Added postgres monitor

---
 package-lock.json         | 215 ++++++++++++++++++++++++++++++++++++++
 package.json              |   1 +
 server/model/monitor.js   |  10 +-
 server/util-server.js     |  28 +++++
 src/pages/EditMonitor.vue |  22 ++--
 5 files changed, 268 insertions(+), 8 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 87342813..d213b883 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -52,6 +52,7 @@
                 "nodemailer": "~6.6.5",
                 "notp": "~2.0.3",
                 "password-hash": "~1.2.2",
+                "pg": "^8.7.3",
                 "postcss-rtlcss": "~3.4.1",
                 "postcss-scss": "~4.0.3",
                 "prismjs": "^1.27.0",
@@ -5335,6 +5336,14 @@
             "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
             "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
         },
+        "node_modules/buffer-writer": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
+            "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==",
+            "engines": {
+                "node": ">=4"
+            }
+        },
         "node_modules/builtins": {
             "version": "5.0.0",
             "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.0.tgz",
@@ -14437,6 +14446,11 @@
                 "node": ">=8"
             }
         },
+        "node_modules/packet-reader": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
+            "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
+        },
         "node_modules/pacote": {
             "version": "13.0.5",
             "resolved": "https://registry.npmjs.org/pacote/-/pacote-13.0.5.tgz",
@@ -14689,11 +14703,88 @@
             "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
             "optional": true
         },
+        "node_modules/pg": {
+            "version": "8.7.3",
+            "resolved": "https://registry.npmjs.org/pg/-/pg-8.7.3.tgz",
+            "integrity": "sha512-HPmH4GH4H3AOprDJOazoIcpI49XFsHCe8xlrjHkWiapdbHK+HLtbm/GQzXYAZwmPju/kzKhjaSfMACG+8cgJcw==",
+            "dependencies": {
+                "buffer-writer": "2.0.0",
+                "packet-reader": "1.0.0",
+                "pg-connection-string": "^2.5.0",
+                "pg-pool": "^3.5.1",
+                "pg-protocol": "^1.5.0",
+                "pg-types": "^2.1.0",
+                "pgpass": "1.x"
+            },
+            "engines": {
+                "node": ">= 8.0.0"
+            },
+            "peerDependencies": {
+                "pg-native": ">=2.0.0"
+            },
+            "peerDependenciesMeta": {
+                "pg-native": {
+                    "optional": true
+                }
+            }
+        },
         "node_modules/pg-connection-string": {
             "version": "2.5.0",
             "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz",
             "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ=="
         },
+        "node_modules/pg-int8": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
+            "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
+            "engines": {
+                "node": ">=4.0.0"
+            }
+        },
+        "node_modules/pg-pool": {
+            "version": "3.5.1",
+            "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.1.tgz",
+            "integrity": "sha512-6iCR0wVrro6OOHFsyavV+i6KYL4lVNyYAB9RD18w66xSzN+d8b66HiwuP30Gp1SH5O9T82fckkzsRjlrhD0ioQ==",
+            "peerDependencies": {
+                "pg": ">=8.0"
+            }
+        },
+        "node_modules/pg-protocol": {
+            "version": "1.5.0",
+            "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz",
+            "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ=="
+        },
+        "node_modules/pg-types": {
+            "version": "2.2.0",
+            "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
+            "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
+            "dependencies": {
+                "pg-int8": "1.0.1",
+                "postgres-array": "~2.0.0",
+                "postgres-bytea": "~1.0.0",
+                "postgres-date": "~1.0.4",
+                "postgres-interval": "^1.1.0"
+            },
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/pgpass": {
+            "version": "1.0.5",
+            "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
+            "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
+            "dependencies": {
+                "split2": "^4.1.0"
+            }
+        },
+        "node_modules/pgpass/node_modules/split2": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz",
+            "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==",
+            "engines": {
+                "node": ">= 10.x"
+            }
+        },
         "node_modules/picocolors": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@@ -14927,6 +15018,41 @@
             "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
             "dev": true
         },
+        "node_modules/postgres-array": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
+            "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/postgres-bytea": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
+            "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/postgres-date": {
+            "version": "1.0.7",
+            "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
+            "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/postgres-interval": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
+            "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
+            "dependencies": {
+                "xtend": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
         "node_modules/prelude-ls": {
             "version": "1.2.1",
             "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -22865,6 +22991,11 @@
             "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
             "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
         },
+        "buffer-writer": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
+            "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw=="
+        },
         "builtins": {
             "version": "5.0.0",
             "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.0.tgz",
@@ -29671,6 +29802,11 @@
                 "semver": "^6.2.0"
             }
         },
+        "packet-reader": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
+            "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
+        },
         "pacote": {
             "version": "13.0.5",
             "resolved": "https://registry.npmjs.org/pacote/-/pacote-13.0.5.tgz",
@@ -29867,11 +30003,67 @@
             "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
             "optional": true
         },
+        "pg": {
+            "version": "8.7.3",
+            "resolved": "https://registry.npmjs.org/pg/-/pg-8.7.3.tgz",
+            "integrity": "sha512-HPmH4GH4H3AOprDJOazoIcpI49XFsHCe8xlrjHkWiapdbHK+HLtbm/GQzXYAZwmPju/kzKhjaSfMACG+8cgJcw==",
+            "requires": {
+                "buffer-writer": "2.0.0",
+                "packet-reader": "1.0.0",
+                "pg-connection-string": "^2.5.0",
+                "pg-pool": "^3.5.1",
+                "pg-protocol": "^1.5.0",
+                "pg-types": "^2.1.0",
+                "pgpass": "1.x"
+            }
+        },
         "pg-connection-string": {
             "version": "2.5.0",
             "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz",
             "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ=="
         },
+        "pg-int8": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
+            "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="
+        },
+        "pg-pool": {
+            "version": "3.5.1",
+            "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.1.tgz",
+            "integrity": "sha512-6iCR0wVrro6OOHFsyavV+i6KYL4lVNyYAB9RD18w66xSzN+d8b66HiwuP30Gp1SH5O9T82fckkzsRjlrhD0ioQ=="
+        },
+        "pg-protocol": {
+            "version": "1.5.0",
+            "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz",
+            "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ=="
+        },
+        "pg-types": {
+            "version": "2.2.0",
+            "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
+            "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
+            "requires": {
+                "pg-int8": "1.0.1",
+                "postgres-array": "~2.0.0",
+                "postgres-bytea": "~1.0.0",
+                "postgres-date": "~1.0.4",
+                "postgres-interval": "^1.1.0"
+            }
+        },
+        "pgpass": {
+            "version": "1.0.5",
+            "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
+            "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
+            "requires": {
+                "split2": "^4.1.0"
+            },
+            "dependencies": {
+                "split2": {
+                    "version": "4.1.0",
+                    "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz",
+                    "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ=="
+                }
+            }
+        },
         "picocolors": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@@ -30035,6 +30227,29 @@
             "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
             "dev": true
         },
+        "postgres-array": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
+            "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="
+        },
+        "postgres-bytea": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
+            "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w=="
+        },
+        "postgres-date": {
+            "version": "1.0.7",
+            "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
+            "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="
+        },
+        "postgres-interval": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
+            "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
+            "requires": {
+                "xtend": "^4.0.0"
+            }
+        },
         "prelude-ls": {
             "version": "1.2.1",
             "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
diff --git a/package.json b/package.json
index efb3e7c1..781e07b1 100644
--- a/package.json
+++ b/package.json
@@ -104,6 +104,7 @@
         "nodemailer": "~6.6.5",
         "notp": "~2.0.3",
         "password-hash": "~1.2.2",
+        "pg": "^8.7.3",
         "postcss-rtlcss": "~3.4.1",
         "postcss-scss": "~4.0.3",
         "prismjs": "^1.27.0",
diff --git a/server/model/monitor.js b/server/model/monitor.js
index 3e026fb6..9f1bbea5 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 { log, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util");
-const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, mqttAsync, setSetting, httpNtlm } = require("../util-server");
+const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mqttAsync, setSetting, httpNtlm } = require("../util-server");
 const { R } = require("redbean-node");
 const { BeanModel } = require("redbean-node/dist/bean-model");
 const { Notification } = require("../notification");
@@ -477,6 +477,14 @@ class Monitor extends BeanModel {
 
                     await mssqlQuery(this.databaseConnectionString, this.databaseQuery);
 
+                    bean.msg = "";
+                    bean.status = UP;
+                    bean.ping = dayjs().valueOf() - startTime;
+                } else if (this.type === "postgres") {
+                    let startTime = dayjs().valueOf();
+
+                    await postgresQuery(this.databaseConnectionString, this.databaseQuery);
+
                     bean.msg = "";
                     bean.status = UP;
                     bean.ping = dayjs().valueOf() - startTime;
diff --git a/server/util-server.js b/server/util-server.js
index 87f9151d..34139383 100644
--- a/server/util-server.js
+++ b/server/util-server.js
@@ -11,6 +11,7 @@ const mqtt = require("mqtt");
 const chroma = require("chroma-js");
 const { badgeConstants } = require("./config");
 const mssql = require("mssql");
+const { Client } = require("pg");
 const { NtlmClient } = require("axios-ntlm");
 
 // From ping-lite
@@ -254,6 +255,33 @@ exports.mssqlQuery = function (connectionString, query) {
     });
 };
 
+/**
+ * Run a query on Postgres
+ * @param {string} connectionString The database connection string
+ * @param {string} query The query to validate the database with
+ * @returns {Promise<(string[]|Object[]|Object)>}
+ */
+exports.postgresQuery = function (connectionString, query) {
+
+    return new Promise((resolve, reject) => {
+
+        const client = new Client({ connectionString });
+
+        client.connect();
+
+        client.query(query)
+            .then(res => {
+                resolve(res);
+            })
+            .catch(err => {
+                reject(err);
+            })
+            .finally(() => {
+                client.end();
+            });
+    });
+};
+
 /**
  * Retrieve value of setting based on key
  * @param {string} key Key of setting to retrieve
diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index 83ffcfd8..cc100bac 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -45,6 +45,9 @@
                                         <option value="sqlserver">
                                             SQL Server
                                         </option>
+                                        <option value="postgres">
+                                            PostgreSQL
+                                        </option>
                                     </optgroup>
                                 </select>
                             </div>
@@ -168,15 +171,21 @@
                                 </div>
                             </template>
 
-                            <!-- SQL Server -->
-                            <template v-if="monitor.type === 'sqlserver'">
+                            <!-- SQL Server and PostgreSQL -->
+                            <template v-if="monitor.type === 'sqlserver' || monitor.type === 'postgres'">
                                 <div class="my-3">
-                                    <label for="sqlserverConnectionString" class="form-label">SQL Server {{ $t("Connection String") }}</label>
-                                    <input id="sqlserverConnectionString" v-model="monitor.databaseConnectionString" type="text" class="form-control">
+                                    <label for="sqlConnectionString" class="form-label">{{ $t("Connection String") }}</label>
+
+                                    <template v-if="monitor.type === 'sqlserver'">
+                                        <input id="sqlConnectionString" v-model="monitor.databaseConnectionString" type="text" class="form-control" placeholder="Server=<hostname>,<port>;Database=<your database>;User Id=<your user id>;Password=<your password>;Encrypt=<true/false>;TrustServerCertificate=<Yes/No>;Connection Timeout=<int>">
+                                    </template>
+                                    <template v-if="monitor.type === 'postgres'">
+                                        <input id="sqlConnectionString" v-model="monitor.databaseConnectionString" placeholder="postgres://username:password@host:port/database" type="text" class="form-control">
+                                    </template>
                                 </div>
                                 <div class="my-3">
-                                    <label for="sqlserverQuery" class="form-label">SQL Server {{ $t("Query") }}</label>
-                                    <textarea id="sqlserverQuery" v-model="monitor.databaseQuery" class="form-control" placeholder="Example: select getdate()"></textarea>
+                                    <label for="sqlQuery" class="form-label">{{ $t("Query") }}</label>
+                                    <textarea id="sqlQuery" v-model="monitor.databaseQuery" class="form-control" placeholder="Example: select getdate()"></textarea>
                                 </div>
                             </template>
 
@@ -584,7 +593,6 @@ export default {
                     method: "GET",
                     interval: 60,
                     retryInterval: this.interval,
-                    databaseConnectionString: "Server=<hostname>,<port>;Database=<your database>;User Id=<your user id>;Password=<your password>;Encrypt=<true/false>;TrustServerCertificate=<Yes/No>;Connection Timeout=<int>",
                     maxretries: 0,
                     notificationIDList: {},
                     ignoreTls: false,

From edcdedcaae098add2ed7b58d89e247e0207c1e8e Mon Sep 17 00:00:00 2001
From: Christopher Pickering <christopher.pickering@keemail.me>
Date: Wed, 15 Jun 2022 13:00:14 -0500
Subject: [PATCH 43/99] Added check for blank password.

---
 package-lock.json     |  1 +
 package.json          |  1 +
 server/util-server.js | 10 +++++++++-
 3 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/package-lock.json b/package-lock.json
index d213b883..b26f6008 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -53,6 +53,7 @@
                 "notp": "~2.0.3",
                 "password-hash": "~1.2.2",
                 "pg": "^8.7.3",
+                "pg-connection-string": "^2.5.0",
                 "postcss-rtlcss": "~3.4.1",
                 "postcss-scss": "~4.0.3",
                 "prismjs": "^1.27.0",
diff --git a/package.json b/package.json
index 781e07b1..17c73689 100644
--- a/package.json
+++ b/package.json
@@ -105,6 +105,7 @@
         "notp": "~2.0.3",
         "password-hash": "~1.2.2",
         "pg": "^8.7.3",
+        "pg-connection-string": "^2.5.0",
         "postcss-rtlcss": "~3.4.1",
         "postcss-scss": "~4.0.3",
         "prismjs": "^1.27.0",
diff --git a/server/util-server.js b/server/util-server.js
index 34139383..dc403cbd 100644
--- a/server/util-server.js
+++ b/server/util-server.js
@@ -12,6 +12,7 @@ const chroma = require("chroma-js");
 const { badgeConstants } = require("./config");
 const mssql = require("mssql");
 const { Client } = require("pg");
+const postgresConParse = require("pg-connection-string").parse;
 const { NtlmClient } = require("axios-ntlm");
 
 // From ping-lite
@@ -265,11 +266,18 @@ exports.postgresQuery = function (connectionString, query) {
 
     return new Promise((resolve, reject) => {
 
+        const config = postgresConParse(connectionString);
+
+        if (config.password === "") {
+            // See https://github.com/brianc/node-postgres/issues/1927
+            return reject(new Error("Password is undefined."));
+        }
+
         const client = new Client({ connectionString });
 
         client.connect();
 
-        client.query(query)
+        return client.query(query)
             .then(res => {
                 resolve(res);
             })

From 4b2a465c94e2671fcaf1b354257b3f0927b1d252 Mon Sep 17 00:00:00 2001
From: "sur.la.route" <christopher.pickering@keemail.me>
Date: Wed, 15 Jun 2022 20:14:06 -0500
Subject: [PATCH 44/99] Fix order of type and placeholder

Co-authored-by: Matthew Nickson <mnickson@sidingsmedia.com>
---
 src/pages/EditMonitor.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index cc100bac..f8784055 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -180,7 +180,7 @@
                                         <input id="sqlConnectionString" v-model="monitor.databaseConnectionString" type="text" class="form-control" placeholder="Server=<hostname>,<port>;Database=<your database>;User Id=<your user id>;Password=<your password>;Encrypt=<true/false>;TrustServerCertificate=<Yes/No>;Connection Timeout=<int>">
                                     </template>
                                     <template v-if="monitor.type === 'postgres'">
-                                        <input id="sqlConnectionString" v-model="monitor.databaseConnectionString" placeholder="postgres://username:password@host:port/database" type="text" class="form-control">
+                                        <input id="sqlConnectionString" v-model="monitor.databaseConnectionString" type="text" class="form-control" placeholder="postgres://username:password@host:port/database">
                                     </template>
                                 </div>
                                 <div class="my-3">

From e1f766756f067f043bfc6a43c3f24e2c9c19f76a Mon Sep 17 00:00:00 2001
From: "sur.la.route" <christopher.pickering@keemail.me>
Date: Wed, 15 Jun 2022 20:14:26 -0500
Subject: [PATCH 45/99] Removed blank line

Co-authored-by: Matthew Nickson <mnickson@sidingsmedia.com>
---
 server/util-server.js | 1 -
 1 file changed, 1 deletion(-)

diff --git a/server/util-server.js b/server/util-server.js
index dc403cbd..89777bce 100644
--- a/server/util-server.js
+++ b/server/util-server.js
@@ -263,7 +263,6 @@ exports.mssqlQuery = function (connectionString, query) {
  * @returns {Promise<(string[]|Object[]|Object)>}
  */
 exports.postgresQuery = function (connectionString, query) {
-
     return new Promise((resolve, reject) => {
 
         const config = postgresConParse(connectionString);

From 47e82ed83ad980b16374ecd1010ccf4b4f9ae9bd Mon Sep 17 00:00:00 2001
From: "sur.la.route" <christopher.pickering@keemail.me>
Date: Wed, 15 Jun 2022 20:14:36 -0500
Subject: [PATCH 46/99] Removed blank line

Co-authored-by: Matthew Nickson <mnickson@sidingsmedia.com>
---
 server/util-server.js | 1 -
 1 file changed, 1 deletion(-)

diff --git a/server/util-server.js b/server/util-server.js
index 89777bce..0ac13c68 100644
--- a/server/util-server.js
+++ b/server/util-server.js
@@ -264,7 +264,6 @@ exports.mssqlQuery = function (connectionString, query) {
  */
 exports.postgresQuery = function (connectionString, query) {
     return new Promise((resolve, reject) => {
-
         const config = postgresConParse(connectionString);
 
         if (config.password === "") {

From c4e2d67d17319cc438be7174aaea3f7f6f560077 Mon Sep 17 00:00:00 2001
From: Robert <treboryx@gmail.com>
Date: Fri, 17 Jun 2022 10:11:53 +0300
Subject: [PATCH 47/99] fix: hide mobile header when not logged in

---
 src/layouts/Layout.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/layouts/Layout.vue b/src/layouts/Layout.vue
index aea58bf1..57519f55 100644
--- a/src/layouts/Layout.vue
+++ b/src/layouts/Layout.vue
@@ -77,7 +77,7 @@
 
         <!-- Mobile Only -->
         <div v-if="$root.isMobile" style="width: 100%; height: 60px;" />
-        <nav v-if="$root.isMobile" class="bottom-nav">
+        <nav v-if="$root.isMobile && $root.loggedIn" class="bottom-nav">
             <router-link to="/dashboard" class="nav-link">
                 <div><font-awesome-icon icon="tachometer-alt" /></div>
                 {{ $t("Dashboard") }}

From 6f01a448ad295685d457c7394bd660c0a566ce44 Mon Sep 17 00:00:00 2001
From: theS1LV3R <s1lv3r@corax.team>
Date: Thu, 23 Jun 2022 23:08:04 +0200
Subject: [PATCH 48/99] feat: get client ip from x-forwarded-for header if
 available

Useful for use-cases where Uptime Kuma is running behind a reverse proxy
---
 server/server.js | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/server/server.js b/server/server.js
index 2d3f37ee..e74abaff 100644
--- a/server/server.js
+++ b/server/server.js
@@ -1677,7 +1677,8 @@ async function shutdownFunction(signal) {
 }
 
 function getClientIp(socket) {
-    return socket.client.conn.remoteAddress.replace(/^.*:/, "");
+    return socket.client.conn.request.headers["x-forwarded-for"]
+            || socket.client.conn.remoteAddress.replace(/^.*:/, "");
 }
 
 /** Final function called before application exits */

From f84ae82983744ab50e7e2b9ac5d4a277b40b60e8 Mon Sep 17 00:00:00 2001
From: rmt/src <144435+rmtsrc@users.noreply.github.com>
Date: Sat, 25 Jun 2022 17:22:53 +0100
Subject: [PATCH 49/99] feat: added Home Assistant notification integration

---
 .../notification-providers/home-assistant.js  | 38 ++++++++++++++++++
 server/notification.js                        |  2 +
 .../notifications/HomeAssistant.vue           | 40 +++++++++++++++++++
 src/components/notifications/index.js         |  2 +
 src/languages/bg-BG.js                        |  1 +
 src/languages/cs-CZ.js                        |  1 +
 src/languages/da-DK.js                        |  1 +
 src/languages/de-DE.js                        |  1 +
 src/languages/en.js                           |  1 +
 src/languages/es-ES.js                        |  1 +
 src/languages/et-EE.js                        |  1 +
 src/languages/eu.js                           |  1 +
 src/languages/fa.js                           |  1 +
 src/languages/fr-FR.js                        |  1 +
 src/languages/hr-HR.js                        |  1 +
 src/languages/hu.js                           |  1 +
 src/languages/id-ID.js                        |  1 +
 src/languages/it-IT.js                        |  1 +
 src/languages/ja.js                           |  1 +
 src/languages/ko-KR.js                        |  1 +
 src/languages/nb-NO.js                        |  1 +
 src/languages/nl-NL.js                        |  1 +
 src/languages/pl.js                           |  1 +
 src/languages/pt-BR.js                        |  1 +
 src/languages/ru-RU.js                        |  1 +
 src/languages/sl-SI.js                        |  1 +
 src/languages/sr-latn.js                      |  1 +
 src/languages/sr.js                           |  1 +
 src/languages/sv-SE.js                        |  1 +
 src/languages/th-TH.js                        |  1 +
 src/languages/tr-TR.js                        |  1 +
 src/languages/uk-UA.js                        |  1 +
 src/languages/vi-VN.js                        |  1 +
 src/languages/zh-CN.js                        |  1 +
 src/languages/zh-HK.js                        |  1 +
 src/languages/zh-TW.js                        |  1 +
 36 files changed, 114 insertions(+)
 create mode 100644 server/notification-providers/home-assistant.js
 create mode 100644 src/components/notifications/HomeAssistant.vue

diff --git a/server/notification-providers/home-assistant.js b/server/notification-providers/home-assistant.js
new file mode 100644
index 00000000..285989ee
--- /dev/null
+++ b/server/notification-providers/home-assistant.js
@@ -0,0 +1,38 @@
+const NotificationProvider = require("./notification-provider");
+const axios = require("axios");
+
+const defaultNotificationService = "notify";
+
+class HomeAssistant extends NotificationProvider {
+    name = "HomeAssistant";
+
+    async send(notification, message, monitor = null, heartbeat = null) {
+        const notificationService = notification?.notificationService || defaultNotificationService;
+
+        try {
+            await axios.post(
+                `${notification.homeAssistantUrl}/api/services/notify/${notificationService}`,
+                {
+                    title: "Uptime Kuma",
+                    message,
+                    ...(notificationService !== "persistent_notification" && { data: {
+                        name: monitor?.name,
+                        status: heartbeat?.status,
+                    } }),
+                },
+                {
+                    headers: {
+                        Authorization: `Bearer ${notification.longLivedAccessToken}`,
+                        "Content-Type": "application/json",
+                    },
+                }
+            );
+
+            return "Sent Successfully.";
+        } catch (error) {
+            this.throwGeneralAxiosError(error);
+        }
+    }
+}
+
+module.exports = HomeAssistant;
diff --git a/server/notification.js b/server/notification.js
index c457ed14..c86983fa 100644
--- a/server/notification.js
+++ b/server/notification.js
@@ -35,6 +35,7 @@ const Gorush = require("./notification-providers/gorush");
 const Alerta = require("./notification-providers/alerta");
 const OneBot = require("./notification-providers/onebot");
 const PushDeer = require("./notification-providers/pushdeer");
+const HomeAssistant = require("./notification-providers/home-assistant");
 
 class Notification {
 
@@ -82,6 +83,7 @@ class Notification {
             new Alerta(),
             new OneBot(),
             new PushDeer(),
+            new HomeAssistant(),
         ];
 
         for (let item of list) {
diff --git a/src/components/notifications/HomeAssistant.vue b/src/components/notifications/HomeAssistant.vue
new file mode 100644
index 00000000..67e370a1
--- /dev/null
+++ b/src/components/notifications/HomeAssistant.vue
@@ -0,0 +1,40 @@
+<template>
+    <div class="mb-3">
+        <label for="homeAssistantUrl" class="form-label">{{ $t("Home Assistant URL") }}<span style="color: red;"><sup>*</sup></span></label>
+        <input id="homeAssistantUrl" v-model="$parent.notification.homeAssistantUrl" type="url" class="form-control" required>
+    </div>
+
+    <div class="mb-3">
+        <label for="longLivedAccessToken" class="form-label">{{ $t("Long-Lived Access Token") }}<span style="color: red;"><sup>*</sup></span></label>
+        <input id="longLivedAccessToken" v-model="$parent.notification.longLivedAccessToken" type="text" class="form-control" required>
+
+        <div class="form-text">
+            <p>{{ $t("Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ") }}</p>
+        </div>
+    </div>
+
+    <div class="mb-3">
+        <label for="notificationService" class="form-label">{{ $t("Notification Service") }}</label>
+        <input id="notificationService" v-model="$parent.notification.notificationService" type="text" :placeholder="$t('default: notify all devices')" class="form-control">
+
+        <div class="form-text">
+            <p>{{ $t("A list of Notification Services can be found in Home Assistant under \"Developer Tools > Services\" search for \"notification\" to find your device/phone name.") }}</p>
+            <p>{{ $t("Automations can optionally be triggered in Home Assistant:") }}</p>
+            <p>
+                {{ $t("Trigger type:") }} <code>Event</code><br />
+                {{ $t("Event type:") }} <code>call_service</code><br />
+                {{ $t("Event data:") }}
+            </p>
+            <pre>domain: notify
+service: mobile_app_my_phone # change to your device name
+service_data:
+  title: Uptime Kuma
+  data:
+    status: 0 # 0=down 1=up
+    # name: Optional Uptime Kuma Monitor Name to filter by</pre>
+            <p>
+                {{ $t("Then choose an action, for example switch the scene to where an RGB light is red.") }}
+            </p>
+        </div>
+    </div>
+</template>
diff --git a/src/components/notifications/index.js b/src/components/notifications/index.js
index 18c316a5..cd0acabb 100644
--- a/src/components/notifications/index.js
+++ b/src/components/notifications/index.js
@@ -33,6 +33,7 @@ import Gorush from "./Gorush.vue";
 import Alerta from "./Alerta.vue";
 import OneBot from "./OneBot.vue";
 import PushDeer from "./PushDeer.vue";
+import HomeAssistant from "./HomeAssistant.vue";
 
 /**
  * Manage all notification form.
@@ -75,6 +76,7 @@ const NotificationFormList = {
     "alerta": Alerta,
     "OneBot": OneBot,
     "PushDeer": PushDeer,
+    "HomeAssistant": HomeAssistant,
 };
 
 export default NotificationFormList;
diff --git a/src/languages/bg-BG.js b/src/languages/bg-BG.js
index b2c185d9..1a6d351a 100644
--- a/src/languages/bg-BG.js
+++ b/src/languages/bg-BG.js
@@ -536,4 +536,5 @@ export default {
     Domain: "Домейн",
     Workstation: "Работна станция",
     disableCloudflaredNoAuthMsg: "Тъй като сте в режим \"No Auth mode\", парола не се изисква.",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/cs-CZ.js b/src/languages/cs-CZ.js
index 1ad47fd3..135c0d4c 100644
--- a/src/languages/cs-CZ.js
+++ b/src/languages/cs-CZ.js
@@ -364,4 +364,5 @@ export default {
     smtpDkimHashAlgo: "Hashovací algoritmus (volitelné)",
     smtpDkimheaderFieldNames: "Podepisovat tyto hlavičky (volitelné)",
     smtpDkimskipFields: "Nepodepisovat tyto hlavičky (volitelné)",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/da-DK.js b/src/languages/da-DK.js
index 83cd97ba..4b0f04d9 100644
--- a/src/languages/da-DK.js
+++ b/src/languages/da-DK.js
@@ -352,4 +352,5 @@ export default {
     serwersmsPhoneNumber: "Telefonnummer",
     serwersmsSenderName: "SMS Afsender Navn (registreret via kundeportal)",
     stackfield: "Stackfield",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/de-DE.js b/src/languages/de-DE.js
index 3df13b94..107adca4 100644
--- a/src/languages/de-DE.js
+++ b/src/languages/de-DE.js
@@ -455,4 +455,5 @@ export default {
     "Domain Names": "Domainnamen",
     signedInDisp: "Angemeldet als {0}",
     signedInDispDisabled: "Authentifizierung deaktiviert.",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/en.js b/src/languages/en.js
index 9aeedd9d..d80c3c8d 100644
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -536,4 +536,5 @@ export default {
     "Domain": "Domain",
     "Workstation": "Workstation",
     disableCloudflaredNoAuthMsg: "You are in No Auth mode, password is not require.",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/es-ES.js b/src/languages/es-ES.js
index 31538295..7b3af365 100644
--- a/src/languages/es-ES.js
+++ b/src/languages/es-ES.js
@@ -206,4 +206,5 @@ export default {
     records: "registros",
     "One record": "Un registro",
     steamApiKeyDescription: "Para monitorear un servidor de juegos de Steam, necesita una clave Steam Web-API. Puede registrar su clave API aquí: ",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/et-EE.js b/src/languages/et-EE.js
index f581a699..448577a7 100644
--- a/src/languages/et-EE.js
+++ b/src/languages/et-EE.js
@@ -206,4 +206,5 @@ export default {
     alertaApiKey: "API võti",
     alertaAlertState: "Häireseisund",
     alertaRecoverState: "Taasta algolek",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/eu.js b/src/languages/eu.js
index c99f1eb7..00a7a5f6 100644
--- a/src/languages/eu.js
+++ b/src/languages/eu.js
@@ -536,4 +536,5 @@ export default {
     Domain: "Domeinua",
     Workstation: "Lan gunea",
     disableCloudflaredNoAuthMsg: "Ez Auth moduan zaude, pasahitza ez da beharrezkoa.",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/fa.js b/src/languages/fa.js
index 52845192..fc7eec9e 100644
--- a/src/languages/fa.js
+++ b/src/languages/fa.js
@@ -205,4 +205,5 @@ export default {
     pushbullet: "Pushbullet",
     line: "Line Messenger",
     mattermost: "Mattermost",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/fr-FR.js b/src/languages/fr-FR.js
index 00abe8d3..d6e74635 100644
--- a/src/languages/fr-FR.js
+++ b/src/languages/fr-FR.js
@@ -309,4 +309,5 @@ export default {
     alertaApiKey: "Clé de l'API",
     alertaAlertState: "État de l'Alerte",
     alertaRecoverState: "État de récupération",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/hr-HR.js b/src/languages/hr-HR.js
index bebd2c56..5bdc6983 100644
--- a/src/languages/hr-HR.js
+++ b/src/languages/hr-HR.js
@@ -375,4 +375,5 @@ export default {
     alertaAlertState: "Stanje upozorenja",
     alertaRecoverState: "Stanje oporavka",
     deleteStatusPageMsg: "Sigurno želite obrisati ovu statusnu stranicu?",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/hu.js b/src/languages/hu.js
index e6118c9e..cb53ca8b 100644
--- a/src/languages/hu.js
+++ b/src/languages/hu.js
@@ -373,4 +373,5 @@ export default {
     alertaAlertState: "Figyelmeztetési állapot",
     alertaRecoverState: "Visszaállási állapot",
     deleteStatusPageMsg: "Biztos, hogy törölni akarja a státusz oldalt?",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/id-ID.js b/src/languages/id-ID.js
index 0a065308..1e8499fd 100644
--- a/src/languages/id-ID.js
+++ b/src/languages/id-ID.js
@@ -283,4 +283,5 @@ export default {
     promosmsPhoneNumber: "Nomor telepon (untuk penerima Polandia Anda dapat melewati kode area)",
     promosmsSMSSender: "Nama Pengirim SMS : Nama pra-registrasi atau salah satu bawaan: InfoSMS, Info SMS, MaxSMS, INFO, SMS",
     "Feishu WebHookUrl": "Feishu WebHookUrl",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/it-IT.js b/src/languages/it-IT.js
index f5276183..5b593c0b 100644
--- a/src/languages/it-IT.js
+++ b/src/languages/it-IT.js
@@ -364,4 +364,5 @@ export default {
     smtpDkimheaderFieldNames: "Campi Intestazione da firmare (opzionale)",
     smtpDkimskipFields: "Campi Intestazione da non firmare (opzionale)",
     GoogleChat: "Google Chat (solo per Google Workspace)",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/ja.js b/src/languages/ja.js
index 187ade0c..c53a6334 100644
--- a/src/languages/ja.js
+++ b/src/languages/ja.js
@@ -198,4 +198,5 @@ export default {
     pushbullet: "Pushbullet",
     line: "Line Messenger",
     mattermost: "Mattermost",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/ko-KR.js b/src/languages/ko-KR.js
index dbb02e65..50f6222a 100644
--- a/src/languages/ko-KR.js
+++ b/src/languages/ko-KR.js
@@ -528,4 +528,5 @@ export default {
     "Go back to the previous page.": "이전 페이지로 돌아가기",
     "Coming Soon": "Coming Soon",
     wayToGetClickSendSMSToken: "{0}에서 API 사용자 이름과 키를 얻을 수 있어요.",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/nb-NO.js b/src/languages/nb-NO.js
index 96f71d97..6d54ec82 100644
--- a/src/languages/nb-NO.js
+++ b/src/languages/nb-NO.js
@@ -282,4 +282,5 @@ export default {
     promosmsTypeSpeed: "SMS SPEED - Høyest prioritet i systemet.Veldig rask på pålitelig, men dyrt (omtrent det dobbeltet av SMS FULL pris).",
     promosmsPhoneNumber: "Telefonnummber (for polske mottakere. Du trenger ikke områdekode.)",
     promosmsSMSSender: "SMS Avsendernavn : Forhåndsregistert navn eller en av standardnavnene: InfoSMS, SMS Info, MaxSMS, INFO, SMS",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/nl-NL.js b/src/languages/nl-NL.js
index 3b6ebd83..f3f95267 100644
--- a/src/languages/nl-NL.js
+++ b/src/languages/nl-NL.js
@@ -462,4 +462,5 @@ export default {
     "Footer Text": "Footer Tekst",
     "Show Powered By": "Laat 'Mogeljik gemaakt door' zien",
     "Domain Names": "Domein Namen",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/pl.js b/src/languages/pl.js
index 3e962746..fe533bba 100644
--- a/src/languages/pl.js
+++ b/src/languages/pl.js
@@ -467,4 +467,5 @@ export default {
     "Domain Names": "Domeny",
     signedInDisp: "Zalogowany jako {0}",
     signedInDispDisabled: "Autoryzacja wyłączona.",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/pt-BR.js b/src/languages/pt-BR.js
index 7bc8d0fd..4c03c4a6 100644
--- a/src/languages/pt-BR.js
+++ b/src/languages/pt-BR.js
@@ -200,4 +200,5 @@ export default {
     pushbullet: "Pushbullet",
     line: "Line Messenger",
     mattermost: "Mattermost",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/ru-RU.js b/src/languages/ru-RU.js
index 0aaf0968..cb7b70ee 100644
--- a/src/languages/ru-RU.js
+++ b/src/languages/ru-RU.js
@@ -400,4 +400,5 @@ export default {
     proxyDescription: "Прокси должны быть привязаны к монитору, чтобы работать.",
     enableProxyDescription: "Этот прокси не будет влиять на запросы монитора, пока не будет активирован. Вы можете контролировать временное отключение прокси для всех мониторов через статус активации.",
     setAsDefaultProxyDescription: "Этот прокси будет по умолчанию включен для новых мониторов. Вы всё ещё можете отдельно отключать прокси в каждом мониторе.",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/sl-SI.js b/src/languages/sl-SI.js
index 3c8497f0..1ad70828 100644
--- a/src/languages/sl-SI.js
+++ b/src/languages/sl-SI.js
@@ -354,4 +354,5 @@ export default {
     serwersmsPhoneNumber: "Telefonska številka",
     serwersmsSenderName: "Ime SMS pošiljatelja (registrirani prek portala za stranke)",
     "stackfield": "Stackfield",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/sr-latn.js b/src/languages/sr-latn.js
index 32e074ee..1bc2bb63 100644
--- a/src/languages/sr-latn.js
+++ b/src/languages/sr-latn.js
@@ -201,4 +201,5 @@ export default {
     pushbullet: "Pushbullet",
     line: "Line Messenger",
     mattermost: "Mattermost",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/sr.js b/src/languages/sr.js
index bd8e4dd3..d811ec55 100644
--- a/src/languages/sr.js
+++ b/src/languages/sr.js
@@ -201,4 +201,5 @@ export default {
     pushbullet: "Pushbullet",
     line: "Line Messenger",
     mattermost: "Mattermost",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/sv-SE.js b/src/languages/sv-SE.js
index 1fc35be1..35c8d4b3 100644
--- a/src/languages/sv-SE.js
+++ b/src/languages/sv-SE.js
@@ -107,4 +107,5 @@ export default {
     "Repeat Password": "Upprepa Lösenord",
     respTime: "Svarstid (ms)",
     notAvailableShort: "Ej Tillg.",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/th-TH.js b/src/languages/th-TH.js
index a573206b..1893e442 100644
--- a/src/languages/th-TH.js
+++ b/src/languages/th-TH.js
@@ -518,4 +518,5 @@ export default {
     "Go back to the previous page.": "กลับไปที่หน้าก่อนหน้า",
     "Coming Soon": "เร็ว ๆ นี้",
     wayToGetClickSendSMSToken: "คุณสามารถรับ API Username และ API Key ได้จาก {0}",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/tr-TR.js b/src/languages/tr-TR.js
index 215b5381..7ae7e647 100644
--- a/src/languages/tr-TR.js
+++ b/src/languages/tr-TR.js
@@ -527,4 +527,5 @@ export default {
     "do nothing": "hiçbir şey yapma",
     "auto acknowledged": "otomatik onaylama",
     "auto resolve": "otomatik çözümleme",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/uk-UA.js b/src/languages/uk-UA.js
index 51802a39..fe3da3e3 100644
--- a/src/languages/uk-UA.js
+++ b/src/languages/uk-UA.js
@@ -392,4 +392,5 @@ export default {
     alertaAlertState: "Стан алерту",
     alertaRecoverState: "Стан відновлення",
     deleteStatusPageMsg: "Дійсно хочете видалити цю сторінку статусів?",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/vi-VN.js b/src/languages/vi-VN.js
index 505776f0..38d3d970 100644
--- a/src/languages/vi-VN.js
+++ b/src/languages/vi-VN.js
@@ -466,4 +466,5 @@ export default {
     "Domain Names": "Domain Names",
     signedInDisp: "Signed in as {0}",
     signedInDispDisabled: "Auth Disabled.",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/zh-CN.js b/src/languages/zh-CN.js
index 67077f38..8ae84b3e 100644
--- a/src/languages/zh-CN.js
+++ b/src/languages/zh-CN.js
@@ -540,4 +540,5 @@ export default {
     "ntfy Topic": "ntfy 主题",
     "Domain": "域名",
     "Workstation": "工作站",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/zh-HK.js b/src/languages/zh-HK.js
index a55f4fb6..ac9069be 100644
--- a/src/languages/zh-HK.js
+++ b/src/languages/zh-HK.js
@@ -380,4 +380,5 @@ export default {
     proxyDescription: "必須將代理伺服器指派給監測器才能運作。",
     enableProxyDescription: "此代理伺服器在啟用前不會在監測器上生效,您可以藉由控制啟用狀態來暫時對所有的監測器停用代理伺服器。",
     setAsDefaultProxyDescription: "預設情況下,新監測器將啟用此代理伺服器。您仍可分別停用各監測器的代理伺服器。",
+    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/zh-TW.js b/src/languages/zh-TW.js
index ace32e17..f3ceefd5 100644
--- a/src/languages/zh-TW.js
+++ b/src/languages/zh-TW.js
@@ -465,4 +465,5 @@ export default {
     "Footer Text": "頁尾文字",
     "Show Powered By": "顯示技術支援文字",
     "Domain Names": "網域名稱",
+    HomeAssistant: "Home Assistant",
 };

From 5dd197374dff744cb765175b0e4eb65271d4dbd1 Mon Sep 17 00:00:00 2001
From: rmt/src <144435+rmtsrc@users.noreply.github.com>
Date: Sun, 26 Jun 2022 10:56:46 +0100
Subject: [PATCH 50/99] fix: only add en translation

---
 src/languages/bg-BG.js   | 1 -
 src/languages/cs-CZ.js   | 1 -
 src/languages/da-DK.js   | 1 -
 src/languages/de-DE.js   | 1 -
 src/languages/es-ES.js   | 1 -
 src/languages/et-EE.js   | 1 -
 src/languages/eu.js      | 1 -
 src/languages/fa.js      | 1 -
 src/languages/fr-FR.js   | 1 -
 src/languages/hr-HR.js   | 1 -
 src/languages/hu.js      | 1 -
 src/languages/id-ID.js   | 1 -
 src/languages/it-IT.js   | 1 -
 src/languages/ja.js      | 1 -
 src/languages/ko-KR.js   | 1 -
 src/languages/nb-NO.js   | 1 -
 src/languages/nl-NL.js   | 1 -
 src/languages/pl.js      | 1 -
 src/languages/pt-BR.js   | 1 -
 src/languages/ru-RU.js   | 1 -
 src/languages/sl-SI.js   | 1 -
 src/languages/sr-latn.js | 1 -
 src/languages/sr.js      | 1 -
 src/languages/sv-SE.js   | 1 -
 src/languages/th-TH.js   | 1 -
 src/languages/tr-TR.js   | 1 -
 src/languages/uk-UA.js   | 1 -
 src/languages/vi-VN.js   | 1 -
 src/languages/zh-CN.js   | 1 -
 src/languages/zh-HK.js   | 1 -
 src/languages/zh-TW.js   | 1 -
 31 files changed, 31 deletions(-)

diff --git a/src/languages/bg-BG.js b/src/languages/bg-BG.js
index 1a6d351a..b2c185d9 100644
--- a/src/languages/bg-BG.js
+++ b/src/languages/bg-BG.js
@@ -536,5 +536,4 @@ export default {
     Domain: "Домейн",
     Workstation: "Работна станция",
     disableCloudflaredNoAuthMsg: "Тъй като сте в режим \"No Auth mode\", парола не се изисква.",
-    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/cs-CZ.js b/src/languages/cs-CZ.js
index 135c0d4c..1ad47fd3 100644
--- a/src/languages/cs-CZ.js
+++ b/src/languages/cs-CZ.js
@@ -364,5 +364,4 @@ export default {
     smtpDkimHashAlgo: "Hashovací algoritmus (volitelné)",
     smtpDkimheaderFieldNames: "Podepisovat tyto hlavičky (volitelné)",
     smtpDkimskipFields: "Nepodepisovat tyto hlavičky (volitelné)",
-    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/da-DK.js b/src/languages/da-DK.js
index 4b0f04d9..83cd97ba 100644
--- a/src/languages/da-DK.js
+++ b/src/languages/da-DK.js
@@ -352,5 +352,4 @@ export default {
     serwersmsPhoneNumber: "Telefonnummer",
     serwersmsSenderName: "SMS Afsender Navn (registreret via kundeportal)",
     stackfield: "Stackfield",
-    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/de-DE.js b/src/languages/de-DE.js
index 107adca4..3df13b94 100644
--- a/src/languages/de-DE.js
+++ b/src/languages/de-DE.js
@@ -455,5 +455,4 @@ export default {
     "Domain Names": "Domainnamen",
     signedInDisp: "Angemeldet als {0}",
     signedInDispDisabled: "Authentifizierung deaktiviert.",
-    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/es-ES.js b/src/languages/es-ES.js
index 7b3af365..31538295 100644
--- a/src/languages/es-ES.js
+++ b/src/languages/es-ES.js
@@ -206,5 +206,4 @@ export default {
     records: "registros",
     "One record": "Un registro",
     steamApiKeyDescription: "Para monitorear un servidor de juegos de Steam, necesita una clave Steam Web-API. Puede registrar su clave API aquí: ",
-    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/et-EE.js b/src/languages/et-EE.js
index 448577a7..f581a699 100644
--- a/src/languages/et-EE.js
+++ b/src/languages/et-EE.js
@@ -206,5 +206,4 @@ export default {
     alertaApiKey: "API võti",
     alertaAlertState: "Häireseisund",
     alertaRecoverState: "Taasta algolek",
-    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/eu.js b/src/languages/eu.js
index 00a7a5f6..c99f1eb7 100644
--- a/src/languages/eu.js
+++ b/src/languages/eu.js
@@ -536,5 +536,4 @@ export default {
     Domain: "Domeinua",
     Workstation: "Lan gunea",
     disableCloudflaredNoAuthMsg: "Ez Auth moduan zaude, pasahitza ez da beharrezkoa.",
-    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/fa.js b/src/languages/fa.js
index fc7eec9e..52845192 100644
--- a/src/languages/fa.js
+++ b/src/languages/fa.js
@@ -205,5 +205,4 @@ export default {
     pushbullet: "Pushbullet",
     line: "Line Messenger",
     mattermost: "Mattermost",
-    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/fr-FR.js b/src/languages/fr-FR.js
index d6e74635..00abe8d3 100644
--- a/src/languages/fr-FR.js
+++ b/src/languages/fr-FR.js
@@ -309,5 +309,4 @@ export default {
     alertaApiKey: "Clé de l'API",
     alertaAlertState: "État de l'Alerte",
     alertaRecoverState: "État de récupération",
-    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/hr-HR.js b/src/languages/hr-HR.js
index 5bdc6983..bebd2c56 100644
--- a/src/languages/hr-HR.js
+++ b/src/languages/hr-HR.js
@@ -375,5 +375,4 @@ export default {
     alertaAlertState: "Stanje upozorenja",
     alertaRecoverState: "Stanje oporavka",
     deleteStatusPageMsg: "Sigurno želite obrisati ovu statusnu stranicu?",
-    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/hu.js b/src/languages/hu.js
index cb53ca8b..e6118c9e 100644
--- a/src/languages/hu.js
+++ b/src/languages/hu.js
@@ -373,5 +373,4 @@ export default {
     alertaAlertState: "Figyelmeztetési állapot",
     alertaRecoverState: "Visszaállási állapot",
     deleteStatusPageMsg: "Biztos, hogy törölni akarja a státusz oldalt?",
-    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/id-ID.js b/src/languages/id-ID.js
index 1e8499fd..0a065308 100644
--- a/src/languages/id-ID.js
+++ b/src/languages/id-ID.js
@@ -283,5 +283,4 @@ export default {
     promosmsPhoneNumber: "Nomor telepon (untuk penerima Polandia Anda dapat melewati kode area)",
     promosmsSMSSender: "Nama Pengirim SMS : Nama pra-registrasi atau salah satu bawaan: InfoSMS, Info SMS, MaxSMS, INFO, SMS",
     "Feishu WebHookUrl": "Feishu WebHookUrl",
-    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/it-IT.js b/src/languages/it-IT.js
index 5b593c0b..f5276183 100644
--- a/src/languages/it-IT.js
+++ b/src/languages/it-IT.js
@@ -364,5 +364,4 @@ export default {
     smtpDkimheaderFieldNames: "Campi Intestazione da firmare (opzionale)",
     smtpDkimskipFields: "Campi Intestazione da non firmare (opzionale)",
     GoogleChat: "Google Chat (solo per Google Workspace)",
-    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/ja.js b/src/languages/ja.js
index c53a6334..187ade0c 100644
--- a/src/languages/ja.js
+++ b/src/languages/ja.js
@@ -198,5 +198,4 @@ export default {
     pushbullet: "Pushbullet",
     line: "Line Messenger",
     mattermost: "Mattermost",
-    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/ko-KR.js b/src/languages/ko-KR.js
index 50f6222a..dbb02e65 100644
--- a/src/languages/ko-KR.js
+++ b/src/languages/ko-KR.js
@@ -528,5 +528,4 @@ export default {
     "Go back to the previous page.": "이전 페이지로 돌아가기",
     "Coming Soon": "Coming Soon",
     wayToGetClickSendSMSToken: "{0}에서 API 사용자 이름과 키를 얻을 수 있어요.",
-    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/nb-NO.js b/src/languages/nb-NO.js
index 6d54ec82..96f71d97 100644
--- a/src/languages/nb-NO.js
+++ b/src/languages/nb-NO.js
@@ -282,5 +282,4 @@ export default {
     promosmsTypeSpeed: "SMS SPEED - Høyest prioritet i systemet.Veldig rask på pålitelig, men dyrt (omtrent det dobbeltet av SMS FULL pris).",
     promosmsPhoneNumber: "Telefonnummber (for polske mottakere. Du trenger ikke områdekode.)",
     promosmsSMSSender: "SMS Avsendernavn : Forhåndsregistert navn eller en av standardnavnene: InfoSMS, SMS Info, MaxSMS, INFO, SMS",
-    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/nl-NL.js b/src/languages/nl-NL.js
index f3f95267..3b6ebd83 100644
--- a/src/languages/nl-NL.js
+++ b/src/languages/nl-NL.js
@@ -462,5 +462,4 @@ export default {
     "Footer Text": "Footer Tekst",
     "Show Powered By": "Laat 'Mogeljik gemaakt door' zien",
     "Domain Names": "Domein Namen",
-    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/pl.js b/src/languages/pl.js
index fe533bba..3e962746 100644
--- a/src/languages/pl.js
+++ b/src/languages/pl.js
@@ -467,5 +467,4 @@ export default {
     "Domain Names": "Domeny",
     signedInDisp: "Zalogowany jako {0}",
     signedInDispDisabled: "Autoryzacja wyłączona.",
-    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/pt-BR.js b/src/languages/pt-BR.js
index 4c03c4a6..7bc8d0fd 100644
--- a/src/languages/pt-BR.js
+++ b/src/languages/pt-BR.js
@@ -200,5 +200,4 @@ export default {
     pushbullet: "Pushbullet",
     line: "Line Messenger",
     mattermost: "Mattermost",
-    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/ru-RU.js b/src/languages/ru-RU.js
index cb7b70ee..0aaf0968 100644
--- a/src/languages/ru-RU.js
+++ b/src/languages/ru-RU.js
@@ -400,5 +400,4 @@ export default {
     proxyDescription: "Прокси должны быть привязаны к монитору, чтобы работать.",
     enableProxyDescription: "Этот прокси не будет влиять на запросы монитора, пока не будет активирован. Вы можете контролировать временное отключение прокси для всех мониторов через статус активации.",
     setAsDefaultProxyDescription: "Этот прокси будет по умолчанию включен для новых мониторов. Вы всё ещё можете отдельно отключать прокси в каждом мониторе.",
-    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/sl-SI.js b/src/languages/sl-SI.js
index 1ad70828..3c8497f0 100644
--- a/src/languages/sl-SI.js
+++ b/src/languages/sl-SI.js
@@ -354,5 +354,4 @@ export default {
     serwersmsPhoneNumber: "Telefonska številka",
     serwersmsSenderName: "Ime SMS pošiljatelja (registrirani prek portala za stranke)",
     "stackfield": "Stackfield",
-    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/sr-latn.js b/src/languages/sr-latn.js
index 1bc2bb63..32e074ee 100644
--- a/src/languages/sr-latn.js
+++ b/src/languages/sr-latn.js
@@ -201,5 +201,4 @@ export default {
     pushbullet: "Pushbullet",
     line: "Line Messenger",
     mattermost: "Mattermost",
-    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/sr.js b/src/languages/sr.js
index d811ec55..bd8e4dd3 100644
--- a/src/languages/sr.js
+++ b/src/languages/sr.js
@@ -201,5 +201,4 @@ export default {
     pushbullet: "Pushbullet",
     line: "Line Messenger",
     mattermost: "Mattermost",
-    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/sv-SE.js b/src/languages/sv-SE.js
index 35c8d4b3..1fc35be1 100644
--- a/src/languages/sv-SE.js
+++ b/src/languages/sv-SE.js
@@ -107,5 +107,4 @@ export default {
     "Repeat Password": "Upprepa Lösenord",
     respTime: "Svarstid (ms)",
     notAvailableShort: "Ej Tillg.",
-    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/th-TH.js b/src/languages/th-TH.js
index 1893e442..a573206b 100644
--- a/src/languages/th-TH.js
+++ b/src/languages/th-TH.js
@@ -518,5 +518,4 @@ export default {
     "Go back to the previous page.": "กลับไปที่หน้าก่อนหน้า",
     "Coming Soon": "เร็ว ๆ นี้",
     wayToGetClickSendSMSToken: "คุณสามารถรับ API Username และ API Key ได้จาก {0}",
-    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/tr-TR.js b/src/languages/tr-TR.js
index 7ae7e647..215b5381 100644
--- a/src/languages/tr-TR.js
+++ b/src/languages/tr-TR.js
@@ -527,5 +527,4 @@ export default {
     "do nothing": "hiçbir şey yapma",
     "auto acknowledged": "otomatik onaylama",
     "auto resolve": "otomatik çözümleme",
-    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/uk-UA.js b/src/languages/uk-UA.js
index fe3da3e3..51802a39 100644
--- a/src/languages/uk-UA.js
+++ b/src/languages/uk-UA.js
@@ -392,5 +392,4 @@ export default {
     alertaAlertState: "Стан алерту",
     alertaRecoverState: "Стан відновлення",
     deleteStatusPageMsg: "Дійсно хочете видалити цю сторінку статусів?",
-    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/vi-VN.js b/src/languages/vi-VN.js
index 38d3d970..505776f0 100644
--- a/src/languages/vi-VN.js
+++ b/src/languages/vi-VN.js
@@ -466,5 +466,4 @@ export default {
     "Domain Names": "Domain Names",
     signedInDisp: "Signed in as {0}",
     signedInDispDisabled: "Auth Disabled.",
-    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/zh-CN.js b/src/languages/zh-CN.js
index 8ae84b3e..67077f38 100644
--- a/src/languages/zh-CN.js
+++ b/src/languages/zh-CN.js
@@ -540,5 +540,4 @@ export default {
     "ntfy Topic": "ntfy 主题",
     "Domain": "域名",
     "Workstation": "工作站",
-    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/zh-HK.js b/src/languages/zh-HK.js
index ac9069be..a55f4fb6 100644
--- a/src/languages/zh-HK.js
+++ b/src/languages/zh-HK.js
@@ -380,5 +380,4 @@ export default {
     proxyDescription: "必須將代理伺服器指派給監測器才能運作。",
     enableProxyDescription: "此代理伺服器在啟用前不會在監測器上生效,您可以藉由控制啟用狀態來暫時對所有的監測器停用代理伺服器。",
     setAsDefaultProxyDescription: "預設情況下,新監測器將啟用此代理伺服器。您仍可分別停用各監測器的代理伺服器。",
-    HomeAssistant: "Home Assistant",
 };
diff --git a/src/languages/zh-TW.js b/src/languages/zh-TW.js
index f3ceefd5..ace32e17 100644
--- a/src/languages/zh-TW.js
+++ b/src/languages/zh-TW.js
@@ -465,5 +465,4 @@ export default {
     "Footer Text": "頁尾文字",
     "Show Powered By": "顯示技術支援文字",
     "Domain Names": "網域名稱",
-    HomeAssistant: "Home Assistant",
 };

From e5e8db6c38aa76f4585f0c81b804435aeb427ca8 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Wed, 29 Jun 2022 22:17:47 +0800
Subject: [PATCH 51/99] Add cacheable-lookup

---
 package-lock.json            | 14 ++++++++++++++
 package.json                 |  1 +
 server/uptime-kuma-server.js |  5 +++++
 3 files changed, 20 insertions(+)

diff --git a/package-lock.json b/package-lock.json
index 9d64290b..2ac4a758 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -22,6 +22,7 @@
                 "bcryptjs": "~2.4.3",
                 "bootstrap": "5.1.3",
                 "bree": "~7.1.5",
+                "cacheable-lookup": "^6.0.4",
                 "chardet": "^1.3.0",
                 "chart.js": "~3.6.2",
                 "chartjs-adapter-dayjs": "~1.0.0",
@@ -4770,6 +4771,14 @@
                 "node": ">= 0.8"
             }
         },
+        "node_modules/cacheable-lookup": {
+            "version": "6.0.4",
+            "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-6.0.4.tgz",
+            "integrity": "sha512-mbcDEZCkv2CZF4G01kr8eBd/5agkt9oCqz75tJMSIsquvRZ2sL6Hi5zGVKi/0OSC9oO1GHfJ2AV0ZIOY9vye0A==",
+            "engines": {
+                "node": ">=10.6.0"
+            }
+        },
         "node_modules/call-bind": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
@@ -19882,6 +19891,11 @@
             "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
             "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw=="
         },
+        "cacheable-lookup": {
+            "version": "6.0.4",
+            "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-6.0.4.tgz",
+            "integrity": "sha512-mbcDEZCkv2CZF4G01kr8eBd/5agkt9oCqz75tJMSIsquvRZ2sL6Hi5zGVKi/0OSC9oO1GHfJ2AV0ZIOY9vye0A=="
+        },
         "call-bind": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
diff --git a/package.json b/package.json
index 08eed263..022e7d8d 100644
--- a/package.json
+++ b/package.json
@@ -74,6 +74,7 @@
         "bcryptjs": "~2.4.3",
         "bootstrap": "5.1.3",
         "bree": "~7.1.5",
+        "cacheable-lookup": "^6.0.4",
         "chardet": "^1.3.0",
         "chart.js": "~3.6.2",
         "chartjs-adapter-dayjs": "~1.0.0",
diff --git a/server/uptime-kuma-server.js b/server/uptime-kuma-server.js
index 605ba533..6f7be7ba 100644
--- a/server/uptime-kuma-server.js
+++ b/server/uptime-kuma-server.js
@@ -7,6 +7,7 @@ const { R } = require("redbean-node");
 const { log } = require("../src/util");
 const Database = require("./database");
 const util = require("util");
+const CacheableLookup = require("cacheable-lookup");
 
 /**
  * `module.exports` (alias: `server`) should be inside this class, in order to avoid circular dependency issue.
@@ -71,6 +72,10 @@ class UptimeKumaServer {
             }
         }
 
+        const cacheable = new CacheableLookup();
+        cacheable.install(http.globalAgent);
+        cacheable.install(https.globalAgent);
+
         this.io = new Server(this.httpServer);
     }
 

From 0a368ff55316f2cfbf2f2d3d5292937b42946564 Mon Sep 17 00:00:00 2001
From: Zoe <s1lv3r@corax.team>
Date: Mon, 4 Jul 2022 20:36:03 +0200
Subject: [PATCH 52/99] feat: add x-real-ip as a secondary header for client ip

Now allows both x-forwarded-for as well as x-real-ip to be used for the client ip, preferring x-forwarded-for
---
 server/server.js | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/server/server.js b/server/server.js
index e74abaff..476f8664 100644
--- a/server/server.js
+++ b/server/server.js
@@ -1677,7 +1677,8 @@ async function shutdownFunction(signal) {
 }
 
 function getClientIp(socket) {
-    return socket.client.conn.request.headers["x-forwarded-for"]
+    return socket.client.conn.request.headers["x-forwarded-for"] 
+            || socket.client.conn.request.headers["x-real-ip"]
             || socket.client.conn.remoteAddress.replace(/^.*:/, "");
 }
 

From c4125a8334d0fb4c7737a96d928f745fa9360b0a Mon Sep 17 00:00:00 2001
From: theS1LV3R <s1lv3r@corax.team>
Date: Mon, 4 Jul 2022 20:38:44 +0200
Subject: [PATCH 53/99] style: fix linter error

---
 server/server.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/server/server.js b/server/server.js
index 476f8664..d3ae075d 100644
--- a/server/server.js
+++ b/server/server.js
@@ -1677,7 +1677,7 @@ async function shutdownFunction(signal) {
 }
 
 function getClientIp(socket) {
-    return socket.client.conn.request.headers["x-forwarded-for"] 
+    return socket.client.conn.request.headers["x-forwarded-for"]
             || socket.client.conn.request.headers["x-real-ip"]
             || socket.client.conn.remoteAddress.replace(/^.*:/, "");
 }

From f33b6de157423ba9ce5778167e8e8a31e2d06b19 Mon Sep 17 00:00:00 2001
From: Chongyi Zheng <harry@harryzheng.com>
Date: Thu, 30 Jun 2022 20:49:48 -0400
Subject: [PATCH 54/99] Support X-Forwarded-Host header

---
 server/uptime-kuma-server.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/server/uptime-kuma-server.js b/server/uptime-kuma-server.js
index 605ba533..991c7ba2 100644
--- a/server/uptime-kuma-server.js
+++ b/server/uptime-kuma-server.js
@@ -49,6 +49,7 @@ class UptimeKumaServer {
 
         log.info("server", "Creating express and socket.io instance");
         this.app = express();
+        this.app.enable("trust proxy");
 
         if (sslKey && sslCert) {
             log.info("server", "Server Type: HTTPS");

From 6ce012c9a141213044522e5d1826c90bcd741adb Mon Sep 17 00:00:00 2001
From: Chongyi Zheng <harry@harryzheng.com>
Date: Tue, 12 Jul 2022 22:45:54 -0400
Subject: [PATCH 55/99] Add trust proxy checkbox in Settings page

---
 src/components/settings/ReverseProxy.vue | 47 ++++++++++++++++++++++++
 src/languages/en.js                      |  4 ++
 src/pages/Settings.vue                   |  4 ++
 3 files changed, 55 insertions(+)

diff --git a/src/components/settings/ReverseProxy.vue b/src/components/settings/ReverseProxy.vue
index 616b0996..85046cc2 100644
--- a/src/components/settings/ReverseProxy.vue
+++ b/src/components/settings/ReverseProxy.vue
@@ -91,6 +91,47 @@
             {{ $t("For example: nginx, Apache and Traefik.") }} <br />
             {{ $t("Please read") }} <a href="https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy" target="_blank">https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy</a>.
         </div>
+
+        <h4 class="my-4">{{ $t("HTTP Headers") }}</h4>
+        <div class="my-3">
+            <label class="form-label">
+                {{ $t("Trust Proxy") }}
+            </label>
+            <div class="form-check">
+                <input
+                    id="trustProxyYes"
+                    v-model="settings.trustProxy"
+                    class="form-check-input"
+                    type="radio"
+                    name="flexRadioDefault"
+                    :value="true"
+                    required
+                />
+                <label class="form-check-label" for="trustProxyYes">
+                    {{ $t("Trust 'X-Forwarded-*' headers") }}
+                </label>
+            </div>
+            <div class="form-check">
+                <input
+                    id="trustProxyNo"
+                    v-model="settings.trustProxy"
+                    class="form-check-input"
+                    type="radio"
+                    name="flexRadioDefault"
+                    :value="false"
+                    required
+                />
+                <label class="form-check-label" for="trustProxyYes">
+                    {{ $t("Don't trust 'X-Forwarded-*' headers") }}
+                </label>
+            </div>
+        </div>
+
+        <div>
+            <button class="btn btn-primary" type="submit" @click="saveSettings()">
+                {{ $t("Save") }}
+            </button>
+        </div>
     </div>
 </template>
 
@@ -113,6 +154,12 @@ export default {
         settings() {
             return this.$parent.$parent.$parent.settings;
         },
+        saveSettings() {
+            return this.$parent.$parent.$parent.saveSettings;
+        },
+        settingsLoaded() {
+            return this.$parent.$parent.$parent.settingsLoaded;
+        },
     },
     watch: {
 
diff --git a/src/languages/en.js b/src/languages/en.js
index 9aeedd9d..3c3cdff4 100644
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -453,6 +453,10 @@ export default {
     "Message:": "Message:",
     "Don't know how to get the token? Please read the guide:": "Don't know how to get the token? Please read the guide:",
     "The current connection may be lost if you are currently connecting via Cloudflare Tunnel. Are you sure want to stop it? Type your current password to confirm it.": "The current connection may be lost if you are currently connecting via Cloudflare Tunnel. Are you sure want to stop it? Type your current password to confirm it.",
+    "HTTP Headers": "HTTP Headers",
+    "Trust Proxy": "Trust Proxy",
+    "Trust 'X-Forwarded-*' headers": "Trust 'X-Forwarded-*' headers",
+    "Don't trust 'X-Forwarded-*' headers": "Don't trust 'X-Forwarded-*' headers",
     "Other Software": "Other Software",
     "For example: nginx, Apache and Traefik.": "For example: nginx, Apache and Traefik.",
     "Please read": "Please read",
diff --git a/src/pages/Settings.vue b/src/pages/Settings.vue
index 03eb09e9..e1013789 100644
--- a/src/pages/Settings.vue
+++ b/src/pages/Settings.vue
@@ -153,6 +153,10 @@ export default {
                     this.settings.tlsExpiryNotifyDays = [ 7, 14, 21 ];
                 }
 
+                if (this.settings.trustProxy === undefined) {
+                    this.settings.trustProxy = false;
+                }
+
                 this.settingsLoaded = true;
             });
         },

From 3fa5dfc87340ffec34d830a4e906f399845e33d6 Mon Sep 17 00:00:00 2001
From: Chongyi Zheng <harry@harryzheng.com>
Date: Tue, 12 Jul 2022 22:59:23 -0400
Subject: [PATCH 56/99] Use x-forwarded-host only when trustProxy is true

---
 server/server.js             | 14 +++++++++++---
 server/uptime-kuma-server.js |  2 --
 2 files changed, 11 insertions(+), 5 deletions(-)

diff --git a/server/server.js b/server/server.js
index 2d3f37ee..0c08da07 100644
--- a/server/server.js
+++ b/server/server.js
@@ -164,12 +164,20 @@ let needSetup = false;
 
     // Entry Page
     app.get("/", async (request, response) => {
-        log.debug("entry", `Request Domain: ${request.hostname}`);
+        let hostname = request.hostname;
+        if (await setting("trustProxy")) {
+            const proxy = request.headers["x-forwarded-host"];
+            if (proxy) {
+                hostname = proxy;
+            }
+        }
 
-        if (request.hostname in StatusPage.domainMappingList) {
+        log.debug("entry", `Request Domain: ${hostname}`);
+
+        if (hostname in StatusPage.domainMappingList) {
             log.debug("entry", "This is a status page domain");
 
-            let slug = StatusPage.domainMappingList[request.hostname];
+            let slug = StatusPage.domainMappingList[hostname];
             await StatusPage.handleStatusPageResponse(response, server.indexHTML, slug);
 
         } else if (exports.entryPage && exports.entryPage.startsWith("statusPage-")) {
diff --git a/server/uptime-kuma-server.js b/server/uptime-kuma-server.js
index 991c7ba2..34031b23 100644
--- a/server/uptime-kuma-server.js
+++ b/server/uptime-kuma-server.js
@@ -49,8 +49,6 @@ class UptimeKumaServer {
 
         log.info("server", "Creating express and socket.io instance");
         this.app = express();
-        this.app.enable("trust proxy");
-
         if (sslKey && sslCert) {
             log.info("server", "Server Type: HTTPS");
             this.httpServer = https.createServer({

From d44d984a46e70910cb823c7f15cc6cf3c627b835 Mon Sep 17 00:00:00 2001
From: SiderealArt <nelson22768384@gmail.com>
Date: Thu, 14 Jul 2022 22:25:39 +0800
Subject: [PATCH 57/99] update zh-TW

---
 src/languages/zh-TW.js | 71 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 71 insertions(+)

diff --git a/src/languages/zh-TW.js b/src/languages/zh-TW.js
index ace32e17..be87c540 100644
--- a/src/languages/zh-TW.js
+++ b/src/languages/zh-TW.js
@@ -13,6 +13,7 @@ export default {
     pauseDashboardHome: "暫停",
     deleteMonitorMsg: "您確定要刪除此監測器嗎?",
     deleteNotificationMsg: "您確定要為所有監測器刪除此通知嗎?",
+    dnsPortDescription: "DNS 伺服器連接埠。預設為 53。您可以隨時變更連接埠。",
     resolverserverDescription: "Cloudflare 為預設伺服器。您可以隨時更換解析伺服器。",
     rrtypeDescription: "選擇您想要監測的資源記錄類型",
     pauseMonitorMsg: "您確定要暫停嗎?",
@@ -332,6 +333,8 @@ export default {
     info: "資訊",
     warning: "警告",
     danger: "危險",
+    error: "錯誤",
+    critical: "嚴重",
     primary: "主要",
     light: "淺色",
     dark: "暗色",
@@ -372,6 +375,13 @@ export default {
     smtpDkimHashAlgo: "雜湊演算法 (選填)",
     smtpDkimheaderFieldNames: "要簽署的郵件標頭 (選填)",
     smtpDkimskipFields: "不簽署的郵件標頭 (選填)",
+    wayToGetPagerDutyKey: "您可以前往服務 -> 服務目錄 -> (選取服務) -> 整合 -> 新增整合以取得。您可以搜尋 \"Events API V2\"。詳細資訊 {0}",
+    "Integration Key": "整合金鑰",
+    "Integration URL": "整合網址",
+    "Auto resolve or acknowledged": "自動解決或認可",
+    "do nothing": "不進行任何操作",
+    "auto acknowledged": "自動認可",
+    "auto resolve": "自動解決",
     gorush: "Gorush",
     alerta: "Alerta",
     alertaApiEndpoint: "API 端點",
@@ -465,4 +475,65 @@ export default {
     "Footer Text": "頁尾文字",
     "Show Powered By": "顯示技術支援文字",
     "Domain Names": "網域名稱",
+    signedInDisp: "以 {0} 身分登入",
+    signedInDispDisabled: "驗證已停用。",
+    "Certificate Expiry Notification": "憑證到期通知",
+    "API Username": "API 使用者名稱",
+    "API Key": "API 金鑰",
+    "Recipient Number": "收件者號碼",
+    "From Name/Number": "來自名字/號碼",
+    "Leave blank to use a shared sender number.": "留空以使用共享寄件人號碼。",
+    "Octopush API Version": "Octopush API 版本",
+    "Legacy Octopush-DM": "舊版 Octopush-DM",
+    "endpoint": "端",
+    octopushAPIKey: "\"API key\" from HTTP API credentials in control panel",
+    octopushLogin: "\"Login\" from HTTP API credentials in control panel",
+    promosmsLogin: "API 登入名稱",
+    promosmsPassword: "API 密碼",
+    "pushoversounds pushover": "Pushover (預設)",
+    "pushoversounds bike": "車鈴",
+    "pushoversounds bugle": "號角",
+    "pushoversounds cashregister": "收銀機",
+    "pushoversounds classical": "古典",
+    "pushoversounds cosmic": "宇宙",
+    "pushoversounds falling": "下落",
+    "pushoversounds gamelan": "甘美朗",
+    "pushoversounds incoming": "來電",
+    "pushoversounds intermission": "中場休息",
+    "pushoversounds magic": "魔法",
+    "pushoversounds mechanical": "機械",
+    "pushoversounds pianobar": "Piano Bar",
+    "pushoversounds siren": "Siren",
+    "pushoversounds spacealarm": "Space Alarm",
+    "pushoversounds tugboat": "汽笛",
+    "pushoversounds alien": "外星鬧鐘 (長)",
+    "pushoversounds climb": "爬升 (長)",
+    "pushoversounds persistent": "持續 (長)",
+    "pushoversounds echo": "Pushover 回音 (長)",
+    "pushoversounds updown": "上下 (長)",
+    "pushoversounds vibrate": "僅震動",
+    "pushoversounds none": "無 (靜音)",
+    pushyAPIKey: "API 密鑰",
+    pushyToken: "裝置權杖",
+    "Show update if available": "顯示可用更新",
+    "Also check beta release": "檢查 Beta 版",
+    "Using a Reverse Proxy?": "正在使用反向代理?",
+    "Check how to config it for WebSocket": "查看如何為 WebSocket 設定",
+    "Steam Game Server": "Steam 遊戲伺服器",
+    "Most likely causes:": "可能原因:",
+    "The resource is no longer available.": "資源已不可用。",
+    "There might be a typing error in the address.": "網址可能有誤。",
+    "What you can try:": "您可以嘗試:",
+    "Retype the address.": "重新輸入網址。",
+    "Go back to the previous page.": "返回上一頁。",
+    "Coming Soon": "即將推出",
+    wayToGetClickSendSMSToken: "您可以從 {0} 取得 API 使用者名稱和金鑰。",
+    "Connection String": "連線字串",
+    "Query": "查詢",
+    settingsCertificateExpiry: "TLS 憑證到期",
+    certificationExpiryDescription: "TLS 將於 X 天後到期時觸發 HTTPS 監測器通知:",
+    "ntfy Topic": "ntfy 主題",
+    "Domain": "網域",
+    "Workstation": "工作站",
+    disableCloudflaredNoAuthMsg: "您處於無驗證模式。無須輸入密碼。",
 };

From 525607f49e6b9c7837c476471df488706c92092c Mon Sep 17 00:00:00 2001
From: Denis Stepanov <denis@stryi.com.ua>
Date: Mon, 18 Jul 2022 09:00:44 +0300
Subject: [PATCH 58/99] Update ukrainian language

---
 src/languages/uk-UA.js | 157 ++++++++++++++++++++++++++++++++++++++---
 1 file changed, 146 insertions(+), 11 deletions(-)

diff --git a/src/languages/uk-UA.js b/src/languages/uk-UA.js
index 51802a39..cc01793c 100644
--- a/src/languages/uk-UA.js
+++ b/src/languages/uk-UA.js
@@ -1,5 +1,5 @@
 export default {
-    languageName: "Український",
+    languageName: "Українська",
     checkEverySecond: "Перевірка кожні {0} секунд",
     retriesDescription: "Максимальна кількість спроб перед позначенням сервісу як недоступного та надсиланням повідомлення",
     ignoreTLSError: "Ігнорувати помилку TLS/SSL для сайтів HTTPS",
@@ -7,11 +7,11 @@ export default {
     maxRedirectDescription: "Максимальна кількість перенаправлень. Поставте 0, щоб вимкнути перенаправлення.",
     acceptedStatusCodesDescription: "Виберіть коди статусів для визначення доступності сервісу.",
     passwordNotMatchMsg: "Повторення паролю не збігається.",
-    notificationDescription: "Прив'яжіть повідомлення до моніторів.",
+    notificationDescription: "Прив'яжіть сповіщення до моніторів.",
     keywordDescription: "Пошук слова в чистому HTML або JSON-відповіді (чутливо до регістру)",
     pauseDashboardHome: "Пауза",
     deleteMonitorMsg: "Ви дійсно хочете видалити цей монітор?",
-    deleteNotificationMsg: "Ви дійсно хочете видалити це повідомлення для всіх моніторів?",
+    deleteNotificationMsg: "Ви дійсно хочете видалити це сповіщення для всіх моніторів?",
     resolverserverDescription: "Cloudflare є сервером за замовчуванням. Ви завжди можете змінити цей сервер.",
     rrtypeDescription: "Виберіть тип ресурсного запису, який ви хочете відстежувати",
     pauseMonitorMsg: "Ви дійсно хочете поставити на паузу?",
@@ -54,7 +54,7 @@ export default {
     Keyword: "Ключове слово",
     "Friendly Name": "Ім'я",
     URL: "URL",
-    Hostname: "Ім'я хоста",
+    Hostname: "Адреса хоста",
     Port: "Порт",
     "Heartbeat Interval": "Частота опитування",
     Retries: "Спроб",
@@ -63,7 +63,7 @@ export default {
     "Max. Redirects": "Макс. кількість перенаправлень",
     "Accepted Status Codes": "Припустимі коди статусу",
     Save: "Зберегти",
-    Notifications: "Повідомлення",
+    Notifications: "Сповіщення",
     "Not available, please setup.": "Доступних сповіщень немає, необхідно створити.",
     "Setup Notification": "Створити сповіщення",
     Light: "Світла",
@@ -100,7 +100,7 @@ export default {
     "No Monitors, please": "Моніторів немає, будь ласка",
     "No Monitors": "Монітори відсутні",
     "add one": "створіть новий",
-    "Notification Type": "Тип повідомлення",
+    "Notification Type": "Тип сповіщення",
     Email: "Пошта",
     Test: "Перевірка",
     "Certificate Info": "Інформація про сертифікат",
@@ -119,7 +119,7 @@ export default {
     Events: "Події",
     Heartbeats: "Опитування",
     "Auto Get": "Авто-отримання",
-    enableDefaultNotificationDescription: "Для кожного нового монітора це повідомлення буде включено за замовчуванням. Ви все ще можете відключити повідомлення в кожному моніторі окремо.",
+    enableDefaultNotificationDescription: "Для кожного нового монітора це сповіщення буде включено за замовчуванням. Ви все ще можете відключити сповіщення в кожному моніторі окремо.",
     "Default enabled": "Використовувати за промовчанням",
     "Also apply to existing monitors": "Застосувати до існуючих моніторів",
     Export: "Експорт",
@@ -170,7 +170,7 @@ export default {
     Purple: "Пурпурний",
     Pink: "Рожевий",
     "Search...": "Пошук...",
-    "Avg. Ping": "Середнє значення пінгу",
+    "Avg. Ping": "Середній пінг",
     "Avg. Response": "Середній час відповіді",
     "Entry Page": "Головна сторінка",
     statusPageNothing: "Тут порожньо. Додайте групу або монітор.",
@@ -210,7 +210,7 @@ export default {
     "Push URL": "URL пуша",
     needPushEvery: "До цієї URL необхідно звертатися кожні {0} секунд",
     pushOptionalParams: "Опціональні параметри: {0}",
-    defaultNotificationName: "Моє повідомлення {notification} ({number})",
+    defaultNotificationName: "Моє сповіщення {notification} ({number})",
     here: "тут",
     Required: "Потрібно",
     "Bot Token": "Токен бота",
@@ -257,7 +257,7 @@ export default {
     "User Key": "Ключ користувача",
     Device: "Пристрій",
     "Message Title": "Заголовок повідомлення",
-    "Notification Sound": "Звук повідомлення",
+    "Notification Sound": "Звук сповіщення",
     "More info on:": "Більше інформації: {0}",
     pushoverDesc1: "Екстренний пріоритет (2) має таймуут повтору за замовчуванням 30 секунд і закінчується через 1 годину.",
     pushoverDesc2: "Якщо ви бажаєте надсилати повідомлення різним пристроям, необхідно заповнити поле Пристрій.",
@@ -354,7 +354,7 @@ export default {
     "No consecutive dashes --": "Заборонено використовувати тире --",
     "HTTP Options": "HTTP Опції",
     Authentication: "Аутентифікація",
-    "HTTP Basic Auth": "HTTP Авторизація",
+    "HTTP Basic Auth": "Базова HTTP",
     PushByTechulus: "Push by Techulus",
     clicksendsms: "ClickSend SMS",
     GoogleChat: "Google Chat (тільки Google Workspace)",
@@ -392,4 +392,139 @@ export default {
     alertaAlertState: "Стан алерту",
     alertaRecoverState: "Стан відновлення",
     deleteStatusPageMsg: "Дійсно хочете видалити цю сторінку статусів?",
+    Proxies: "Проксі",
+    default: "За замовчуванням",
+    enabled: "Активно",
+    setAsDefault: "Встановити за замовчуванням",
+    deleteProxyMsg: "Ви впевнені, що хочете видалити цей проксі для всіх моніторів?",
+    proxyDescription: "Щоб функціонувати, монітору потрібно призначити проксі.",
+    enableProxyDescription: "Цей проксі не впливатиме на запити моніторингу, доки його не буде активовано. Ви можете контролювати тимчасове відключення проксі з усіх моніторів за статусом активації.",
+    setAsDefaultProxyDescription: "Цей проксі буде ввімкнено за умовчанням для нових моніторів. Ви все одно можете вимкнути проксі окремо для кожного монітора.",
+    Invalid: "Недійсний",
+    AccessKeyId: "AccessKey ID",
+    SecretAccessKey: "AccessKey Secret",
+    PhoneNumbers: "PhoneNumbers",
+    TemplateCode: "TemplateCode",
+    SignName: "SignName",
+    "Sms template must contain parameters: ": "Шаблон смс повинен містити параметри: ",
+    "Bark Endpoint": "Bark Endpoint",
+    WebHookUrl: "WebHookUrl",
+    SecretKey: "SecretKey",
+    "For safety, must use secret key": "Для безпеки необхідно використовувати секретний ключ",
+    "Device Token": "Токен пристрою",
+    Platform: "Платформа",
+    iOS: "iOS",
+    Android: "Android",
+    Huawei: "Huawei",
+    High: "Високий",
+    Retry: "Повтор",
+    Topic: "Тема",
+    "WeCom Bot Key": "WeCom Bot ключ",
+    "Setup Proxy": "Налаштувати проксі",
+    "Proxy Protocol": "Протокол проксі",
+    "Proxy Server": "Проксі-сервер",
+    "Proxy server has authentication": "Проксі-сервер має аутентифікацію",
+    User: "Користувач",
+    Installed: "Встановлено",
+    "Not installed": "Не встановлено",
+    Running: "Запущено",
+    "Not running": "Не запущено",
+    "Remove Token": "Видалити токен",
+    Start: "Запустити",
+    Stop: "Зупинити",
+    "Uptime Kuma": "Uptime Kuma",
+    Slug: "Slug",
+    "Accept characters:": "Прийняти символи:",
+    startOrEndWithOnly: "Починається або закінчується лише {0}",
+    "No consecutive dashes": "Немає послідовних тире",
+    "The slug is already taken. Please choose another slug.": "The slug is already taken. Please choose another slug.",
+    "No Proxy": "Без проксі",
+    "Page Not Found": "Сторінку не знайдено",
+    "Reverse Proxy": "Реверсивний проксі",
+    wayToGetCloudflaredURL: "(Завантажити Cloudflare з {0})",
+    cloudflareWebsite: "Веб-сайт Cloudflare",
+    "Message:": "Повідомлення:",
+    "Don't know how to get the token? Please read the guide:": "Не знаєте, як отримати токен? Прочитайте посібник:",
+    "The current connection may be lost if you are currently connecting via Cloudflare Tunnel. Are you sure want to stop it? Type your current password to confirm it.": "Поточне з’єднання може бути втрачено, якщо ви зараз під’єднуєтеся через Cloudflare Tunnel. Ви дійсно хочете зробити це? Для підтвердження введіть поточний пароль.",
+    "Other Software": "Інше програмне забезпечення",
+    "For example: nginx, Apache and Traefik.": "Наприклад: nginx, Apache and Traefik.",
+    "Please read": "Будь ласка, прочитайте",
+    "Subject:": "Тема:",
+    "Valid To:": "Дійсний до:",
+    "Days Remaining:": "Залишилось днів:",
+    "Issuer:": "Емітент:",
+    "Fingerprint:": "Відбиток:",
+    "No status pages": "Немає сторінок статусу",
+    "Domain Name Expiry Notification": "Сповіщення про закінчення терміну дії доменного імені",
+    Proxy: "Проксі",
+    "Date Created": "Дата створення",
+    onebotHttpAddress: "OneBot адреса HTTP",
+    onebotMessageType: "OneBot тип повідомлення",
+    onebotGroupMessage: "Група",
+    onebotPrivateMessage: "Приватне",
+    onebotUserOrGroupId: "Група/Користувач ID",
+    onebotSafetyTips: "Для безпеки необхідно встановити маркер доступу",
+    "PushDeer Key": "PushDeer ключ",
+    "Footer Text": "Текст нижнього колонтитула",
+    "Show Powered By": "Показувати платформу",
+    "Domain Names": "Доменні імена",
+    signedInDisp: "Ви ввійшли як {0}",
+    signedInDispDisabled: "Авторизація вимкнена.",
+    "Certificate Expiry Notification": "Сповіщення про закінчення терміну дії сертифіката",
+    "API Username": "Користувач API",
+    "API Key": "Ключ API",
+    "Recipient Number": "Номер одержувача",
+    "From Name/Number": "Від Ім'я/Номер",
+    "Leave blank to use a shared sender number.": "Залиште поле порожнім, щоб використовувати спільний номер відправника.",
+    "Octopush API Version": "Octopush API версія",
+    "Legacy Octopush-DM": "Legacy Octopush-DM",
+    "endpoint": "кінцева точка",
+    octopushAPIKey: "\"Ключ API\" з облікових даних HTTP API в панелі керування",
+    octopushLogin: "\"Ім'я користувача\" з облікових даних HTTP API на панелі керування",
+    promosmsLogin: "API Логін",
+    promosmsPassword: "API Пароль",
+    "pushoversounds pushover": "Pushover (по замовчуванню)",
+    "pushoversounds bike": "Bike",
+    "pushoversounds bugle": "Bugle",
+    "pushoversounds cashregister": "Cash Register",
+    "pushoversounds classical": "Classical",
+    "pushoversounds cosmic": "Cosmic",
+    "pushoversounds falling": "Falling",
+    "pushoversounds gamelan": "Gamelan",
+    "pushoversounds incoming": "Incoming",
+    "pushoversounds intermission": "Intermission",
+    "pushoversounds magic": "Magic",
+    "pushoversounds mechanical": "Mechanical",
+    "pushoversounds pianobar": "Piano Bar",
+    "pushoversounds siren": "Siren",
+    "pushoversounds spacealarm": "Space Alarm",
+    "pushoversounds tugboat": "Tug Boat",
+    "pushoversounds alien": "Alien Alarm (long)",
+    "pushoversounds climb": "Climb (long)",
+    "pushoversounds persistent": "Persistent (long)",
+    "pushoversounds echo": "Pushover Echo (long)",
+    "pushoversounds updown": "Up Down (long)",
+    "pushoversounds vibrate": "Vibrate Only",
+    "pushoversounds none": "None (silent)",
+    pushyAPIKey: "Секретний ключ API",
+    pushyToken: "Токен пристрою",
+    "Using a Reverse Proxy?": "Використовувати зворотній проксі?",
+    "Check how to config it for WebSocket": "Перевірте, як налаштувати його для WebSocket",
+    "Steam Game Server": "Ігровий сервер Steam",
+    "Most likely causes:": "Найімовірніші причини:",
+    "The resource is no longer available.": "Ресурс більше не доступний.",
+    "There might be a typing error in the address.": "Можливо, в адресі є помилка.",
+    "What you can try:": "Що ви можете спробувати:",
+    "Retype the address.": "Повторно введіть адресу.",
+    "Go back to the previous page.": "Повернутися на попередню сторінку.",
+    "Coming Soon": "Незабаром",
+    wayToGetClickSendSMSToken: "Ви можете отримати ім’я користувача API та ключ API з {0} .",
+    "Connection String": "Рядок підключення",
+    "Query": "Запит",
+    settingsCertificateExpiry: "Закінчення терміну дії сертифіката TLS",
+    certificationExpiryDescription: "Запуск сповіщення для HTTPS моніторів коли до закінчення терміну дії TLS сертифіката:",
+    "ntfy Topic": "ntfy Тема",
+    "Domain": "Домен",
+    "Workstation": "Робоча станція",
+    disableCloudflaredNoAuthMsg: "Ви перебуваєте в режимі без авторизації, пароль не потрібен.",
 };

From 25d711e6834d88c10d21099e53ab333f758b8fc0 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Mon, 18 Jul 2022 22:06:25 +0800
Subject: [PATCH 59/99] Fix jsdoc data type

---
 server/util-server.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/server/util-server.js b/server/util-server.js
index f6a0e396..deacb3a9 100644
--- a/server/util-server.js
+++ b/server/util-server.js
@@ -384,7 +384,7 @@ exports.checkCertificate = function (res) {
 
 /**
  * Check if the provided status code is within the accepted ranges
- * @param {string} status The status code to check
+ * @param {number} status The status code to check
  * @param {string[]} acceptedCodes An array of accepted status codes
  * @returns {boolean} True if status code within range, false otherwise
  * @throws {Error} Will throw an error if the provided status code is not a valid range string or code string

From 2073f0c28476bb46fb953ecefb9622273e8819d9 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Mon, 18 Jul 2022 22:33:35 +0800
Subject: [PATCH 60/99] Bind cacheable-lookup to custom http agent

---
 server/cacheable-dns-http-agent.js | 50 ++++++++++++++++++++++++++++++
 server/model/monitor.js            |  6 +++-
 server/uptime-kuma-server.js       |  6 ++--
 3 files changed, 57 insertions(+), 5 deletions(-)
 create mode 100644 server/cacheable-dns-http-agent.js

diff --git a/server/cacheable-dns-http-agent.js b/server/cacheable-dns-http-agent.js
new file mode 100644
index 00000000..0eb7a6ee
--- /dev/null
+++ b/server/cacheable-dns-http-agent.js
@@ -0,0 +1,50 @@
+const https = require("https");
+const http = require("http");
+const CacheableLookup = require("cacheable-lookup");
+
+class CacheableDnsHttpAgent {
+
+    static cacheable = new CacheableLookup();
+
+    static httpAgentList = {};
+    static httpsAgentList = {};
+
+    /**
+     * Register cacheable to global agents
+     */
+    static registerGlobalAgent() {
+        this.cacheable.install(http.globalAgent);
+        this.cacheable.install(https.globalAgent);
+    }
+
+    /**
+     * @var {https.AgentOptions} agentOptions
+     * @return {https.Agent}
+     */
+    static getHttpsAgent(agentOptions) {
+        let key = JSON.stringify(agentOptions);
+        if (!(key in this.httpsAgentList)) {
+            this.httpsAgentList[key] = new https.Agent(agentOptions);
+            this.cacheable.install(this.httpsAgentList[key]);
+        }
+        return this.httpsAgentList[key];
+    }
+
+    /**
+     * @var {http.AgentOptions} agentOptions
+     * @return {https.Agents}
+     */
+    static getHttpAgent(agentOptions) {
+        let key = JSON.stringify(agentOptions);
+        if (!(key in this.httpAgentList)) {
+            this.httpAgentList[key] = new http.Agent(agentOptions);
+            this.cacheable.install(this.httpAgentList[key]);
+        }
+        return this.httpAgentList[key];
+    }
+
+}
+
+module.exports = {
+    CacheableDnsHttpAgent,
+};
diff --git a/server/model/monitor.js b/server/model/monitor.js
index 5c97785f..bcb5a8b7 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -16,6 +16,7 @@ const { demoMode } = require("../config");
 const version = require("../../package.json").version;
 const apicache = require("../modules/apicache");
 const { UptimeKumaServer } = require("../uptime-kuma-server");
+const { CacheableDnsHttpAgent } = require("../cacheable-dns-http-agent");
 
 /**
  * status:
@@ -434,10 +435,13 @@ class Monitor extends BeanModel {
                             "Accept": "*/*",
                             "User-Agent": "Uptime-Kuma/" + version,
                         },
-                        httpsAgent: new https.Agent({
+                        httpsAgent: CacheableDnsHttpAgent.getHttpsAgent({
                             maxCachedSessions: 0,      // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
                             rejectUnauthorized: !this.getIgnoreTls(),
                         }),
+                        httpAgent: CacheableDnsHttpAgent.getHttpAgent({
+                            maxCachedSessions: 0,
+                        }),
                         maxRedirects: this.maxredirects,
                         validateStatus: (status) => {
                             return checkStatusCode(status, this.getAcceptedStatuscodes());
diff --git a/server/uptime-kuma-server.js b/server/uptime-kuma-server.js
index 6f7be7ba..e93e2b78 100644
--- a/server/uptime-kuma-server.js
+++ b/server/uptime-kuma-server.js
@@ -7,7 +7,7 @@ const { R } = require("redbean-node");
 const { log } = require("../src/util");
 const Database = require("./database");
 const util = require("util");
-const CacheableLookup = require("cacheable-lookup");
+const { CacheableDnsHttpAgent } = require("./cacheable-dns-http-agent");
 
 /**
  * `module.exports` (alias: `server`) should be inside this class, in order to avoid circular dependency issue.
@@ -72,9 +72,7 @@ class UptimeKumaServer {
             }
         }
 
-        const cacheable = new CacheableLookup();
-        cacheable.install(http.globalAgent);
-        cacheable.install(https.globalAgent);
+        CacheableDnsHttpAgent.registerGlobalAgent();
 
         this.io = new Server(this.httpServer);
     }

From 65d71e5db0653361c55e191313e491269f6b61af Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Mon, 18 Jul 2022 23:14:16 +0800
Subject: [PATCH 61/99] Fix mssqlQuery keep adding error listener, which causes
 memory leak. Also it is not necessary since the error catched in the promise
 .catch(..).

---
 server/util-server.js | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/server/util-server.js b/server/util-server.js
index deacb3a9..ec68c2f3 100644
--- a/server/util-server.js
+++ b/server/util-server.js
@@ -238,10 +238,6 @@ exports.dnsResolve = function (hostname, resolverServer, resolverPort, rrtype) {
  */
 exports.mssqlQuery = function (connectionString, query) {
     return new Promise((resolve, reject) => {
-        mssql.on("error", err => {
-            reject(err);
-        });
-
         mssql.connect(connectionString).then(pool => {
             return pool.request()
                 .query(query);

From 9cd060c6c3fba9912bced3643a268d84b24f42f1 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Mon, 18 Jul 2022 23:27:05 +0800
Subject: [PATCH 62/99] socks-proxy-agent 6.2.X is a breaking change, freeze to
 6.1.1.

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 8693135a..93c249f4 100644
--- a/package.json
+++ b/package.json
@@ -99,7 +99,7 @@
         "redbean-node": "0.1.4",
         "socket.io": "~4.4.1",
         "socket.io-client": "~4.4.1",
-        "socks-proxy-agent": "^6.1.1",
+        "socks-proxy-agent": "6.1.1",
         "tar": "^6.1.11",
         "tcp-ping": "~0.1.1",
         "thirty-two": "~1.0.2"

From 8f7b7e74c955eea2d4107d1658f94530101aede9 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Mon, 18 Jul 2022 23:28:19 +0800
Subject: [PATCH 63/99] socks-proxy-agent 6.2.X is a breaking change, freeze to
 6.1.1.

---
 package-lock.json | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index b0e55246..e64f9fa7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -47,7 +47,7 @@
                 "redbean-node": "0.1.4",
                 "socket.io": "~4.4.1",
                 "socket.io-client": "~4.4.1",
-                "socks-proxy-agent": "^6.1.1",
+                "socks-proxy-agent": "6.1.1",
                 "tar": "^6.1.11",
                 "tcp-ping": "~0.1.1",
                 "thirty-two": "~1.0.2"
@@ -14306,13 +14306,13 @@
             }
         },
         "node_modules/socks-proxy-agent": {
-            "version": "6.2.1",
-            "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz",
-            "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==",
+            "version": "6.1.1",
+            "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz",
+            "integrity": "sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==",
             "dependencies": {
                 "agent-base": "^6.0.2",
-                "debug": "^4.3.3",
-                "socks": "^2.6.2"
+                "debug": "^4.3.1",
+                "socks": "^2.6.1"
             },
             "engines": {
                 "node": ">= 10"
@@ -27095,13 +27095,13 @@
             }
         },
         "socks-proxy-agent": {
-            "version": "6.2.1",
-            "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz",
-            "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==",
+            "version": "6.1.1",
+            "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz",
+            "integrity": "sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==",
             "requires": {
                 "agent-base": "^6.0.2",
-                "debug": "^4.3.3",
-                "socks": "^2.6.2"
+                "debug": "^4.3.1",
+                "socks": "^2.6.1"
             }
         },
         "sortablejs": {

From 17ed051401f3889e8be041084301f05517a33526 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Mon, 18 Jul 2022 23:32:45 +0800
Subject: [PATCH 64/99] Add CacheableDnsHttpAgent.install()

---
 package.json                       | 2 +-
 server/cacheable-dns-http-agent.js | 4 ++++
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 93c249f4..7a18dbdf 100644
--- a/package.json
+++ b/package.json
@@ -68,7 +68,7 @@
         "badge-maker": "^3.3.1",
         "bcryptjs": "~2.4.3",
         "bree": "~7.1.5",
-        "cacheable-lookup": "^6.0.4",
+        "cacheable-lookup": "~6.0.4",
         "chardet": "^1.3.0",
         "check-password-strength": "^2.0.5",
         "cheerio": "^1.0.0-rc.10",
diff --git a/server/cacheable-dns-http-agent.js b/server/cacheable-dns-http-agent.js
index 0eb7a6ee..56e8430e 100644
--- a/server/cacheable-dns-http-agent.js
+++ b/server/cacheable-dns-http-agent.js
@@ -17,6 +17,10 @@ class CacheableDnsHttpAgent {
         this.cacheable.install(https.globalAgent);
     }
 
+    static install(agent) {
+        this.cacheable.install(agent);
+    }
+
     /**
      * @var {https.AgentOptions} agentOptions
      * @return {https.Agent}

From c412c66aebb04fab95a075dc13e9114edc045025 Mon Sep 17 00:00:00 2001
From: Mario Garrido <mariogarrido@7graus.com>
Date: Fri, 22 Jul 2022 06:29:41 +0100
Subject: [PATCH 65/99] Add pt-PT language file

---
 src/languages/pt-PT.js | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 src/languages/pt-PT.js

diff --git a/src/languages/pt-PT.js b/src/languages/pt-PT.js
new file mode 100644
index 00000000..e69de29b

From 77340cf0d296456d770c0244b4ec27f5c18348b7 Mon Sep 17 00:00:00 2001
From: Mario Garrido <mariogarrido@7graus.com>
Date: Fri, 22 Jul 2022 15:16:23 +0100
Subject: [PATCH 66/99] feat: adicionar pt-PT

---
 src/languages/pt-PT.js | 203 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 203 insertions(+)

diff --git a/src/languages/pt-PT.js b/src/languages/pt-PT.js
index e69de29b..ac452569 100644
--- a/src/languages/pt-PT.js
+++ b/src/languages/pt-PT.js
@@ -0,0 +1,203 @@
+export default {
+    languageName: "Português (Portugal)",
+    checkEverySecond: "Verificar a cada {0} segundos.",
+    retryCheckEverySecond: "Tentar novamente a cada {0} segundos.",
+    retriesDescription: "Máximo de tentativas antes que o serviço seja marcado como inativo e uma notificação seja enviada",
+    ignoreTLSError: "Ignorar erros TLS/SSL para sites HTTPS",
+    upsideDownModeDescription: "Inverte o status de cabeça para baixo. Se o serviço estiver acessível, ele está OFFLINE.",
+    maxRedirectDescription: "Número máximo de redirecionamentos a seguir. Define como 0 para desativar redirecionamentos.",
+    acceptedStatusCodesDescription: "Selecione os códigos de status que são considerados uma resposta bem-sucedida.",
+    passwordNotMatchMsg: "A senha repetida não corresponde.",
+    notificationDescription: "Atribuir uma notificação ao (s) monitor (es) para que funcione.",
+    keywordDescription: "Pesquisa a palavra-chave em html simples ou resposta JSON e diferencia maiúsculas de minúsculas",
+    pauseDashboardHome: "Pausa",
+    deleteMonitorMsg: "Tens a certeza de que queres excluir este monitor?",
+    deleteNotificationMsg: "Tems a certeza de que queres excluir esta notificação para todos os monitores?",
+    resolverserverDescription: "A Cloudflare é o servidor padrão, podes alterar o servidor resolvedor a qualquer momento.",
+    rrtypeDescription: "Seleciona o RR-Type que queres monitorar",
+    pauseMonitorMsg: "Tens a certeza que queres fazer uma pausa?",
+    enableDefaultNotificationDescription: "Para cada novo monitor, esta notificação estará activa por padrão. Podes também desativar a notificação separadamente para cada monitor.",
+    clearEventsMsg: "Tens a certeza que queres excluir todos os eventos deste monitor?",
+    clearHeartbeatsMsg: "Tens a certeza de que queres excluir todos os heartbeats deste monitor?",
+    confirmClearStatisticsMsg: "Tens a certeza que queres excluir TODAS as estatísticas?",
+    importHandleDescription: "Escolha 'Ignorar existente' se quiseres ignorar todos os monitores ou notificações com o mesmo nome. 'Substituir' excluirá todos os monitores e notificações existentes.",
+    confirmImportMsg: "Tens a certeza que queres importar o backup? Certifique-se que selecionou a opção de importação correta.",
+    twoFAVerifyLabel: "Insire o teu token para verificar se 2FA está a funcionar",
+    tokenValidSettingsMsg: "O token é válido! Agora podes salvar as configurações 2FA.",
+    confirmEnableTwoFAMsg: "Tens a certeza de que queres habilitar 2FA?",
+    confirmDisableTwoFAMsg: "Tens a certeza de que queres desativar 2FA?",
+    Settings: "Configurações",
+    Dashboard: "Dashboard",
+    "New Update": "Nova Atualização",
+    Language: "Linguagem",
+    Appearance: "Aparência",
+    Theme: "Tema",
+    General: "Geral",
+    Version: "Versão",
+    "Check Update On GitHub": "Verificar atualização no Github",
+    List: "Lista",
+    Add: "Adicionar",
+    "Add New Monitor": "Adicionar novo monitor",
+    "Quick Stats": "Estatísticas rápidas",
+    Up: "On",
+    Down: "Off",
+    Pending: "Pendente",
+    Unknown: "Desconhecido",
+    Pause: "Pausa",
+    Name: "Nome",
+    Status: "Status",
+    DateTime: "Data hora",
+    Message: "Mensagem",
+    "No important events": "Nenhum evento importante",
+    Resume: "Resumo",
+    Edit: "Editar",
+    Delete: "Apagar",
+    Current: "Atual",
+    Uptime: "Tempo de atividade",
+    "Cert Exp.": "Cert Exp.",
+    day: "dia | dias",
+    "-day": "-dia",
+    hour: "hora",
+    "-hour": "-hora",
+    Response: "Resposta",
+    Ping: "Ping",
+    "Monitor Type": "Tipo de Monitor",
+    Keyword: "Palavra-Chave",
+    "Friendly Name": "Nome Amigável",
+    URL: "URL",
+    Hostname: "Hostname",
+    Port: "Porta",
+    "Heartbeat Interval": "Intervalo de Heartbeat",
+    Retries: "Novas tentativas",
+    "Heartbeat Retry Interval": "Intervalo de repetição de Heartbeat",
+    Advanced: "Avançado",
+    "Upside Down Mode": "Modo de cabeça para baixo",
+    "Max. Redirects": "Redirecionamento Máx.",
+    "Accepted Status Codes": "Status Code Aceitáveis",
+    Save: "Guardar",
+    Notifications: "Notificações",
+    "Not available, please setup.": "Não disponível, por favor configure.",
+    "Setup Notification": "Configurar Notificação",
+    Light: "Claro",
+    Dark: "Escuro",
+    Auto: "Auto",
+    "Theme - Heartbeat Bar": "Tema - Barra de Heartbeat",
+    Normal: "Normal",
+    Bottom: "Inferior",
+    None: "Nenhum",
+    Timezone: "Fuso horário",
+    "Search Engine Visibility": "Visibilidade do mecanismo de pesquisa",
+    "Allow indexing": "Permitir Indexação",
+    "Discourage search engines from indexing site": "Desencorar que motores de busca indexem o site",
+    "Change Password": "Mudar senha",
+    "Current Password": "Senha atual",
+    "New Password": "Nova Senha",
+    "Repeat New Password": "Repetir Nova Senha",
+    "Update Password": "Atualizar Senha",
+    "Disable Auth": "Desativar Autenticação",
+    "Enable Auth": "Ativar Autenticação",
+    "disableauth.message1": "Tens a certeza que queres <strong>desativar a autenticação</strong>?",
+    "disableauth.message2": "Isso é para <strong>alguém que tem autenticação de terceiros</strong> em frente ao 'UpTime Kuma' como o Cloudflare Access.",
+    "Please use this option carefully!": "Por favor, utilize isso com cautela.",
+    Logout: "Deslogar",
+    Leave: "Sair",
+    "I understand, please disable": "Eu entendo, por favor desative.",
+    Confirm: "Confirmar",
+    Yes: "Sim",
+    No: "Não",
+    Username: "Usuário",
+    Password: "Senha",
+    "Remember me": "Lembre-me",
+    Login: "Autenticar",
+    "No Monitors, please": "Nenhum monitor, por favor",
+    "add one": "adicionar um",
+    "Notification Type": "Tipo de Notificação",
+    Email: "Email",
+    Test: "Testar",
+    "Certificate Info": "Info. do Certificado ",
+    "Resolver Server": "Resolver Servidor",
+    "Resource Record Type": "Tipo de registro de aplicação",
+    "Last Result": "Último resultado",
+    "Create your admin account": "Crie sua conta de admin",
+    "Repeat Password": "Repita a senha",
+    "Import Backup": "Importar Backup",
+    "Export Backup": "Exportar Backup",
+    Export: "Exportar",
+    Import: "Importar",
+    respTime: "Tempo de Resp. (ms)",
+    notAvailableShort: "N/A",
+    "Default enabled": "Padrão habilitado",
+    "Apply on all existing monitors": "Aplicar em todos os monitores existentes",
+    Create: "Criar",
+    "Clear Data": "Limpar Dados",
+    Events: "Eventos",
+    Heartbeats: "Heartbeats",
+    "Auto Get": "Obter Automático",
+    backupDescription: "Podes fazer backup de todos os monitores e todas as notificações em um arquivo JSON.",
+    backupDescription2: "OBS: Os dados do histórico e do evento não estão incluídos.",
+    backupDescription3: "Dados confidenciais, como tokens de notificação, estão incluídos no arquivo de exportação, mantenha-o com cuidado.",
+    alertNoFile: "Selecione um arquivo para importar.",
+    alertWrongFileType: "Selecione um arquivo JSON.",
+    "Clear all statistics": "Limpar todas as estatísticas",
+    "Skip existing": "Saltar existente",
+    Overwrite: "Sobrescrever",
+    Options: "Opções",
+    "Keep both": "Manter os dois",
+    "Verify Token": "Verificar Token",
+    "Setup 2FA": "Configurar 2FA",
+    "Enable 2FA": "Ativar 2FA",
+    "Disable 2FA": "Desativar 2FA",
+    "2FA Settings": "Configurações do 2FA ",
+    "Two Factor Authentication": "Autenticação e Dois Fatores",
+    Active: "Ativo",
+    Inactive: "Inativo",
+    Token: "Token",
+    "Show URI": "Mostrar URI",
+    Tags: "Tag",
+    "Add New below or Select...": "Adicionar Novo abaixo ou Selecionar ...",
+    "Tag with this name already exist.": "Já existe uma etiqueta com este nome.",
+    "Tag with this value already exist.": "Já existe uma etiqueta com este valor.",
+    color: "cor",
+    "value (optional)": "valor (opcional)",
+    Gray: "Cinza",
+    Red: "Vermelho",
+    Orange: "Laranja",
+    Green: "Verde",
+    Blue: "Azul",
+    Indigo: "Índigo",
+    Purple: "Roxo",
+    Pink: "Rosa",
+    "Search...": "Pesquisa...",
+    "Avg. Ping": "Ping Médio.",
+    "Avg. Response": "Resposta Média. ",
+    "Status Page": "Página de Status",
+    "Status Pages": "Página de Status",
+    "Entry Page": "Página de entrada",
+    statusPageNothing: "Nada aqui, por favor, adicione um grupo ou monitor.",
+    "No Services": "Nenhum Serviço",
+    "All Systems Operational": "Todos os Serviços Operacionais",
+    "Partially Degraded Service": "Serviço parcialmente degradados",
+    "Degraded Service": "Serviço Degradado",
+    "Add Group": "Adicionar Grupo",
+    "Add a monitor": "Adicionar um monitor",
+    "Edit Status Page": "Editar Página de Status",
+    "Go to Dashboard": "Ir para o dashboard",
+    telegram: "Telegram",
+    webhook: "Webhook",
+    smtp: "Email (SMTP)",
+    discord: "Discord",
+    teams: "Microsoft Teams",
+    signal: "Signal",
+    gotify: "Gotify",
+    slack: "Slack",
+    "rocket.chat": "Rocket.chat",
+    pushover: "Pushover",
+    pushy: "Pushy",
+    octopush: "Octopush",
+    promosms: "PromoSMS",
+    lunasea: "LunaSea",
+    apprise: "Apprise (Support 50+ Notification services)",
+    pushbullet: "Pushbullet",
+    line: "Line Messenger",
+    mattermost: "Mattermost",
+};

From 2ccf1fe41b5fa4aa4235f626a6ce6673bfad7076 Mon Sep 17 00:00:00 2001
From: Mario Garrido <mariogarrido@7graus.com>
Date: Fri, 22 Jul 2022 15:42:38 +0100
Subject: [PATCH 67/99] fix: small changes in semantics

---
 src/languages/pt-PT.js | 54 +++++++++++++++++++++---------------------
 1 file changed, 27 insertions(+), 27 deletions(-)

diff --git a/src/languages/pt-PT.js b/src/languages/pt-PT.js
index ac452569..21e68d26 100644
--- a/src/languages/pt-PT.js
+++ b/src/languages/pt-PT.js
@@ -6,24 +6,24 @@ export default {
     ignoreTLSError: "Ignorar erros TLS/SSL para sites HTTPS",
     upsideDownModeDescription: "Inverte o status de cabeça para baixo. Se o serviço estiver acessível, ele está OFFLINE.",
     maxRedirectDescription: "Número máximo de redirecionamentos a seguir. Define como 0 para desativar redirecionamentos.",
-    acceptedStatusCodesDescription: "Selecione os códigos de status que são considerados uma resposta bem-sucedida.",
+    acceptedStatusCodesDescription: "Seleciona os códigos de status que são considerados uma resposta bem-sucedida.",
     passwordNotMatchMsg: "A senha repetida não corresponde.",
     notificationDescription: "Atribuir uma notificação ao (s) monitor (es) para que funcione.",
-    keywordDescription: "Pesquisa a palavra-chave em html simples ou resposta JSON e diferencia maiúsculas de minúsculas",
+    keywordDescription: "Pesquisa a palavra-chave em HTML simples ou resposta JSON e diferencia maiúsculas de minúsculas",
     pauseDashboardHome: "Pausa",
     deleteMonitorMsg: "Tens a certeza de que queres excluir este monitor?",
-    deleteNotificationMsg: "Tems a certeza de que queres excluir esta notificação para todos os monitores?",
-    resolverserverDescription: "A Cloudflare é o servidor padrão, podes alterar o servidor resolvedor a qualquer momento.",
-    rrtypeDescription: "Seleciona o RR-Type que queres monitorar",
+    deleteNotificationMsg: "Tens a certeza de que queres excluir esta notificação para todos os monitores?",
+    resolverserverDescription: "A Cloudflare é o servidor padrão, podes alterar o servidor 'resolvedor' a qualquer momento.",
+    rrtypeDescription: "Seleciona o RR-Type que queres monitorizar",
     pauseMonitorMsg: "Tens a certeza que queres fazer uma pausa?",
-    enableDefaultNotificationDescription: "Para cada novo monitor, esta notificação estará activa por padrão. Podes também desativar a notificação separadamente para cada monitor.",
+    enableDefaultNotificationDescription: "Para cada monitor novo esta notificação vai estar activa por padrão. Podes também desativar a notificação separadamente para cada monitor.",
     clearEventsMsg: "Tens a certeza que queres excluir todos os eventos deste monitor?",
     clearHeartbeatsMsg: "Tens a certeza de que queres excluir todos os heartbeats deste monitor?",
     confirmClearStatisticsMsg: "Tens a certeza que queres excluir TODAS as estatísticas?",
-    importHandleDescription: "Escolha 'Ignorar existente' se quiseres ignorar todos os monitores ou notificações com o mesmo nome. 'Substituir' excluirá todos os monitores e notificações existentes.",
-    confirmImportMsg: "Tens a certeza que queres importar o backup? Certifique-se que selecionou a opção de importação correta.",
-    twoFAVerifyLabel: "Insire o teu token para verificar se 2FA está a funcionar",
-    tokenValidSettingsMsg: "O token é válido! Agora podes salvar as configurações 2FA.",
+    importHandleDescription: "Escolhe 'Ignorar existente' se quiseres ignorar todos os monitores ou notificações com o mesmo nome. 'Substituir' excluirá todos os monitores e notificações existentes.",
+    confirmImportMsg: "Tens a certeza que queres importar o backup? Certifica-te que selecionaste a opção de importação correta.",
+    twoFAVerifyLabel: "Insire o teu token para verificares se o 2FA está a funcionar",
+    tokenValidSettingsMsg: "O token é válido! Agora podes salvar as configurações do 2FA.",
     confirmEnableTwoFAMsg: "Tens a certeza de que queres habilitar 2FA?",
     confirmDisableTwoFAMsg: "Tens a certeza de que queres desativar 2FA?",
     Settings: "Configurações",
@@ -67,16 +67,16 @@ export default {
     URL: "URL",
     Hostname: "Hostname",
     Port: "Porta",
-    "Heartbeat Interval": "Intervalo de Heartbeat",
+    "Heartbeat Interval": "Intervalo de Heartbeats",
     Retries: "Novas tentativas",
-    "Heartbeat Retry Interval": "Intervalo de repetição de Heartbeat",
+    "Heartbeat Retry Interval": "Intervalo de repetição de Heartbeats",
     Advanced: "Avançado",
     "Upside Down Mode": "Modo de cabeça para baixo",
     "Max. Redirects": "Redirecionamento Máx.",
     "Accepted Status Codes": "Status Code Aceitáveis",
     Save: "Guardar",
     Notifications: "Notificações",
-    "Not available, please setup.": "Não disponível, por favor configure.",
+    "Not available, please setup.": "Não disponível, por favor configura.",
     "Setup Notification": "Configurar Notificação",
     Light: "Claro",
     Dark: "Escuro",
@@ -88,7 +88,7 @@ export default {
     Timezone: "Fuso horário",
     "Search Engine Visibility": "Visibilidade do mecanismo de pesquisa",
     "Allow indexing": "Permitir Indexação",
-    "Discourage search engines from indexing site": "Desencorar que motores de busca indexem o site",
+    "Discourage search engines from indexing site": "Desencorajar que motores de busca indexem o site",
     "Change Password": "Mudar senha",
     "Current Password": "Senha atual",
     "New Password": "Nova Senha",
@@ -98,16 +98,16 @@ export default {
     "Enable Auth": "Ativar Autenticação",
     "disableauth.message1": "Tens a certeza que queres <strong>desativar a autenticação</strong>?",
     "disableauth.message2": "Isso é para <strong>alguém que tem autenticação de terceiros</strong> em frente ao 'UpTime Kuma' como o Cloudflare Access.",
-    "Please use this option carefully!": "Por favor, utilize isso com cautela.",
-    Logout: "Deslogar",
+    "Please use this option carefully!": "Por favor, utiliza esta opção com cuidado.",
+    Logout: "Logout",
     Leave: "Sair",
-    "I understand, please disable": "Eu entendo, por favor desative.",
+    "I understand, please disable": "Eu entendo, por favor desativa.",
     Confirm: "Confirmar",
     Yes: "Sim",
     No: "Não",
-    Username: "Usuário",
+    Username: "Utilizador",
     Password: "Senha",
-    "Remember me": "Lembre-me",
+    "Remember me": "Lembra-me",
     Login: "Autenticar",
     "No Monitors, please": "Nenhum monitor, por favor",
     "add one": "adicionar um",
@@ -118,8 +118,8 @@ export default {
     "Resolver Server": "Resolver Servidor",
     "Resource Record Type": "Tipo de registro de aplicação",
     "Last Result": "Último resultado",
-    "Create your admin account": "Crie sua conta de admin",
-    "Repeat Password": "Repita a senha",
+    "Create your admin account": "Cria a tua conta de admin",
+    "Repeat Password": "Repete a senha",
     "Import Backup": "Importar Backup",
     "Export Backup": "Exportar Backup",
     Export: "Exportar",
@@ -133,11 +133,11 @@ export default {
     Events: "Eventos",
     Heartbeats: "Heartbeats",
     "Auto Get": "Obter Automático",
-    backupDescription: "Podes fazer backup de todos os monitores e todas as notificações em um arquivo JSON.",
+    backupDescription: "Podes fazer backup de todos os monitores e todas as notificações num arquivo JSON.",
     backupDescription2: "OBS: Os dados do histórico e do evento não estão incluídos.",
-    backupDescription3: "Dados confidenciais, como tokens de notificação, estão incluídos no arquivo de exportação, mantenha-o com cuidado.",
-    alertNoFile: "Selecione um arquivo para importar.",
-    alertWrongFileType: "Selecione um arquivo JSON.",
+    backupDescription3: "Dados confidenciais, como tokens de notificação, estão incluídos no arquivo de exportação, mantem-no com cuidado.",
+    alertNoFile: "Seleciona um arquivo para importar.",
+    alertWrongFileType: "Seleciona um arquivo JSON.",
     "Clear all statistics": "Limpar todas as estatísticas",
     "Skip existing": "Saltar existente",
     Overwrite: "Sobrescrever",
@@ -148,7 +148,7 @@ export default {
     "Enable 2FA": "Ativar 2FA",
     "Disable 2FA": "Desativar 2FA",
     "2FA Settings": "Configurações do 2FA ",
-    "Two Factor Authentication": "Autenticação e Dois Fatores",
+    "Two Factor Authentication": "Autenticação de Dois Fatores",
     Active: "Ativo",
     Inactive: "Inativo",
     Token: "Token",
@@ -173,7 +173,7 @@ export default {
     "Status Page": "Página de Status",
     "Status Pages": "Página de Status",
     "Entry Page": "Página de entrada",
-    statusPageNothing: "Nada aqui, por favor, adicione um grupo ou monitor.",
+    statusPageNothing: "Nada aqui, por favor, adiciona um grupo ou monitor.",
     "No Services": "Nenhum Serviço",
     "All Systems Operational": "Todos os Serviços Operacionais",
     "Partially Degraded Service": "Serviço parcialmente degradados",

From 239611a016a85712305100818d4c7b88a14664a9 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Fri, 22 Jul 2022 23:27:02 +0800
Subject: [PATCH 68/99] Do not set sendUrl if sendUrl is undefined

---
 package-lock.json                                    | 2 +-
 server/socket-handlers/status-page-socket-handler.js | 6 +++++-
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index e64f9fa7..d76f5a94 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,7 +16,7 @@
                 "badge-maker": "^3.3.1",
                 "bcryptjs": "~2.4.3",
                 "bree": "~7.1.5",
-                "cacheable-lookup": "^6.0.4",
+                "cacheable-lookup": "~6.0.4",
                 "chardet": "^1.3.0",
                 "check-password-strength": "^2.0.5",
                 "cheerio": "^1.0.0-rc.10",
diff --git a/server/socket-handlers/status-page-socket-handler.js b/server/socket-handlers/status-page-socket-handler.js
index 80017e7d..16d6ee73 100644
--- a/server/socket-handlers/status-page-socket-handler.js
+++ b/server/socket-handlers/status-page-socket-handler.js
@@ -202,7 +202,11 @@ module.exports.statusPageSocketHandler = (socket) => {
                     relationBean.weight = monitorOrder++;
                     relationBean.group_id = groupBean.id;
                     relationBean.monitor_id = monitor.id;
-                    relationBean.send_url = monitor.sendUrl;
+
+                    if (monitor.sendUrl !== undefined) {
+                        relationBean.send_url = monitor.sendUrl;
+                    }
+
                     await R.store(relationBean);
                 }
 

From 0d098b0958048ccf9503049a4436b9a4f758e4d8 Mon Sep 17 00:00:00 2001
From: c0derMo <jaydeveloper@outlook.de>
Date: Fri, 22 Jul 2022 15:47:04 +0000
Subject: [PATCH 69/99] Docker Hosts are now a table & have their own dialog

---
 db/patch-add-docker-columns.sql               |  13 +-
 server/client.js                              |  20 +++
 server/docker.js                              |  67 ++++++++
 server/model/docker_host.js                   |  19 +++
 server/model/monitor.js                       |  13 +-
 server/server.js                              |   8 +-
 .../socket-handlers/docker-socket-handler.js  |  67 ++++++++
 src/components/DockerHostDialog.vue           | 160 ++++++++++++++++++
 src/mixins/socket.js                          |   5 +
 src/pages/EditMonitor.vue                     |  44 +++--
 10 files changed, 385 insertions(+), 31 deletions(-)
 create mode 100644 server/docker.js
 create mode 100644 server/model/docker_host.js
 create mode 100644 server/socket-handlers/docker-socket-handler.js
 create mode 100644 src/components/DockerHostDialog.vue

diff --git a/db/patch-add-docker-columns.sql b/db/patch-add-docker-columns.sql
index 56475667..4cea448d 100644
--- a/db/patch-add-docker-columns.sql
+++ b/db/patch-add-docker-columns.sql
@@ -1,13 +1,18 @@
 -- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
 BEGIN TRANSACTION;
 
+CREATE TABLE docker_host (
+	id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+	user_id INT NOT NULL,
+	docker_daemon VARCHAR(255),
+	docker_type VARCHAR(255),
+	name VARCHAR(255)
+);
+
 ALTER TABLE monitor
-	ADD docker_daemon VARCHAR(255);
+	ADD docker_host INTEGER REFERENCES docker_host(id);
 
 ALTER TABLE monitor
 	ADD docker_container VARCHAR(255);
 
-ALTER TABLE monitor
-	ADD docker_type VARCHAR(255);
-
 COMMIT;
diff --git a/server/client.js b/server/client.js
index f6f133d1..61403842 100644
--- a/server/client.js
+++ b/server/client.js
@@ -122,10 +122,30 @@ async function sendInfo(socket) {
     });
 }
 
+async function sendDockerHostList(socket) {
+    const timeLogger = new TimeLogger();
+
+    let result = [];
+    let list = await R.find("docker_host", " user_id = ? ", [
+        socket.userID,
+    ]);
+
+    for (let bean of list) {
+        result.push(bean.export());
+    }
+
+    io.to(socket.userID).emit("dockerHostList", result);
+
+    timeLogger.print("Send Docker Host List");
+
+    return list;
+}
+
 module.exports = {
     sendNotificationList,
     sendImportantHeartbeatList,
     sendHeartbeatList,
     sendProxyList,
     sendInfo,
+    sendDockerHostList
 };
diff --git a/server/docker.js b/server/docker.js
new file mode 100644
index 00000000..a13236aa
--- /dev/null
+++ b/server/docker.js
@@ -0,0 +1,67 @@
+const axios = require("axios");
+const { R } = require("redbean-node");
+const version = require("../package.json").version;
+const https = require("https");
+
+class DockerHost {
+    static async save(dockerHost, dockerHostID, userID) {
+        let bean;
+
+        if (dockerHostID) {
+            bean = await R.findOne("docker_host", " id = ? AND user_id = ? ", [ dockerHostID, userID ]);
+
+            if (!bean) {
+                throw new Error("docker host not found");
+            }
+
+        } else {
+            bean = R.dispense("docker_host");
+        }
+
+        bean.user_id = userID;
+        bean.docker_daemon = dockerHost.docker_daemon;
+        bean.docker_type = dockerHost.docker_type;
+        bean.name = dockerHost.name;
+
+        await R.store(bean);
+
+        return bean;
+    }
+
+    static async delete(dockerHostID, userID) {
+        let bean = await R.findOne("docker_host", " id = ? AND user_id = ? ", [ dockerHostID, userID ]);
+
+        if (!bean) {
+            throw new Error("docker host not found");
+        }
+
+        await R.trash(bean);
+    }
+
+    static async getAmountContainer(dockerHost) {
+        const options = {
+            url: "/containers/json?all=true",
+            headers: {
+                "Accept": "*/*",
+                "User-Agent": "Uptime-Kuma/" + version
+            },
+            httpsAgent: new https.Agent({
+                maxCachedSessions: 0,      // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
+                rejectUnauthorized: false,
+            }),
+        };
+
+        if (dockerHost.docker_type === "socket") {
+            options.socketPath = dockerHost.docker_daemon;
+        } else if (dockerHost.docker_type === "tcp") {
+            options.baseURL = dockerHost.docker_daemon;
+        }
+
+        let res = await axios.request(options);
+        return res.data.length;
+    }
+}
+
+module.exports = {
+    DockerHost,
+}
\ No newline at end of file
diff --git a/server/model/docker_host.js b/server/model/docker_host.js
new file mode 100644
index 00000000..26f3035a
--- /dev/null
+++ b/server/model/docker_host.js
@@ -0,0 +1,19 @@
+const { BeanModel } = require("redbean-node/dist/bean-model");
+
+class DockerHost extends BeanModel {
+    /**
+     * Returns an object that ready to parse to JSON
+     * @returns {Object}
+     */
+    toJSON() {
+        return {
+            id: this._id,
+            userId: this._user_id,
+            daemon: this._dockerDaemon,
+            type: this._dockerType,
+            name: this._name,
+        }
+    }
+}
+
+module.exports = DockerHost;
\ No newline at end of file
diff --git a/server/model/monitor.js b/server/model/monitor.js
index eff167c6..373796e9 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -89,8 +89,7 @@ class Monitor extends BeanModel {
             dns_last_result: this.dns_last_result,
             pushToken: this.pushToken,
             docker_container: this.docker_container,
-            docker_daemon: this.docker_daemon,
-            docker_type: this.docker_type,
+            docker_host: this.docker_host,
             proxyId: this.proxy_id,
             notificationIDList,
             tags: tags,
@@ -471,6 +470,8 @@ class Monitor extends BeanModel {
                 } else if (this.type === "docker") {
                     log.debug(`[${this.name}] Prepare Options for Axios`);
 
+                    const docker_host = await R.load("docker_host", this.docker_host);
+
                     const options = {
                         url: `/containers/${this.docker_container}/json`,
                         headers: {
@@ -483,10 +484,10 @@ class Monitor extends BeanModel {
                         }),
                     };
 
-                    if (this.docker_type === "socket") {
-                        options.socketPath = this.docker_daemon;
-                    } else if (this.docker_type === "tcp") {
-                        options.baseURL = this.docker_daemon;
+                    if (docker_host._dockerType === "socket") {
+                        options.socketPath = docker_host._dockerDaemon;
+                    } else if (docker_host._dockerType === "tcp") {
+                        options.baseURL = docker_host._dockerDaemon;
                     }
 
                     log.debug(`[${this.name}] Axios Request`);
diff --git a/server/server.js b/server/server.js
index 71a9a7b5..1e566148 100644
--- a/server/server.js
+++ b/server/server.js
@@ -118,13 +118,14 @@ if (config.demoMode) {
 }
 
 // Must be after io instantiation
-const { sendNotificationList, sendHeartbeatList, sendImportantHeartbeatList, sendInfo, sendProxyList } = require("./client");
+const { sendNotificationList, sendHeartbeatList, sendImportantHeartbeatList, sendInfo, sendProxyList, sendDockerHostList } = require("./client");
 const { statusPageSocketHandler } = require("./socket-handlers/status-page-socket-handler");
 const databaseSocketHandler = require("./socket-handlers/database-socket-handler");
 const TwoFA = require("./2fa");
 const StatusPage = require("./model/status_page");
 const { cloudflaredSocketHandler, autoStart: cloudflaredAutoStart, stop: cloudflaredStop } = require("./socket-handlers/cloudflared-socket-handler");
 const { proxySocketHandler } = require("./socket-handlers/proxy-socket-handler");
+const { dockerSocketHandler } = require("./socket-handlers/docker-socket-handler");
 
 app.use(express.json());
 
@@ -665,8 +666,7 @@ let needSetup = false;
                 bean.dns_resolve_server = monitor.dns_resolve_server;
                 bean.pushToken = monitor.pushToken;
                 bean.docker_container = monitor.docker_container;
-                bean.docker_daemon = monitor.docker_daemon;
-                bean.docker_type = monitor.docker_type;
+                bean.docker_host = monitor.docker_host;
                 bean.proxyId = Number.isInteger(monitor.proxyId) ? monitor.proxyId : null;
                 bean.mqttUsername = monitor.mqttUsername;
                 bean.mqttPassword = monitor.mqttPassword;
@@ -1425,6 +1425,7 @@ let needSetup = false;
         cloudflaredSocketHandler(socket);
         databaseSocketHandler(socket);
         proxySocketHandler(socket);
+        dockerSocketHandler(socket);
 
         log.debug("server", "added all socket handlers");
 
@@ -1525,6 +1526,7 @@ async function afterLogin(socket, user) {
     let monitorList = await server.sendMonitorList(socket);
     sendNotificationList(socket);
     sendProxyList(socket);
+    sendDockerHostList(socket);
 
     await sleep(500);
 
diff --git a/server/socket-handlers/docker-socket-handler.js b/server/socket-handlers/docker-socket-handler.js
new file mode 100644
index 00000000..eddcd7b8
--- /dev/null
+++ b/server/socket-handlers/docker-socket-handler.js
@@ -0,0 +1,67 @@
+const { sendDockerHostList } = require("../client");
+const { checkLogin } = require("../util-server");
+const { DockerHost } = require("../docker");
+
+module.exports.dockerSocketHandler = (socket) => {
+    socket.on("addDockerHost", async (dockerHost, dockerHostID, callback) => {
+        try {
+            checkLogin(socket);
+
+            let dockerHostBean = await DockerHost.save(dockerHost, dockerHostID, socket.userID);
+            await sendDockerHostList(socket);
+
+            callback({
+                ok: true,
+                msg: "Saved",
+                id: dockerHostBean.id,
+            });
+
+        } catch (e) {
+            callback({
+                ok: false,
+                msg: e.message,
+            })
+        }
+    });
+
+    socket.on("deleteDockerHost", async (dockerHostID, callback) => {
+        try {
+            checkLogin(socket);
+
+            await DockerHost.delete(dockerHostID, socket.userID);
+            await sendDockerHostList(socket);
+
+            callback({
+                ok: true,
+                msg: "Deleted",
+            });
+
+        } catch (e) {
+            callback({
+                ok: false,
+                msg: e.message,
+            })
+        }
+    });
+
+    socket.on("testDockerHost", async (dockerHost, callback) => {
+        try {
+            checkLogin(socket);
+
+            let amount = await DockerHost.getAmountContainer(dockerHost);
+
+            callback({
+                ok: true,
+                msg: "Amount of containers: " + amount,
+            });
+
+        } catch (e) {
+            console.error(e);
+
+            callback({
+                ok: false,
+                msg: e.message,
+            })
+        }
+    })
+}
\ No newline at end of file
diff --git a/src/components/DockerHostDialog.vue b/src/components/DockerHostDialog.vue
new file mode 100644
index 00000000..e52c4ecf
--- /dev/null
+++ b/src/components/DockerHostDialog.vue
@@ -0,0 +1,160 @@
+<template>
+    <form @submit.prevent="submit">
+        <div ref="modal" class="modal fade" tabindex="-1" data-bs-backdrop="static">
+            <div class="modal-dialog">
+                <div class="modal-content">
+                    <div class="modal-header">
+                        <h5 id="exampleModalLabel" class="modal-title">
+                            {{ $t("Setup Docker Host") }}
+                        </h5>
+                        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" />
+                    </div>
+                    <div class="modal-body">
+                        <div class="mb-3">
+                            <label for="docker-name" class="form-label">{{ $t("Friendly Name") }}</label>
+                            <input id="docker-name" v-model="dockerHost.name" type="text" class="form-control" required>
+                        </div>
+
+                        <div class="mb-3">
+                            <label for="docker-type" class="form-label">{{ $t("Connection Type") }}</label>
+                            <select id="docker-type" v-model="dockerHost.dockerType" class="form-select">
+                                <option v-for="type in connectionTypes" :key="type" :value="type">{{ $t(type) }}</option>
+                            </select>
+                        </div>
+
+                        <div class="mb-3">
+                            <label for="docker-daemon" class="form-label">{{ $t("Docker Daemon") }}</label>
+                            <input id="docker-daemon" v-model="dockerHost.dockerDaemon" type="text" class="form-control" required>
+                        </div>
+                    </div>
+
+                    <div class="modal-footer">
+                        <button v-if="id" type="button" class="btn btn-danger" :disabled="processing" @click="deleteConfirm">
+                            {{ $t("Delete") }}
+                        </button>
+                        <button type="button" class="btn btn-warning" :disabled="processing" @click="test">
+                            {{ $t("Test") }}
+                        </button>
+                        <button type="submit" class="btn btn-primary" :disabled="processing">
+                            <div v-if="processing" class="spinner-border spinner-border-sm me-1"></div>
+                            {{ $t("Save") }}
+                        </button>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </form>
+
+    <Confirm ref="confirmDelete" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="deleteDockerHost">
+        {{ $t("deleteDockerHostMsg") }}
+    </Confirm>
+</template>
+
+<script lang="ts">
+import { Modal } from "bootstrap";
+
+import Confirm from "./Confirm.vue";
+
+export default {
+    components: {
+        Confirm,
+    },
+    props: {},
+    emits: [ "added" ],
+    data() {
+        return {
+            model: null,
+            processing: false,
+            id: null,
+            connectionTypes: ["socket", "tcp"],
+            dockerHost: {
+                name: "",
+                dockerDaemon: "",
+                dockerType: "",
+                // Do not set default value here, please scroll to show()
+            }
+        };
+    },
+
+    mounted() {
+        this.modal = new Modal(this.$refs.modal);
+    },
+    methods: {
+
+        deleteConfirm() {
+            this.modal.hide();
+            this.$refs.confirmDelete.show();
+        },
+
+        show(dockerHostID) {
+            if (dockerHostID) {
+                this.id = dockerHostID;
+
+                for (let n of this.$root.dockerHostList) {
+                    if (n.id === dockerHostID) {
+                        this.dockerHost = n;
+                        break;
+                    }
+                }
+            } else {
+                this.id = null;
+                this.dockerHost = {
+                    name: "",
+                    dockerType: "socket",
+                    dockerDaemon: "/var/run/docker.sock",
+                };
+            }
+
+            this.modal.show();
+        },
+
+        submit() {
+            this.processing = true;
+            this.$root.getSocket().emit("addDockerHost", this.dockerHost, this.id, (res) => {
+                this.$root.toastRes(res);
+                this.processing = false;
+
+                if (res.ok) {
+                    this.modal.hide();
+
+                    // Emit added event, doesn't emit edit.
+                    if (! this.id) {
+                        this.$emit("added", res.id);
+                    }
+
+                }
+            });
+        },
+
+        test() {
+            this.processing = true;
+            this.$root.getSocket().emit("testDockerHost", this.dockerHost, (res) => {
+                this.$root.toastRes(res);
+                this.processing = false;
+            });
+        },
+
+        deleteDockerHost() {
+            this.processing = true;
+            this.$root.getSocket().emit("deleteDockerHost", this.id, (res) => {
+                this.$root.toastRes(res);
+                this.processing = false;
+
+                if (res.ok) {
+                    this.modal.hide();
+                }
+            });
+        },
+    },
+};
+</script>
+
+<style lang="scss" scoped>
+@import "../assets/vars.scss";
+
+.dark {
+    .modal-dialog .form-text, .modal-dialog p {
+        color: $dark-font-color;
+    }
+}
+</style>
diff --git a/src/mixins/socket.js b/src/mixins/socket.js
index c54b573f..f6de82c2 100644
--- a/src/mixins/socket.js
+++ b/src/mixins/socket.js
@@ -39,6 +39,7 @@ export default {
             uptimeList: { },
             tlsInfoList: {},
             notificationList: [],
+            dockerHostList: [],
             statusPageListLoaded: false,
             statusPageList: [],
             proxyList: [],
@@ -141,6 +142,10 @@ export default {
                 });
             });
 
+            socket.on("dockerHostList", (data) => {
+                this.dockerHostList = data;
+            })
+
             socket.on("heartbeat", (data) => {
                 if (! (data.monitorID in this.heartbeatList)) {
                     this.heartbeatList[data.monitorID] = [];
diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index 60032459..5ff318bf 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -148,25 +148,25 @@
                                 <input id="docker_container" v-model="monitor.docker_container" type="text" class="form-control" required>
                             </div>
 
-                            <!-- Docker Connection Type -->
+                            <!-- Docker Host -->
                             <!-- For Docker Type -->
                             <div v-if="monitor.type === 'docker'" class="my-3">
-                                <label for="docker_type" class="form-label">{{ $t("Docker Type") }}</label>
-                                <select id="docker_type" v-model="monitor.docker_type" class="form-select">
-                                    <option value="socket">
-                                        {{ $t("docker_socket") }}
-                                    </option>
-                                    <option value="tcp">
-                                        {{ $t("docker_tcp") }}
-                                    </option>
-                                </select>
-                            </div>
+                                <h2 class="mb-2">{{ $t("Docker Host") }}</h2>
+                                <p v-if="$root.dockerHostList.length === 0">
+                                    {{ $t("Not available, please setup.") }}
+                                </p>
 
-                            <!-- Docker Daemon -->
-                            <!-- For Docker Type -->
-                            <div v-if="monitor.type === 'docker'" class="my-3">
-                                <label for="docker_daemon" class="form-label">{{ $t("Docker Daemon") }}</label>
-                                <input id="docker_daemon" v-model="monitor.docker_daemon" type="text" class="form-control" required>
+                                <div class="mb-3">
+                                    <label for="docker-host" class="form-label">{{ $t("Docker Host") }}</label>
+                                    <select id="docket-host" v-model="monitor.docker_host" class="form-select">
+                                        <option v-for="host in $root.dockerHostList" :key="host.id" :value="host.id">{{ host.name }}</option>
+                                    </select>
+                                    <a href="#" @click="$refs.dockerHostDialog.show(monitor.docker_host)">{{ $t("Edit") }}</a>
+                                </div>
+
+                                <button class="btn btn-primary me-2" type="button" @click="$refs.dockerHostDialog.show()">
+                                    {{ $t("Setup Docker Host") }}
+                                </button>
                             </div>
 
                             <!-- MQTT -->
@@ -446,6 +446,7 @@
             </form>
 
             <NotificationDialog ref="notificationDialog" @added="addedNotification" />
+            <DockerHostDialog ref="dockerHostDialog" @added="addedDockerHost" />
             <ProxyDialog ref="proxyDialog" @added="addedProxy" />
         </div>
     </transition>
@@ -456,6 +457,7 @@ import VueMultiselect from "vue-multiselect";
 import { useToast } from "vue-toastification";
 import CopyableInput from "../components/CopyableInput.vue";
 import NotificationDialog from "../components/NotificationDialog.vue";
+import DockerHostDialog from "../components/DockerHostDialog.vue";
 import ProxyDialog from "../components/ProxyDialog.vue";
 import TagsManager from "../components/TagsManager.vue";
 import { genSecret, isDev } from "../util.ts";
@@ -467,6 +469,7 @@ export default {
         ProxyDialog,
         CopyableInput,
         NotificationDialog,
+        DockerHostDialog,
         TagsManager,
         VueMultiselect,
     },
@@ -625,8 +628,7 @@ export default {
                     dns_resolve_type: "A",
                     dns_resolve_server: "1.1.1.1",
                     docker_container: "",
-                    docker_daemon: "/var/run/docker.sock",
-                    docker_type: "socket",
+                    docker_host: null,
                     proxyId: null,
                     mqttUsername: "",
                     mqttPassword: "",
@@ -740,6 +742,12 @@ export default {
         addedProxy(id) {
             this.monitor.proxyId = id;
         },
+
+        // Added a Docker Host Event
+        // Enable it if the Docker Host is added in EditMonitor.vue
+        addedDockerHost(id) {
+            this.monitor.docker_host = id;
+        }
     },
 };
 </script>

From e356d5f62391fbcbaa523c291e2219372128ed7c Mon Sep 17 00:00:00 2001
From: c0derMo <jaydeveloper@outlook.de>
Date: Fri, 22 Jul 2022 15:57:40 +0000
Subject: [PATCH 70/99] Fixing linting & adding documentation

---
 server/client.js                              |  5 +++++
 server/docker.js                              | 20 ++++++++++++++++++-
 server/model/docker_host.js                   |  4 ++--
 server/model/monitor.js                       | 10 +++++-----
 .../socket-handlers/docker-socket-handler.js  | 14 ++++++++-----
 src/components/DockerHostDialog.vue           |  2 +-
 src/mixins/socket.js                          |  2 +-
 7 files changed, 42 insertions(+), 15 deletions(-)

diff --git a/server/client.js b/server/client.js
index 61403842..bda77642 100644
--- a/server/client.js
+++ b/server/client.js
@@ -122,6 +122,11 @@ async function sendInfo(socket) {
     });
 }
 
+/**
+ * Send list of docker hosts to client
+ * @param {Socket} socket Socket.io socket instance
+ * @returns {Promise<Bean[]>}
+ */
 async function sendDockerHostList(socket) {
     const timeLogger = new TimeLogger();
 
diff --git a/server/docker.js b/server/docker.js
index a13236aa..57e793ab 100644
--- a/server/docker.js
+++ b/server/docker.js
@@ -4,6 +4,13 @@ const version = require("../package.json").version;
 const https = require("https");
 
 class DockerHost {
+    /**
+     * Save a docker host
+     * @param {Object} dockerHost Docker host to save
+     * @param {?number} dockerHostID ID of the docker host to update
+     * @param {number} userID ID of the user who adds the docker host
+     * @returns {Promise<Bean>}
+     */
     static async save(dockerHost, dockerHostID, userID) {
         let bean;
 
@@ -28,6 +35,12 @@ class DockerHost {
         return bean;
     }
 
+    /**
+     * Delete a Docker host
+     * @param {number} dockerHostID ID of the Docker host to delete
+     * @param {number} userID ID of the user who created the Docker host
+     * @returns {Promise<void>}
+     */
     static async delete(dockerHostID, userID) {
         let bean = await R.findOne("docker_host", " id = ? AND user_id = ? ", [ dockerHostID, userID ]);
 
@@ -38,6 +51,11 @@ class DockerHost {
         await R.trash(bean);
     }
 
+    /**
+     * Fetches the amount of containers on the Docker host
+     * @param {Object} dockerHost Docker host to check for
+     * @returns {number} Total amount of containers on the host
+     */
     static async getAmountContainer(dockerHost) {
         const options = {
             url: "/containers/json?all=true",
@@ -64,4 +82,4 @@ class DockerHost {
 
 module.exports = {
     DockerHost,
-}
\ No newline at end of file
+};
diff --git a/server/model/docker_host.js b/server/model/docker_host.js
index 26f3035a..229a9a52 100644
--- a/server/model/docker_host.js
+++ b/server/model/docker_host.js
@@ -12,8 +12,8 @@ class DockerHost extends BeanModel {
             daemon: this._dockerDaemon,
             type: this._dockerType,
             name: this._name,
-        }
+        };
     }
 }
 
-module.exports = DockerHost;
\ No newline at end of file
+module.exports = DockerHost;
diff --git a/server/model/monitor.js b/server/model/monitor.js
index 373796e9..babff876 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -470,7 +470,7 @@ class Monitor extends BeanModel {
                 } else if (this.type === "docker") {
                     log.debug(`[${this.name}] Prepare Options for Axios`);
 
-                    const docker_host = await R.load("docker_host", this.docker_host);
+                    const dockerHost = await R.load("docker_host", this.docker_host);
 
                     const options = {
                         url: `/containers/${this.docker_container}/json`,
@@ -484,10 +484,10 @@ class Monitor extends BeanModel {
                         }),
                     };
 
-                    if (docker_host._dockerType === "socket") {
-                        options.socketPath = docker_host._dockerDaemon;
-                    } else if (docker_host._dockerType === "tcp") {
-                        options.baseURL = docker_host._dockerDaemon;
+                    if (dockerHost._dockerType === "socket") {
+                        options.socketPath = dockerHost._dockerDaemon;
+                    } else if (dockerHost._dockerType === "tcp") {
+                        options.baseURL = dockerHost._dockerDaemon;
                     }
 
                     log.debug(`[${this.name}] Axios Request`);
diff --git a/server/socket-handlers/docker-socket-handler.js b/server/socket-handlers/docker-socket-handler.js
index eddcd7b8..7f3646bb 100644
--- a/server/socket-handlers/docker-socket-handler.js
+++ b/server/socket-handlers/docker-socket-handler.js
@@ -2,6 +2,10 @@ const { sendDockerHostList } = require("../client");
 const { checkLogin } = require("../util-server");
 const { DockerHost } = require("../docker");
 
+/**
+ * Handlers for docker hosts
+ * @param {Socket} socket Socket.io instance
+ */
 module.exports.dockerSocketHandler = (socket) => {
     socket.on("addDockerHost", async (dockerHost, dockerHostID, callback) => {
         try {
@@ -20,7 +24,7 @@ module.exports.dockerSocketHandler = (socket) => {
             callback({
                 ok: false,
                 msg: e.message,
-            })
+            });
         }
     });
 
@@ -40,7 +44,7 @@ module.exports.dockerSocketHandler = (socket) => {
             callback({
                 ok: false,
                 msg: e.message,
-            })
+            });
         }
     });
 
@@ -61,7 +65,7 @@ module.exports.dockerSocketHandler = (socket) => {
             callback({
                 ok: false,
                 msg: e.message,
-            })
+            });
         }
-    })
-}
\ No newline at end of file
+    });
+};
diff --git a/src/components/DockerHostDialog.vue b/src/components/DockerHostDialog.vue
index e52c4ecf..d7cf2de0 100644
--- a/src/components/DockerHostDialog.vue
+++ b/src/components/DockerHostDialog.vue
@@ -66,7 +66,7 @@ export default {
             model: null,
             processing: false,
             id: null,
-            connectionTypes: ["socket", "tcp"],
+            connectionTypes: [ "socket", "tcp" ],
             dockerHost: {
                 name: "",
                 dockerDaemon: "",
diff --git a/src/mixins/socket.js b/src/mixins/socket.js
index f6de82c2..e2096b37 100644
--- a/src/mixins/socket.js
+++ b/src/mixins/socket.js
@@ -144,7 +144,7 @@ export default {
 
             socket.on("dockerHostList", (data) => {
                 this.dockerHostList = data;
-            })
+            });
 
             socket.on("heartbeat", (data) => {
                 if (! (data.monitorID in this.heartbeatList)) {

From f1bcecb0c64c9197592f007faf8cdd755755d2c0 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Sun, 24 Jul 2022 14:08:03 +0800
Subject: [PATCH 71/99] Merge package-lock.json

---
 package-lock.json | 216 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 216 insertions(+)

diff --git a/package-lock.json b/package-lock.json
index d76f5a94..778e6bc3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -42,6 +42,8 @@
                 "nodemailer": "~6.6.5",
                 "notp": "~2.0.3",
                 "password-hash": "~1.2.2",
+                "pg": "^8.7.3",
+                "pg-connection-string": "^2.5.0",
                 "prom-client": "~13.2.0",
                 "prometheus-api-metrics": "~3.2.1",
                 "redbean-node": "0.1.4",
@@ -4789,6 +4791,14 @@
             "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
             "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
         },
+        "node_modules/buffer-writer": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
+            "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==",
+            "engines": {
+                "node": ">=4"
+            }
+        },
         "node_modules/bulk-write-stream": {
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/bulk-write-stream/-/bulk-write-stream-2.0.1.tgz",
@@ -12488,6 +12498,11 @@
                 "url": "https://github.com/sponsors/sindresorhus"
             }
         },
+        "node_modules/packet-reader": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
+            "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
+        },
         "node_modules/parent-module": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -12636,11 +12651,88 @@
             "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
             "optional": true
         },
+        "node_modules/pg": {
+            "version": "8.7.3",
+            "resolved": "https://registry.npmjs.org/pg/-/pg-8.7.3.tgz",
+            "integrity": "sha512-HPmH4GH4H3AOprDJOazoIcpI49XFsHCe8xlrjHkWiapdbHK+HLtbm/GQzXYAZwmPju/kzKhjaSfMACG+8cgJcw==",
+            "dependencies": {
+                "buffer-writer": "2.0.0",
+                "packet-reader": "1.0.0",
+                "pg-connection-string": "^2.5.0",
+                "pg-pool": "^3.5.1",
+                "pg-protocol": "^1.5.0",
+                "pg-types": "^2.1.0",
+                "pgpass": "1.x"
+            },
+            "engines": {
+                "node": ">= 8.0.0"
+            },
+            "peerDependencies": {
+                "pg-native": ">=2.0.0"
+            },
+            "peerDependenciesMeta": {
+                "pg-native": {
+                    "optional": true
+                }
+            }
+        },
         "node_modules/pg-connection-string": {
             "version": "2.5.0",
             "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz",
             "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ=="
         },
+        "node_modules/pg-int8": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
+            "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
+            "engines": {
+                "node": ">=4.0.0"
+            }
+        },
+        "node_modules/pg-pool": {
+            "version": "3.5.1",
+            "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.1.tgz",
+            "integrity": "sha512-6iCR0wVrro6OOHFsyavV+i6KYL4lVNyYAB9RD18w66xSzN+d8b66HiwuP30Gp1SH5O9T82fckkzsRjlrhD0ioQ==",
+            "peerDependencies": {
+                "pg": ">=8.0"
+            }
+        },
+        "node_modules/pg-protocol": {
+            "version": "1.5.0",
+            "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz",
+            "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ=="
+        },
+        "node_modules/pg-types": {
+            "version": "2.2.0",
+            "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
+            "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
+            "dependencies": {
+                "pg-int8": "1.0.1",
+                "postgres-array": "~2.0.0",
+                "postgres-bytea": "~1.0.0",
+                "postgres-date": "~1.0.4",
+                "postgres-interval": "^1.1.0"
+            },
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/pgpass": {
+            "version": "1.0.5",
+            "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
+            "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
+            "dependencies": {
+                "split2": "^4.1.0"
+            }
+        },
+        "node_modules/pgpass/node_modules/split2": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz",
+            "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==",
+            "engines": {
+                "node": ">= 10.x"
+            }
+        },
         "node_modules/picocolors": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@@ -12896,6 +12988,41 @@
             "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
             "dev": true
         },
+        "node_modules/postgres-array": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
+            "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/postgres-bytea": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
+            "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/postgres-date": {
+            "version": "1.0.7",
+            "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
+            "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/postgres-interval": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
+            "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
+            "dependencies": {
+                "xtend": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
         "node_modules/prelude-ls": {
             "version": "1.2.1",
             "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -20016,6 +20143,11 @@
             "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
             "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
         },
+        "buffer-writer": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
+            "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw=="
+        },
         "bulk-write-stream": {
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/bulk-write-stream/-/bulk-write-stream-2.0.1.tgz",
@@ -25736,6 +25868,11 @@
                 "p-timeout": "^3.0.0"
             }
         },
+        "packet-reader": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
+            "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
+        },
         "parent-module": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -25845,11 +25982,67 @@
             "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
             "optional": true
         },
+        "pg": {
+            "version": "8.7.3",
+            "resolved": "https://registry.npmjs.org/pg/-/pg-8.7.3.tgz",
+            "integrity": "sha512-HPmH4GH4H3AOprDJOazoIcpI49XFsHCe8xlrjHkWiapdbHK+HLtbm/GQzXYAZwmPju/kzKhjaSfMACG+8cgJcw==",
+            "requires": {
+                "buffer-writer": "2.0.0",
+                "packet-reader": "1.0.0",
+                "pg-connection-string": "^2.5.0",
+                "pg-pool": "^3.5.1",
+                "pg-protocol": "^1.5.0",
+                "pg-types": "^2.1.0",
+                "pgpass": "1.x"
+            }
+        },
         "pg-connection-string": {
             "version": "2.5.0",
             "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz",
             "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ=="
         },
+        "pg-int8": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
+            "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="
+        },
+        "pg-pool": {
+            "version": "3.5.1",
+            "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.1.tgz",
+            "integrity": "sha512-6iCR0wVrro6OOHFsyavV+i6KYL4lVNyYAB9RD18w66xSzN+d8b66HiwuP30Gp1SH5O9T82fckkzsRjlrhD0ioQ=="
+        },
+        "pg-protocol": {
+            "version": "1.5.0",
+            "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz",
+            "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ=="
+        },
+        "pg-types": {
+            "version": "2.2.0",
+            "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
+            "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
+            "requires": {
+                "pg-int8": "1.0.1",
+                "postgres-array": "~2.0.0",
+                "postgres-bytea": "~1.0.0",
+                "postgres-date": "~1.0.4",
+                "postgres-interval": "^1.1.0"
+            }
+        },
+        "pgpass": {
+            "version": "1.0.5",
+            "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
+            "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
+            "requires": {
+                "split2": "^4.1.0"
+            },
+            "dependencies": {
+                "split2": {
+                    "version": "4.1.0",
+                    "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz",
+                    "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ=="
+                }
+            }
+        },
         "picocolors": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@@ -26018,6 +26211,29 @@
             "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
             "dev": true
         },
+        "postgres-array": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
+            "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="
+        },
+        "postgres-bytea": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
+            "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w=="
+        },
+        "postgres-date": {
+            "version": "1.0.7",
+            "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
+            "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="
+        },
+        "postgres-interval": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
+            "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
+            "requires": {
+                "xtend": "^4.0.0"
+            }
+        },
         "prelude-ls": {
             "version": "1.2.1",
             "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",

From fb3b407577b5b2c9e9b8e6a45d53a6b32caf8d7b Mon Sep 17 00:00:00 2001
From: c0derMo <jaydeveloper@outlook.de>
Date: Sun, 24 Jul 2022 12:34:43 +0000
Subject: [PATCH 72/99] Added a settings page & localization

---
 server/docker.js                   |  3 ++
 src/components/settings/Docker.vue | 50 ++++++++++++++++++++++++++++++
 src/languages/en.js                | 20 +++++++-----
 src/pages/EditMonitor.vue          |  2 +-
 src/pages/Settings.vue             |  3 ++
 src/router.js                      |  5 +++
 6 files changed, 74 insertions(+), 9 deletions(-)
 create mode 100644 src/components/settings/Docker.vue

diff --git a/server/docker.js b/server/docker.js
index 57e793ab..ed9e0854 100644
--- a/server/docker.js
+++ b/server/docker.js
@@ -48,6 +48,9 @@ class DockerHost {
             throw new Error("docker host not found");
         }
 
+        // Delete removed proxy from monitors if exists
+        await R.exec("UPDATE monitor SET docker_host = null WHERE docker_host = ?", [ dockerHostID ]);
+
         await R.trash(bean);
     }
 
diff --git a/src/components/settings/Docker.vue b/src/components/settings/Docker.vue
new file mode 100644
index 00000000..7b99bb8b
--- /dev/null
+++ b/src/components/settings/Docker.vue
@@ -0,0 +1,50 @@
+<template>
+    <div>
+        <div class="dockerHost-list my-4">
+            <p v-if="$root.dockerHostList.length === 0">
+                {{ $t("Not available, please setup.") }}
+            </p>
+
+            <ul class="list-group mb-3" style="border-radius: 1rem;">
+                <li v-for="(dockerHost, index) in $root.dockerHostList" :key="index" class="list-group-item">
+                    {{ dockerHost.name }}<br>
+                    <a href="#" @click="$refs.dockerHostDialog.show(dockerHost.id)">{{ $t("Edit") }}</a>
+                </li>
+            </ul>
+
+            <button class="btn btn-primary me-2" type="button" @click="$refs.dockerHostDialog.show()">
+                {{ $t("Setup Docker Host") }}
+            </button>
+        </div>
+
+        <DockerHostDialog ref="dockerHostDialog" />
+    </div>
+</template>
+
+<script>
+import DockerHostDialog from "../../components/DockerHostDialog.vue";
+import ActionInput from "../ActionInput.vue";
+
+export default {
+    components: {
+        DockerHostDialog,
+        ActionInput,
+    },
+
+    data() {
+        return {};
+    },
+
+    computed: {
+        settings() {
+            return this.$parent.$parent.$parent.settings;
+        },
+        saveSettings() {
+            return this.$parent.$parent.$parent.saveSettings;
+        },
+        settingsLoaded() {
+            return this.$parent.$parent.$parent.settingsLoaded;
+        },
+    }
+};
+</script>
\ No newline at end of file
diff --git a/src/languages/en.js b/src/languages/en.js
index 37392a4f..c7e4b377 100644
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -372,12 +372,6 @@ export default {
     smtpDkimHashAlgo: "Hash Algorithm (Optional)",
     smtpDkimheaderFieldNames: "Header Keys to sign (Optional)",
     smtpDkimskipFields: "Header Keys not to sign (Optional)",
-    "Container Name / ID": "Container Name / ID",
-    "Docker Daemon": "Docker Daemon",
-    "Docker Container": "Docker Container",
-    "Docker Type": "Connection Type",
-    docker_socket: "Socket",
-    docker_tcp: "TCP / HTTP",
     wayToGetPagerDutyKey: "You can get this by going to Service -> Service Directory -> (Select a service) -> Integrations -> Add integration. Here you can search for \"Events API V2\". More info {0}",
     "Integration Key": "Integration Key",
     "Integration URL": "Integration URL",
@@ -487,7 +481,7 @@ export default {
     "Leave blank to use a shared sender number.": "Leave blank to use a shared sender number.",
     "Octopush API Version": "Octopush API Version",
     "Legacy Octopush-DM": "Legacy Octopush-DM",
-    "endpoint": "endpoint",
+    endpoint: "endpoint",
     octopushAPIKey: "\"API key\" from HTTP API credentials in control panel",
     octopushLogin: "\"Login\" from HTTP API credentials in control panel",
     promosmsLogin: "API Login Name",
@@ -531,7 +525,17 @@ export default {
     "Coming Soon": "Coming Soon",
     wayToGetClickSendSMSToken: "You can get API Username and API Key from {0} .",
     "Connection String": "Connection String",
-    "Query": "Query",
+    Query: "Query",
     settingsCertificateExpiry: "TLS Certificate Expiry",
     certificationExpiryDescription: "HTTPS Monitors trigger notification when TLS certificate expires in:",
+    "Setup Docker Host": "Setup Docker Host",
+    "Connection Type": "Connection Type",
+    "Docker Daemon": "Docker Daemon",
+    deleteDockerHostMsg: "Are you sure want to delete this docker host for all monitors?",
+    socket: "Socket",
+    tcp: "TCP / HTTP",
+    "Docker Container": "Docker Container",
+    "Container Name / ID": "Container Name / ID",
+    "Docker Host": "Docker Host",
+    "Docker Hosts": "Docker Hosts"
 };
diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index 5ff318bf..c93fa97d 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -156,7 +156,7 @@
                                     {{ $t("Not available, please setup.") }}
                                 </p>
 
-                                <div class="mb-3">
+                                <div class="mb-3" v-else>
                                     <label for="docker-host" class="form-label">{{ $t("Docker Host") }}</label>
                                     <select id="docket-host" v-model="monitor.docker_host" class="form-select">
                                         <option v-for="host in $root.dockerHostList" :key="host.id" :value="host.id">{{ host.name }}</option>
diff --git a/src/pages/Settings.vue b/src/pages/Settings.vue
index 54d15009..d8ebc26e 100644
--- a/src/pages/Settings.vue
+++ b/src/pages/Settings.vue
@@ -89,6 +89,9 @@ export default {
                 "monitor-history": {
                     title: this.$t("Monitor History"),
                 },
+                "docker-hosts": {
+                    title: this.$t("Docker Hosts"),
+                },
                 security: {
                     title: this.$t("Security"),
                 },
diff --git a/src/router.js b/src/router.js
index 72619477..7d29a188 100644
--- a/src/router.js
+++ b/src/router.js
@@ -25,6 +25,7 @@ const Security = () => import("./components/settings/Security.vue");
 import Proxies from "./components/settings/Proxies.vue";
 import Backup from "./components/settings/Backup.vue";
 import About from "./components/settings/About.vue";
+import DockerHosts from "./components/settings/Docker.vue";
 
 const routes = [
     {
@@ -95,6 +96,10 @@ const routes = [
                                 path: "monitor-history",
                                 component: MonitorHistory,
                             },
+                            {
+                                path: "docker-hosts",
+                                component: DockerHosts,
+                            },
                             {
                                 path: "security",
                                 component: Security,

From 13f7db655b9d2b718a65a3902406c79066090f85 Mon Sep 17 00:00:00 2001
From: __filename <63600304+ankhgerel@users.noreply.github.com>
Date: Sun, 24 Jul 2022 20:44:43 +0800
Subject: [PATCH 73/99] chore(locale): change some typo ko-KR

---
 src/languages/ko-KR.js | 52 +++++++++++++++++++++---------------------
 1 file changed, 26 insertions(+), 26 deletions(-)

diff --git a/src/languages/ko-KR.js b/src/languages/ko-KR.js
index dbb02e65..4d1d6194 100644
--- a/src/languages/ko-KR.js
+++ b/src/languages/ko-KR.js
@@ -3,7 +3,7 @@ export default {
     checkEverySecond: "{0}초마다 확인해요.",
     retryCheckEverySecond: "{0}초마다 다시 확인해요.",
     retriesDescription: "서비스가 중단된 후 알림을 보내기 전 최대 재시도 횟수",
-    ignoreTLSError: "HTTPS 웹사이트에서 TLS/SSL 에러 무시하기",
+    ignoreTLSError: "HTTPS 웹사이트에서 TLS/SSL 오류 무시하기",
     upsideDownModeDescription: "서버 상태를 반대로 표시해요. 서버가 작동하면 오프라인으로 표시할 거예요.",
     maxRedirectDescription: "최대 리다이렉트 횟수예요. 0을 입력하면 리다이렉트를 꺼요.",
     acceptedStatusCodesDescription: "응답 성공으로 간주할 상태 코드를 정해요.",
@@ -30,7 +30,7 @@ export default {
     Dashboard: "대시보드",
     "New Update": "새로운 업데이트",
     Language: "언어",
-    Appearance: "외형",
+    Appearance: "디스플레이",
     Theme: "테마",
     General: "일반",
     Version: "버전",
@@ -78,7 +78,7 @@ export default {
     Notifications: "알림",
     "Not available, please setup.": "존재하지 않아요, 새로운 거 하나 만드는 건 어때요?",
     "Setup Notification": "알림 설정",
-    Light: "라이트",
+    Light: "화이트",
     Dark: "다크",
     Auto: "자동",
     "Theme - Heartbeat Bar": "테마 - 하트비트 바",
@@ -91,7 +91,7 @@ export default {
     "Discourage search engines from indexing site": "검색 엔진 인덱싱 거부",
     "Change Password": "비밀번호 변경",
     "Current Password": "기존 비밀번호",
-    "New Password": "새로운 비밀번호",
+    "New Password": "새 비밀번호",
     "Repeat New Password": "새로운 비밀번호 재입력",
     "Update Password": "비밀번호 변경",
     "Disable Auth": "인증 비활성화",
@@ -109,14 +109,14 @@ export default {
     Password: "비밀번호",
     "Remember me": "비밀번호 기억하기",
     Login: "로그인",
-    "No Monitors, please": "모니터링이 없어요,",
-    "add one": "하나 추가해봐요",
+    "No Monitors, please": "모니터링이 현재 없어요,",
+    "add one": "한번 추가해보실레요?",
     "Notification Type": "알림 종류",
     Email: "이메일",
     Test: "테스트",
     "Certificate Info": "인증서 정보",
     "Resolver Server": "Resolver 서버",
-    "Resource Record Type": "자원 레코드 유형",
+    "Resource Record Type": "리소스 레코드 유형",
     "Last Result": "최근 결과",
     "Create your admin account": "관리자 계정 만들기",
     "Repeat Password": "비밀번호 재입력",
@@ -208,19 +208,19 @@ export default {
     smtpBCC: "숨은 참조",
     discord: "Discord",
     "Discord Webhook URL": "Discord Webhook URL",
-    wayToGetDiscordURL: "서버 설정 -> 연동 -> 웹후크 보기 -> 새 웹후크에서 얻을 수 있어요.",
+    wayToGetDiscordURL: "서버 설정 -> 연동 -> 웹후크 보기 -> 새 웹후크에서 얻을 수 있어요!",
     "Bot Display Name": "표시 이름",
     "Prefix Custom Message": "접두사 메시지",
     "Hello @everyone is...": "{'@'}everyone 서버 상태 알림이에요...",
     teams: "Microsoft Teams",
     "Webhook URL": "Webhook URL",
-    wayToGetTeamsURL: "{0}에서 Webhook을 어떻게 만드는지 알아봐요.",
+    wayToGetTeamsURL: "{0}에서 Webhook을 어떻게 만드는지 알아보세요!",
     signal: "Signal",
     Number: "숫자",
     Recipients: "받는 사람",
     needSignalAPI: "REST API를 사용하는 Signal 클라이언트가 있어야 해요.",
     wayToCheckSignalURL: "밑에 URL을 확인해 URL 설정 방법을 볼 수 있어요.",
-    signalImportant: "중요: 받는 사람의 그룹과 숫자는 섞을 수 없어요!",
+    signalImportant: "경고: 받는 사람의 그룹과 숫자는 섞을 수 없어요!",
     gotify: "Gotify",
     "Application Token": "애플리케이션 토큰",
     "Server URL": "서버 URL",
@@ -230,8 +230,8 @@ export default {
     "Channel Name": "채널 이름",
     "Uptime Kuma URL": "Uptime Kuma URL",
     aboutWebhooks: "Webhook에 대한 설명: {0}",
-    aboutChannelName: "Webhook 채널을 우회하려면 {0} 채널 이름칸에 채널 이름을 입력해주세요. 예: #기타-채널",
-    aboutKumaURL: "Uptime Kuma URL칸을 공백으로 두면 기본적으로 Project Github 페이지로 설정해요.",
+    aboutChannelName: "Webhook 채널을 무시하려면 {0} 채널 이름칸에 채널 이름을 입력해주세요. 예: #기타-채널",
+    aboutKumaURL: "Uptime Kuma URL칸을 공백으로 두면 기본적으로 Github Project  페이지로 설정해요.",
     emojiCheatSheet: "이모지 목록 시트: {0}",
     "rocket.chat": "Rocket.chat",
     pushover: "Pushover",
@@ -243,8 +243,8 @@ export default {
     pushbullet: "Pushbullet",
     line: "Line Messenger",
     mattermost: "Mattermost",
-    "User Key": "사용자 키",
-    Device: "장치",
+    "User Key": "유저 키",
+    Device: "디바이스",
     "Message Title": "메시지 제목",
     "Notification Sound": "알림음",
     "More info on:": "자세한 정보: {0}",
@@ -254,7 +254,7 @@ export default {
     octopushTypePremium: "프리미엄 (빠름) - 알림 기능에 적합해요)",
     octopushTypeLowCost: "저렴한 요금 (느림) - 가끔 차단될 수 있어요)",
     "Check octopush prices": "{0}에서 Octopush 가격을 확인할 수 있어요.",
-    octopushPhoneNumber: "휴대전화 번호 (intl format, eg : +33612345678) ",
+    octopushPhoneNumber: "휴대전화 번호 (intl format, 예시: +82 1023456789) ",
     octopushSMSSender: "보내는 사람 이름 : 3-11개의 영숫자 및 여백공간 (a-z, A-Z, 0-9)",
     "LunaSea Device ID": "LunaSea 장치 ID",
     "Apprise URL": "Apprise URL",
@@ -324,17 +324,17 @@ export default {
     Content: "내용",
     Style: "스타일",
     info: "정보",
-    warning: "경고",
-    danger: "위험",
+    warning: "주의",
+    danger: "경고",
     primary: "기본",
-    light: "라이트",
+    light: "화이트",
     dark: "다크",
-    Post: "올리기",
+    Post: "게시",
     "Please input title and content": "제목과 내용을 작성해주세요.",
     Created: "생성 날짜",
     "Last Updated": "마지막 업데이트",
     Unpin: "제거",
-    "Switch to Light Theme": "라이트 테마로 전환",
+    "Switch to Light Theme": "화이트 테마로 전환",
     "Switch to Dark Theme": "다크 테마로 전환",
     "Show Tags": "태그 보이기",
     "Hide Tags": "태그 숨기기",
@@ -361,8 +361,8 @@ export default {
     topicExplanation: "모니터링할 MQTT Topic",
     successMessage: "성공 메시지",
     successMessageExplanation: "성공으로 간주되는 MQTT 메시지",
-    error: "error",
-    critical: "critical",
+    error: "오류",
+    critical: "크리티컬",
     Customize: "커스터마이즈",
     "Custom Footer": "커스텀 Footer",
     "Custom CSS": "커스텀 CSS",
@@ -406,7 +406,7 @@ export default {
     PhoneNumbers: "휴대전화 번호",
     TemplateCode: "템플릿 코드",
     SignName: "SignName",
-    "Sms template must contain parameters: ": "Sms 템플릿은 다음과 같은 파라미터가 포함되어야 해요:",
+    "Sms template must contain parameters: ": "SMS 템플릿은 다음과 같은 파라미터가 포함되어야 해요:",
     "Bark Endpoint": "Bark Endpoint",
     WebHookUrl: "웹훅 URL",
     SecretKey: "Secret Key",
@@ -518,14 +518,14 @@ export default {
     "Show update if available": "사용 가능한 경우에 업데이트 표시",
     "Also check beta release": "베타 릴리즈 확인",
     "Using a Reverse Proxy?": "리버스 프록시를 사용하시나요?",
-    "Check how to config it for WebSocket": "웹소켓에 대한 설정 방법 확인",
+    "Check how to config it for WebSocket": "웹소켓 대한 설정 방법",
     "Steam Game Server": "스팀 게임 서버",
     "Most likely causes:": "원인:",
-    "The resource is no longer available.": "더이상 사용할 수 없어요.",
+    "The resource is no longer available.": "더이상 사용할 수 없어요...",
     "There might be a typing error in the address.": "주소에 오탈자가 있을 수 있어요.",
     "What you can try:": "해결 방법:",
     "Retype the address.": "주소 다시 입력하기",
     "Go back to the previous page.": "이전 페이지로 돌아가기",
-    "Coming Soon": "Coming Soon",
+    "Coming Soon": "Coming Soon...",
     wayToGetClickSendSMSToken: "{0}에서 API 사용자 이름과 키를 얻을 수 있어요.",
 };

From 1062e629c525d757a747b31bf2affa7cce168010 Mon Sep 17 00:00:00 2001
From: c0derMo <jaydeveloper@outlook.de>
Date: Sun, 24 Jul 2022 12:50:43 +0000
Subject: [PATCH 74/99] Fix linting issues

---
 src/components/settings/Docker.vue | 4 +---
 src/languages/en.js                | 2 +-
 src/pages/EditMonitor.vue          | 2 +-
 3 files changed, 3 insertions(+), 5 deletions(-)

diff --git a/src/components/settings/Docker.vue b/src/components/settings/Docker.vue
index 7b99bb8b..c411c307 100644
--- a/src/components/settings/Docker.vue
+++ b/src/components/settings/Docker.vue
@@ -23,12 +23,10 @@
 
 <script>
 import DockerHostDialog from "../../components/DockerHostDialog.vue";
-import ActionInput from "../ActionInput.vue";
 
 export default {
     components: {
         DockerHostDialog,
-        ActionInput,
     },
 
     data() {
@@ -47,4 +45,4 @@ export default {
         },
     }
 };
-</script>
\ No newline at end of file
+</script>
diff --git a/src/languages/en.js b/src/languages/en.js
index 60404da2..29d479c9 100644
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -541,7 +541,7 @@ export default {
     "Docker Container": "Docker Container",
     "Container Name / ID": "Container Name / ID",
     "Docker Host": "Docker Host",
-    "Docker Hosts": "Docker Hosts"
+    "Docker Hosts": "Docker Hosts",
     "ntfy Topic": "ntfy Topic",
     "Domain": "Domain",
     "Workstation": "Workstation",
diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue
index fd8788c3..ac6a3e2e 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -159,7 +159,7 @@
                                     {{ $t("Not available, please setup.") }}
                                 </p>
 
-                                <div class="mb-3" v-else>
+                                <div v-else class="mb-3">
                                     <label for="docker-host" class="form-label">{{ $t("Docker Host") }}</label>
                                     <select id="docket-host" v-model="monitor.docker_host" class="form-select">
                                         <option v-for="host in $root.dockerHostList" :key="host.id" :value="host.id">{{ host.name }}</option>

From 75f6ff8b587adb0efd96b1f3cfd88b4b5a430524 Mon Sep 17 00:00:00 2001
From: __filename <63600304+ankhgerel@users.noreply.github.com>
Date: Mon, 25 Jul 2022 16:39:00 +0800
Subject: [PATCH 75/99] fix(locale): Edit multiple space

Co-authored-by: Kyungyoon Kim <ruddbs5302@gmail.com>
---
 src/languages/ko-KR.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/languages/ko-KR.js b/src/languages/ko-KR.js
index 4d1d6194..570dc11a 100644
--- a/src/languages/ko-KR.js
+++ b/src/languages/ko-KR.js
@@ -231,7 +231,7 @@ export default {
     "Uptime Kuma URL": "Uptime Kuma URL",
     aboutWebhooks: "Webhook에 대한 설명: {0}",
     aboutChannelName: "Webhook 채널을 무시하려면 {0} 채널 이름칸에 채널 이름을 입력해주세요. 예: #기타-채널",
-    aboutKumaURL: "Uptime Kuma URL칸을 공백으로 두면 기본적으로 Github Project  페이지로 설정해요.",
+    aboutKumaURL: "Uptime Kuma URL칸을 공백으로 두면 기본적으로 Github Project 페이지로 설정해요.",
     emojiCheatSheet: "이모지 목록 시트: {0}",
     "rocket.chat": "Rocket.chat",
     pushover: "Pushover",

From 694b4cadb33a19099451db3adbc0cd218cff4869 Mon Sep 17 00:00:00 2001
From: __filename <63600304+ankhgerel@users.noreply.github.com>
Date: Mon, 25 Jul 2022 16:41:29 +0800
Subject: [PATCH 76/99] fix(locale): Edit typo

Co-authored-by: Kyungyoon Kim <ruddbs5302@gmail.com>
---
 src/languages/ko-KR.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/languages/ko-KR.js b/src/languages/ko-KR.js
index 570dc11a..f44fbeee 100644
--- a/src/languages/ko-KR.js
+++ b/src/languages/ko-KR.js
@@ -521,7 +521,7 @@ export default {
     "Check how to config it for WebSocket": "웹소켓 대한 설정 방법",
     "Steam Game Server": "스팀 게임 서버",
     "Most likely causes:": "원인:",
-    "The resource is no longer available.": "더이상 사용할 수 없어요...",
+    "The resource is no longer available.": "더 이상 사용할 수 없어요...",
     "There might be a typing error in the address.": "주소에 오탈자가 있을 수 있어요.",
     "What you can try:": "해결 방법:",
     "Retype the address.": "주소 다시 입력하기",

From b16cb6a337610564466dd4ca1ef7ffe199af020e Mon Sep 17 00:00:00 2001
From: __filename <63600304+ankhgerel@users.noreply.github.com>
Date: Mon, 25 Jul 2022 20:11:51 +0800
Subject: [PATCH 77/99] fix(locale): Edit non-space place

Co-authored-by: Kyungyoon Kim <ruddbs5302@gmail.com>
---
 src/languages/ko-KR.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/languages/ko-KR.js b/src/languages/ko-KR.js
index f44fbeee..f181389b 100644
--- a/src/languages/ko-KR.js
+++ b/src/languages/ko-KR.js
@@ -254,7 +254,7 @@ export default {
     octopushTypePremium: "프리미엄 (빠름) - 알림 기능에 적합해요)",
     octopushTypeLowCost: "저렴한 요금 (느림) - 가끔 차단될 수 있어요)",
     "Check octopush prices": "{0}에서 Octopush 가격을 확인할 수 있어요.",
-    octopushPhoneNumber: "휴대전화 번호 (intl format, 예시: +82 1023456789) ",
+    octopushPhoneNumber: "휴대전화 번호 (intl format, 예시: +821023456789) ",
     octopushSMSSender: "보내는 사람 이름 : 3-11개의 영숫자 및 여백공간 (a-z, A-Z, 0-9)",
     "LunaSea Device ID": "LunaSea 장치 ID",
     "Apprise URL": "Apprise URL",

From d8253405b45147b6ad278ea5f5aa4c7e66149cf7 Mon Sep 17 00:00:00 2001
From: Mario Garrido <mariogarrido@7graus.com>
Date: Tue, 26 Jul 2022 02:07:38 +0100
Subject: [PATCH 78/99] fix: add language to the language list

---
 src/i18n.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/i18n.js b/src/i18n.js
index d848cf3d..8495cd99 100644
--- a/src/i18n.js
+++ b/src/i18n.js
@@ -11,6 +11,7 @@ const languageList = {
     "es-ES": "Español",
     "eu": "Euskara",
     "fa": "Farsi",
+    "pt-PT": "Português (Portugal)",
     "pt-BR": "Português (Brasileiro)",
     "fr-FR": "Français (France)",
     "hu": "Magyar",

From 4575f31094f4bdbbd8b10e3c913b783e7845a497 Mon Sep 17 00:00:00 2001
From: 0x01code <25381518+0x01code@users.noreply.github.com>
Date: Fri, 29 Jul 2022 14:13:50 +0700
Subject: [PATCH 79/99] Add support for line notify providers (#1781)

* add line notify support

* add way to get line notify

* Fix duplicate key 'HTTP Basic Auth'

* Revert language files changes

* Revert language files changes

* Fix general message

Co-authored-by: Louis Lam <louislam@users.noreply.github.com>
---
 server/notification-providers/linenotify.js | 43 +++++++++++++++++++++
 server/notification.js                      |  2 +
 src/components/notifications/LineNotify.vue |  9 +++++
 src/components/notifications/index.js       |  2 +
 src/languages/en.js                         |  1 +
 src/languages/th-TH.js                      |  1 +
 6 files changed, 58 insertions(+)
 create mode 100644 server/notification-providers/linenotify.js
 create mode 100644 src/components/notifications/LineNotify.vue

diff --git a/server/notification-providers/linenotify.js b/server/notification-providers/linenotify.js
new file mode 100644
index 00000000..8454152d
--- /dev/null
+++ b/server/notification-providers/linenotify.js
@@ -0,0 +1,43 @@
+const NotificationProvider = require("./notification-provider");
+const axios = require("axios");
+const qs = require("qs");
+const { DOWN, UP } = require("../../src/util");
+
+class LineNotify extends NotificationProvider {
+
+    name = "LineNotify";
+
+    async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
+        let okMsg = "Sent Successfully.";
+        try {
+            let lineAPIUrl = "https://notify-api.line.me/api/notify";
+            let config = {
+                headers: {
+                    "Content-Type": "application/x-www-form-urlencoded",
+                    "Authorization": "Bearer " + notification.lineNotifyAccessToken
+                }
+            };
+            if (heartbeatJSON == null) {
+                let testMessage = {
+                    "message": msg,
+                };
+                await axios.post(lineAPIUrl, qs.stringify(testMessage), config);
+            } else if (heartbeatJSON["status"] === DOWN) {
+                let downMessage = {
+                    "message": "\n[🔴 Down]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"]
+                };
+                await axios.post(lineAPIUrl, qs.stringify(downMessage), config);
+            } else if (heartbeatJSON["status"] === UP) {
+                let upMessage = {
+                    "message": "\n[✅ Up]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"]
+                };
+                await axios.post(lineAPIUrl, qs.stringify(upMessage), config);
+            }
+            return okMsg;
+        } catch (error) {
+            this.throwGeneralAxiosError(error);
+        }
+    }
+}
+
+module.exports = LineNotify;
diff --git a/server/notification.js b/server/notification.js
index a3b3a70b..ad1c8705 100644
--- a/server/notification.js
+++ b/server/notification.js
@@ -13,6 +13,7 @@ const GoogleChat = require("./notification-providers/google-chat");
 const Gorush = require("./notification-providers/gorush");
 const Gotify = require("./notification-providers/gotify");
 const Line = require("./notification-providers/line");
+const LineNotify = require("./notification-providers/linenotify");
 const LunaSea = require("./notification-providers/lunasea");
 const Matrix = require("./notification-providers/matrix");
 const Mattermost = require("./notification-providers/mattermost");
@@ -61,6 +62,7 @@ class Notification {
             new Gorush(),
             new Gotify(),
             new Line(),
+            new LineNotify(),
             new LunaSea(),
             new Matrix(),
             new Mattermost(),
diff --git a/src/components/notifications/LineNotify.vue b/src/components/notifications/LineNotify.vue
new file mode 100644
index 00000000..0f6897f4
--- /dev/null
+++ b/src/components/notifications/LineNotify.vue
@@ -0,0 +1,9 @@
+<template>
+    <div class="mb-3">
+        <label for="line-notify-access-token" class="form-label">{{ $t("Access Token") }}</label>
+        <input id="line-notify-access-token" v-model="$parent.notification.lineNotifyAccessToken" type="text" class="form-control" :required="true">
+    </div>
+    <i18n-t tag="div" keypath="wayToGetLineNotifyToken" class="form-text" style="margin-top: 8px;">
+        <a href="https://notify-bot.line.me/" target="_blank">https://notify-bot.line.me/</a>
+    </i18n-t>
+</template>
diff --git a/src/components/notifications/index.js b/src/components/notifications/index.js
index e5cbe8ce..c1b7da4a 100644
--- a/src/components/notifications/index.js
+++ b/src/components/notifications/index.js
@@ -11,6 +11,7 @@ import GoogleChat from "./GoogleChat.vue";
 import Gorush from "./Gorush.vue";
 import Gotify from "./Gotify.vue";
 import Line from "./Line.vue";
+import LineNotify from "./LineNotify.vue";
 import LunaSea from "./LunaSea.vue";
 import Matrix from "./Matrix.vue";
 import Mattermost from "./Mattermost.vue";
@@ -54,6 +55,7 @@ const NotificationFormList = {
     "gorush": Gorush,
     "gotify": Gotify,
     "line": Line,
+    "LineNotify": LineNotify,
     "lunasea": LunaSea,
     "matrix": Matrix,
     "mattermost": Mattermost,
diff --git a/src/languages/en.js b/src/languages/en.js
index 9aeedd9d..15bdcc8a 100644
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -536,4 +536,5 @@ export default {
     "Domain": "Domain",
     "Workstation": "Workstation",
     disableCloudflaredNoAuthMsg: "You are in No Auth mode, password is not require.",
+    wayToGetLineNotifyToken: "You can get a access token from {0}",
 };
diff --git a/src/languages/th-TH.js b/src/languages/th-TH.js
index a573206b..92c4eb80 100644
--- a/src/languages/th-TH.js
+++ b/src/languages/th-TH.js
@@ -518,4 +518,5 @@ export default {
     "Go back to the previous page.": "กลับไปที่หน้าก่อนหน้า",
     "Coming Soon": "เร็ว ๆ นี้",
     wayToGetClickSendSMSToken: "คุณสามารถรับ API Username และ API Key ได้จาก {0}",
+    wayToGetLineNotifyToken: "คุณสามารถรับ access token ได้จาก {0}",
 };

From f3322398e577c4ddf989daf12e2a366c6980d82b Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Fri, 29 Jul 2022 20:57:13 +0800
Subject: [PATCH 80/99] Fix and improve test docker host

---
 server/docker.js                              | 30 +++++++++++++++----
 .../socket-handlers/docker-socket-handler.js  | 14 +++++++--
 2 files changed, 35 insertions(+), 9 deletions(-)

diff --git a/server/docker.js b/server/docker.js
index ed9e0854..1c939d64 100644
--- a/server/docker.js
+++ b/server/docker.js
@@ -59,7 +59,7 @@ class DockerHost {
      * @param {Object} dockerHost Docker host to check for
      * @returns {number} Total amount of containers on the host
      */
-    static async getAmountContainer(dockerHost) {
+    static async testDockerHost(dockerHost) {
         const options = {
             url: "/containers/json?all=true",
             headers: {
@@ -72,14 +72,32 @@ class DockerHost {
             }),
         };
 
-        if (dockerHost.docker_type === "socket") {
-            options.socketPath = dockerHost.docker_daemon;
-        } else if (dockerHost.docker_type === "tcp") {
-            options.baseURL = dockerHost.docker_daemon;
+        if (dockerHost.dockerType === "socket") {
+            options.socketPath = dockerHost.dockerDaemon;
+        } else if (dockerHost.dockerType === "tcp") {
+            options.baseURL = dockerHost.dockerDaemon;
         }
 
         let res = await axios.request(options);
-        return res.data.length;
+
+        if (Array.isArray(res.data)) {
+
+            if (res.data.length > 1) {
+
+                if ("ImageID" in res.data[0]) {
+                    return res.data.length;
+                } else {
+                    throw new Error("Invalid Docker response, is it Docker really a daemon?");
+                }
+
+            } else {
+                return res.data.length;
+            }
+
+        } else {
+            throw new Error("Invalid Docker response, is it Docker really a daemon?");
+        }
+
     }
 }
 
diff --git a/server/socket-handlers/docker-socket-handler.js b/server/socket-handlers/docker-socket-handler.js
index 7f3646bb..5a53494d 100644
--- a/server/socket-handlers/docker-socket-handler.js
+++ b/server/socket-handlers/docker-socket-handler.js
@@ -1,6 +1,7 @@
 const { sendDockerHostList } = require("../client");
 const { checkLogin } = require("../util-server");
 const { DockerHost } = require("../docker");
+const { log } = require("../../src/util");
 
 /**
  * Handlers for docker hosts
@@ -52,15 +53,22 @@ module.exports.dockerSocketHandler = (socket) => {
         try {
             checkLogin(socket);
 
-            let amount = await DockerHost.getAmountContainer(dockerHost);
+            let amount = await DockerHost.testDockerHost(dockerHost);
+            let msg;
+
+            if (amount > 1) {
+                msg = "Connected Successfully. Amount of containers: " + amount;
+            } else {
+                msg = "Connected Successfully, but there are no containers?";
+            }
 
             callback({
                 ok: true,
-                msg: "Amount of containers: " + amount,
+                msg,
             });
 
         } catch (e) {
-            console.error(e);
+            log.error("docker", e);
 
             callback({
                 ok: false,

From 8ced61697aea943aa0e25ff52f6b705715614242 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Sat, 30 Jul 2022 19:48:12 +0800
Subject: [PATCH 81/99] Fix save docker host issue

---
 server/client.js                    |  2 +-
 server/docker.js                    |  4 ++--
 server/model/docker_host.js         | 10 +++++-----
 src/components/DockerHostDialog.vue | 11 ++++++++++-
 4 files changed, 18 insertions(+), 9 deletions(-)

diff --git a/server/client.js b/server/client.js
index 69892e66..a0c52e1e 100644
--- a/server/client.js
+++ b/server/client.js
@@ -139,7 +139,7 @@ async function sendDockerHostList(socket) {
     ]);
 
     for (let bean of list) {
-        result.push(bean.export());
+        result.push(bean.toJSON());
     }
 
     io.to(socket.userID).emit("dockerHostList", result);
diff --git a/server/docker.js b/server/docker.js
index 1c939d64..177fa6cb 100644
--- a/server/docker.js
+++ b/server/docker.js
@@ -26,8 +26,8 @@ class DockerHost {
         }
 
         bean.user_id = userID;
-        bean.docker_daemon = dockerHost.docker_daemon;
-        bean.docker_type = dockerHost.docker_type;
+        bean.docker_daemon = dockerHost.dockerDaemon;
+        bean.docker_type = dockerHost.dockerType;
         bean.name = dockerHost.name;
 
         await R.store(bean);
diff --git a/server/model/docker_host.js b/server/model/docker_host.js
index 229a9a52..20598292 100644
--- a/server/model/docker_host.js
+++ b/server/model/docker_host.js
@@ -7,11 +7,11 @@ class DockerHost extends BeanModel {
      */
     toJSON() {
         return {
-            id: this._id,
-            userId: this._user_id,
-            daemon: this._dockerDaemon,
-            type: this._dockerType,
-            name: this._name,
+            id: this.id,
+            userID: this.user_id,
+            dockerDaemon: this.docker_daemon,
+            dockerType: this.docker_type,
+            name: this.name,
         };
     }
 }
diff --git a/src/components/DockerHostDialog.vue b/src/components/DockerHostDialog.vue
index d7cf2de0..681cc244 100644
--- a/src/components/DockerHostDialog.vue
+++ b/src/components/DockerHostDialog.vue
@@ -52,8 +52,9 @@
 
 <script lang="ts">
 import { Modal } from "bootstrap";
-
 import Confirm from "./Confirm.vue";
+import { useToast } from "vue-toastification";
+const toast = useToast();
 
 export default {
     components: {
@@ -88,14 +89,22 @@ export default {
 
         show(dockerHostID) {
             if (dockerHostID) {
+                let found = false;
+
                 this.id = dockerHostID;
 
                 for (let n of this.$root.dockerHostList) {
                     if (n.id === dockerHostID) {
                         this.dockerHost = n;
+                        found = true;
                         break;
                     }
                 }
+
+                if (!found) {
+                    toast.error("Docker Host not found!");
+                }
+
             } else {
                 this.id = null;
                 this.dockerHost = {

From 1223b56205c813301c5d0a476702ccc148c474a5 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Sat, 30 Jul 2022 19:57:51 +0800
Subject: [PATCH 82/99] Add example

---
 src/components/DockerHostDialog.vue | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/src/components/DockerHostDialog.vue b/src/components/DockerHostDialog.vue
index 681cc244..92a8ce45 100644
--- a/src/components/DockerHostDialog.vue
+++ b/src/components/DockerHostDialog.vue
@@ -25,6 +25,14 @@
                         <div class="mb-3">
                             <label for="docker-daemon" class="form-label">{{ $t("Docker Daemon") }}</label>
                             <input id="docker-daemon" v-model="dockerHost.dockerDaemon" type="text" class="form-control" required>
+
+                            <div class="form-text">
+                                {{ $t("Examples") }}:
+                                <ul>
+                                    <li>/var/run/docker.sock</li>
+                                    <li>tcp://localhost:2375</li>
+                                </ul>
+                            </div>
                         </div>
                     </div>
 

From 4e4156285a679dd21b8253dad24d165e0c6f3434 Mon Sep 17 00:00:00 2001
From: dtorner <dtorner@users.noreply.github.com>
Date: Sun, 31 Jul 2022 11:13:36 +0200
Subject: [PATCH 83/99] Updated ES tramaslation

Just some changes here and there, mainly caps and some words.
TY to the developers for this good piece of code :-)
---
 src/languages/es-ES.js | 60 +++++++++++++++++++++---------------------
 1 file changed, 30 insertions(+), 30 deletions(-)

diff --git a/src/languages/es-ES.js b/src/languages/es-ES.js
index 31538295..51327740 100644
--- a/src/languages/es-ES.js
+++ b/src/languages/es-ES.js
@@ -7,8 +7,8 @@ export default {
     maxRedirectDescription: "Número máximo de direcciones a seguir. Establecer a 0 para deshabilitar.",
     acceptedStatusCodesDescription: "Seleccionar los códigos de estado que se consideran como respuesta exitosa.",
     passwordNotMatchMsg: "La contraseña repetida no coincide.",
-    notificationDescription: "Por favor asigne una notificación a el/los monitor(es) para hacerlos funcional(es).",
-    keywordDescription: "Palabra clave en HTML plano o respuesta JSON y es sensible a mayúsculas",
+    notificationDescription: "Por favor asigna una notificación a el/los monitor(es) para hacerlos funcional(es).",
+    keywordDescription: "Palabra clave en HTML plano o respuesta JSON, es sensible a mayúsculas",
     pauseDashboardHome: "Pausado",
     deleteMonitorMsg: "¿Seguro que quieres eliminar este monitor?",
     deleteNotificationMsg: "¿Seguro que quieres eliminar esta notificación para todos los monitores?",
@@ -35,7 +35,7 @@ export default {
     Pause: "Pausar",
     Name: "Nombre",
     Status: "Estado",
-    DateTime: "Fecha y Hora",
+    DateTime: "Fecha y hora",
     Message: "Mensaje",
     "No important events": "No hay eventos importantes",
     Resume: "Reanudar",
@@ -50,7 +50,7 @@ export default {
     "-hour": "-hora",
     Response: "Respuesta",
     Ping: "Ping",
-    "Monitor Type": "Tipo de Monitor",
+    "Monitor Type": "Tipo de monitor",
     Keyword: "Palabra clave",
     "Friendly Name": "Nombre sencillo",
     URL: "URL",
@@ -60,11 +60,11 @@ export default {
     Retries: "Reintentos",
     Advanced: "Avanzado",
     "Upside Down Mode": "Modo invertido",
-    "Max. Redirects": "Redirecciones Máximas",
+    "Max. Redirects": "Redirecciones máximas",
     "Accepted Status Codes": "Códigos de estado aceptados",
     Save: "Guardar",
     Notifications: "Notificaciones",
-    "Not available, please setup.": "No disponible, por favor configúrelo.",
+    "Not available, please setup.": "No disponible, por favor configúralo.",
     "Setup Notification": "Configurar notificación",
     Light: "Claro",
     Dark: "Oscuro",
@@ -82,8 +82,8 @@ export default {
     "New Password": "Nueva contraseña",
     "Repeat New Password": "Repetir nueva contraseña",
     "Update Password": "Actualizar contraseña",
-    "Disable Auth": "Deshabilitar Autenticación",
-    "Enable Auth": "Habilitar Autenticación",
+    "Disable Auth": "Deshabilitar autenticación",
+    "Enable Auth": "Habilitar autenticación",
     "disableauth.message1": "Seguro que deseas <strong>deshabilitar la autenticación</strong>?",
     "disableauth.message2": "Es para <strong>quien implementa autenticación de terceros</strong> ante Uptime Kuma como por ejemplo Cloudflare Access.",
     "Please use this option carefully!": "Por favor usar con cuidado.",
@@ -104,32 +104,32 @@ export default {
     Test: "Test",
     "Certificate Info": "Información del certificado",
     "Resolver Server": "Servidor de resolución",
-    "Resource Record Type": "Tipo de Registro",
+    "Resource Record Type": "Tipo de registro",
     "Last Result": "Último resultado",
     "Create your admin account": "Crea tu cuenta de administrador",
     "Repeat Password": "Repetir contraseña",
     respTime: "Tiempo de resp. (ms)",
     notAvailableShort: "N/A",
     Create: "Crear",
-    clearEventsMsg: "¿Está seguro de que desea eliminar todos los eventos de este monitor?",
-    clearHeartbeatsMsg: "¿Está seguro de que desea eliminar todos los latidos de este monitor?",
-    confirmClearStatisticsMsg: "¿Está seguro de que desea eliminar TODAS las estadísticas?",
-    "Clear Data": "Borrar Datos",
+    clearEventsMsg: "¿Estás seguro de que deseas eliminar todos los eventos de este monitor?",
+    clearHeartbeatsMsg: "¿Estás seguro de que deseas eliminar todos los latidos de este monitor?",
+    confirmClearStatisticsMsg: "¿Estás seguro de que deseas eliminar TODAS las estadísticas?",
+    "Clear Data": "Borrar datos",
     Events: "Eventos",
     Heartbeats: "Latidos",
     "Auto Get": "Obtener automáticamente",
-    enableDefaultNotificationDescription: "Para cada nuevo monitor, esta notificación estará habilitada de forma predeterminada. Aún puede deshabilitar la notificación por separado para cada monitor.",
+    enableDefaultNotificationDescription: "Para cada nuevo monitor, esta notificación estará habilitada de forma predeterminada. Aún puedes deshabilitar la notificación por separado para cada monitor.",
     "Default enabled": "Habilitado por defecto",
     "Also apply to existing monitors": "También se aplica a monitores existentes",
     Export: "Exportar",
     Import: "Importar",
-    backupDescription: "Puede hacer una copia de seguridad de todos los monitores y todas las notificaciones en un archivo JSON.",
+    backupDescription: "Puedes hacer una copia de seguridad de todos los monitores y todas las notificaciones en un archivo JSON.",
     backupDescription2: "PD: el historial y los datos de eventos no están incluidos.",
-    backupDescription3: "Los datos confidenciales, como los tokens de notificación, se incluyen en el archivo de exportación. Guárdelo con cuidado.",
-    alertNoFile: "Seleccione un archivo para importar.",
-    alertWrongFileType: "Seleccione un archivo JSON.",
-    twoFAVerifyLabel: "Ingrese su token para verificar que 2FA está funcionando",
-    tokenValidSettingsMsg: "¡El token es válido! Ahora puede guardar la configuración de 2FA.",
+    backupDescription3: "Los datos confidenciales, como los tokens de notificación, se incluyen en el archivo de exportación. Guárdalo con cuidado.",
+    alertNoFile: "Selecciona un archivo para importar.",
+    alertWrongFileType: "Selecciona un archivo JSON.",
+    twoFAVerifyLabel: "Ingresa tu token para verificar que 2FA está funcionando",
+    tokenValidSettingsMsg: "¡El token es válido! Ahora puedes guardar la configuración de 2FA.",
     confirmEnableTwoFAMsg: "¿Estás seguro de que quieres habilitar 2FA?",
     confirmDisableTwoFAMsg: "¿Estás seguro de que quieres desactivar 2FA?",
     "Apply on all existing monitors": "Aplicar en todos los monitores existentes",
@@ -145,19 +145,19 @@ export default {
     "Show URI": "Mostrar URI",
     "Clear all statistics": "Borrar todas las estadísticas",
     retryCheckEverySecond: "Reintentar cada {0} segundo.",
-    importHandleDescription: "Elija 'Omitir existente' si desea omitir todos los monitores o notificaciones con el mismo nombre. 'Sobrescribir' eliminará todos los monitores y notificaciones existentes.",
-    confirmImportMsg: "¿Estás seguro de importar la copia de seguridad? Asegúrese de haber seleccionado la opción de importación correcta.",
+    importHandleDescription: "Elige 'Omitir existente' si deseas omitir todos los monitores o notificaciones con el mismo nombre. 'Sobrescribir' eliminará todos los monitores y notificaciones existentes.",
+    confirmImportMsg: "¿Estás seguro de importar la copia de seguridad? Asegúrate de haber seleccionado la opción de importación correcta.",
     "Heartbeat Retry Interval": "Intervalo de reintento de latido",
     "Import Backup": "Importar copia de seguridad",
     "Export Backup": "Exportar copia de seguridad",
     "Skip existing": "Omitir existente",
     Overwrite: "Sobrescribir",
     Options: "Opciones",
-    "Keep both": "Mantén ambos",
+    "Keep both": "Manténer ambos",
     Tags: "Etiquetas",
-    "Add New below or Select...": "Agregar nuevo a continuación o Seleccionar...",
-    "Tag with this name already exist.": "La etiqueta con este nombre ya existe.",
-    "Tag with this value already exist.": "La etiqueta con este valor ya existe.",
+    "Add New below or Select...": "Agregar nuevo a continuación o seleccionar...",
+    "Tag with this name already exist.": "Una etiqueta con este nombre ya existe.",
+    "Tag with this value already exist.": "Una etiqueta con este valor ya existe.",
     color: "color",
     "value (optional)": "valor (opcional)",
     Gray: "Gris",
@@ -172,17 +172,17 @@ export default {
     "Avg. Ping": "Ping promedio",
     "Avg. Response": "Respuesta promedio",
     "Entry Page": "Página de entrada",
-    statusPageNothing: "No hay nada aquí, agregue un grupo o un monitor.",
+    statusPageNothing: "No hay nada aquí, agrega un grupo o un monitor.",
     "No Services": "Sin servicio",
     "All Systems Operational": "Todos los sistemas están operativos",
     "Partially Degraded Service": "Servicio parcialmente degradado",
     "Degraded Service": "Servicio degradado",
-    "Add Group": "Agregar Grupo",
+    "Add Group": "Agregar grupo",
     "Add a monitor": "Agregar un monitor",
     "Edit Status Page": "Editar página de estado",
     "Go to Dashboard": "Ir al panel de control",
     "Status Page": "Página de estado",
-    "Status Pages": "Página de estado",
+    "Status Pages": "Páginas de estado",
     telegram: "Telegram",
     webhook: "Webhook",
     smtp: "Email (SMTP)",
@@ -205,5 +205,5 @@ export default {
     clearDataOlderThan: "Mantener los datos del historial del monitor durante {0} días.",
     records: "registros",
     "One record": "Un registro",
-    steamApiKeyDescription: "Para monitorear un servidor de juegos de Steam, necesita una clave Steam Web-API. Puede registrar su clave API aquí: ",
+    steamApiKeyDescription: "Para monitorear un servidor de juegos de Steam, necesitas una clave Steam Web-API. Puedes registrar tu clave API aquí: ",
 };

From ffb1a948feedc4e1919fd145b187fe31fa1db8e3 Mon Sep 17 00:00:00 2001
From: MrEddX <66828538+MrEddX@users.noreply.github.com>
Date: Fri, 29 Jul 2022 21:04:33 +0300
Subject: [PATCH 84/99] Update bg-BG.js

Translation Update
---
 src/languages/bg-BG.js | 142 +++++++++--------------------------------
 1 file changed, 29 insertions(+), 113 deletions(-)

diff --git a/src/languages/bg-BG.js b/src/languages/bg-BG.js
index b2c185d9..15d330c1 100644
--- a/src/languages/bg-BG.js
+++ b/src/languages/bg-BG.js
@@ -12,15 +12,15 @@ export default {
     keywordDescription: "Търси ключова дума в чист html или JSON отговор - чувствителна е към регистъра",
     pauseDashboardHome: "Пауза",
     deleteMonitorMsg: "Наистина ли желаете да изтриете този монитор?",
-    deleteNotificationMsg: "Наистина ли желаете да изтриете това известие за всички монитори?",
+    deleteNotificationMsg: "Наистина ли желаете да изтриете това известяване за всички монитори?",
     resolverserverDescription: "Cloudflare е сървърът по подразбиране, но можете да го промените по всяко време.",
     rrtypeDescription: "Изберете ресурсния запис, който желаете да наблюдавате",
     pauseMonitorMsg: "Наистина ли желаете да поставите в режим пауза?",
-    enableDefaultNotificationDescription: "За всеки нов монитор това известие ще бъде активирано по подразбиране. Можете да го изключите за всеки отделен монитор.",
+    enableDefaultNotificationDescription: "За всеки нов монитор това известяване ще бъде активирано по подразбиране. Можете да го изключите за всеки отделен монитор.",
     clearEventsMsg: "Наистина ли желаете да изтриете всички събития за този монитор?",
     clearHeartbeatsMsg: "Наистина ли желаете да изтриете всички записи за честотни проверки на този монитор?",
     confirmClearStatisticsMsg: "Наистина ли желаете да изтриете всички статистически данни?",
-    importHandleDescription: "Изберете 'Пропусни съществуващите', ако желаете да пропуснете всеки монитор или известие със същото име. 'Презапис' ще изтрие всеки съществуващ монитор и известие.",
+    importHandleDescription: "Изберете 'Пропусни съществуващите', ако желаете да пропуснете всеки монитор или известяване със същото име. 'Презапис' ще изтрие всеки съществуващ монитор и известяване.",
     confirmImportMsg: "Сигурни ли сте, че желаете импортирането на архива? Моля, уверете се, че сте избрали правилната опция за импортиране.",
     twoFAVerifyLabel: "Моля, въведете вашия токен код, за да проверите дали 2FA работи",
     tokenValidSettingsMsg: "Токен кодът е валиден! Вече можете да запазите настройките за 2FA.",
@@ -55,7 +55,8 @@ export default {
     Current: "Текущ",
     Uptime: "Достъпност",
     "Cert Exp.": "Вал. сертификат",
-    day: "ден | дни",
+    days: "дни",
+    day: "ден",
     "-day": "-дни",
     hour: "час",
     "-hour": "-часa",
@@ -75,9 +76,9 @@ export default {
     "Max. Redirects": "Макс. брой пренасочвания",
     "Accepted Status Codes": "Допустими статус кодове",
     Save: "Запази",
-    Notifications: "Известия",
+    Notifications: "Известявания",
     "Not available, please setup.": "Не са налични. Моля, настройте.",
-    "Setup Notification": "Настрой известие",
+    "Setup Notification": "Настройки за известявания",
     Light: "Светла",
     Dark: "Тъмна",
     Auto: "Автоматично",
@@ -89,16 +90,13 @@ export default {
     "Search Engine Visibility": "Видимост за търсачки",
     "Allow indexing": "Разреши индексиране",
     "Discourage search engines from indexing site": "Не позволявай на търсачките да индексират този сайт",
-    "Change Password": "Промяна на парола",
+    "Change Password": "Промени парола",
     "Current Password": "Текуща парола",
     "New Password": "Нова парола",
     "Repeat New Password": "Повторете новата парола",
-    "Update Password": "Актуализирай паролата",
+    "Update Password": "Актуализирай парола",
     "Disable Auth": "Изключи удостоверяване",
     "Enable Auth": "Включи удостоверяване",
-    "disableauth.message1": "Сигурни ли сте, че желаете да <strong>изключите удостоверяването</strong>?",
-    "disableauth.message2": "Използва се в случаите, когато <strong>има настроен алтернативен метод за удостоверяване</strong> преди Uptime Kuma, например Cloudflare Access, Authelia или друг механизъм за удостоверяване.",
-    "Please use this option carefully!": "Моля, използвайте с повишено внимание.",
     Logout: "Изход от профила",
     Leave: "Отказ",
     "I understand, please disable": "Разбирам. Моля, изключи",
@@ -111,7 +109,7 @@ export default {
     Login: "Вход",
     "No Monitors, please": "Все още няма монитори. Моля, добавете поне ",
     "add one": "един.",
-    "Notification Type": "Тип известие",
+    "Notification Type": "Тип известяване",
     Email: "Имейл",
     Test: "Тест",
     "Certificate Info": "Информация за сертификат",
@@ -133,9 +131,9 @@ export default {
     Events: "Събития",
     Heartbeats: "Проверки",
     "Auto Get": "Авт. попълване",
-    backupDescription: "Можете да архивирате всички монитори и всички известия в JSON файл.",
+    backupDescription: "Можете да архивирате всички монитори и всички известявания в JSON файл.",
     backupDescription2: "PS: Имайте предвид, че данните за история и събития няма да бъдат включени.",
-    backupDescription3: "Чувствителни данни, като токен кодове за известия, се съдържат в експортирания файл. Моля, бъдете внимателни с неговото съхранение.",
+    backupDescription3: "Чувствителни данни, като токен кодове за известяване, се съдържат в експортирания файл. Моля, бъдете внимателни с неговото съхранение.",
     alertNoFile: "Моля, изберете файл за импортиране.",
     alertWrongFileType: "Моля, изберете JSON файл.",
     "Clear all statistics": "Изтрий цялата статистика",
@@ -147,7 +145,7 @@ export default {
     "Setup 2FA": "Настройка 2FA",
     "Enable 2FA": "Включи 2FA",
     "Disable 2FA": "Изключи 2FA",
-    "2FA Settings": "Настройка за 2FA",
+    "2FA Settings": "Настройки 2FA",
     "Two Factor Authentication": "Двуфакторно удостоверяване",
     Active: "Активно",
     Inactive: "Неактивно",
@@ -204,7 +202,7 @@ export default {
     "Push URL": "Генериран Push URL адрес",
     needPushEvery: "Необходимо е да извършвате заявка към този URL адрес на всеки {0} секунди",
     pushOptionalParams: "Допълнителни, но не задължителни параметри: {0}",
-    defaultNotificationName: "Моето {notification} известие ({number})",
+    defaultNotificationName: "Моето {notification} известяване ({number})",
     here: "тук",
     Required: "Задължително поле",
     "Bot Token": "Бот токен",
@@ -254,7 +252,7 @@ export default {
     "Notification Sound": "Звуков сигнал",
     "More info on:": "Повече информация на: {0}",
     pushoverDesc1: "Приоритет Спешно (2) по подразбиране изчаква 30 секунди между повторните опити и изтича след 1 час.",
-    pushoverDesc2: "Ако желаете да изпратите известия до различни устройства, попълнете полето Устройство.",
+    pushoverDesc2: "Ако желаете да изпратите известявания до различни устройства, попълнете полето Устройство.",
     "SMS Type": "SMS тип",
     octopushTypePremium: "Премиум (Бърз - препоръчителен в случай на тревога)",
     octopushTypeLowCost: "Евтин (Бавен - понякога бива блокиран от оператора)",
@@ -277,7 +275,7 @@ export default {
     lineDevConsoleTo: "Line - Конзола за разработчици - {0}",
     "Basic Settings": "Основни настройки",
     "User ID": "Потребител ID",
-    "Messaging API": "API за съобщаване",
+    "Messaging API": "API за известяване",
     wayToGetLineChannelToken: "Необходимо е първо да посетите {0}, за да създадете (Messaging API) за доставчик и канал, след което може да вземете токен кода за канал и потребителско ID от споменатите по-горе елементи на менюто.",
     "Icon URL": "URL адрес за иконка",
     aboutIconURL: "Може да предоставите линк към картинка в поле \"URL Адрес за иконка\" за да отмените картинката на профила по подразбиране. Няма да се използва, ако вече сте настроили емотикон.",
@@ -293,7 +291,7 @@ export default {
     matrixHomeserverURL: "Сървър URL адрес (започва с http(s):// и порт по желание)",
     "Internal Room Id": "ID на вътрешна стая",
     matrixDesc1: "Може да намерите \"ID на вътрешна стая\" в разширените настройки на стаята във вашия Matrix клиент. Примерен изглед: !QMdRCpUIfLwsfjxye6:home.server.",
-    matrixDesc2: "Силно препоръчваме да създадете НОВ потребител и да НЕ използвате токен кодът на вашия личен Matrix потребирел, т.к. той позволява пълен достъп до вашия акаунт и всички стаи към които сте се присъединили. Вместо това създайте нов потребител и го поканете само в стаята, където желаете да получавате известията. Токен код за достъп ще получите изпълнявайки {0}",
+    matrixDesc2: "Силно препоръчваме да създадете НОВ потребител и да НЕ използвате токен кодът на вашия личен Matrix потребирел, т.к. той позволява пълен достъп до вашия акаунт и всички стаи към които сте се присъединили. Вместо това създайте нов потребител и го поканете само в стаята, където желаете да получавате известяванията. Токен код за достъп ще получите изпълнявайки {0}",
     Method: "Метод",
     Body: "Съобщение",
     Headers: "Хедъри",
@@ -301,7 +299,7 @@ export default {
     HeadersInvalidFormat: "Заявените хедъри не са валидни JSON: ",
     BodyInvalidFormat: "Заявеното съобщение не е валиден JSON: ",
     "Monitor History": "История на мониторите",
-    clearDataOlderThan: "Ще се съхранява за {0} дни.",
+    clearDataOlderThan: "Ще се съхранява {0} дни.",
     records: "записа",
     "One record": "Един запис",
     steamApiKeyDescription: "За да мониторирате Steam Gameserver се нуждаете от Steam Web-API ключ. Може да регистрирате Вашия API ключ тук: ",
@@ -310,12 +308,12 @@ export default {
     PasswordsDoNotMatch: "Паролите не съвпадат.",
     "Current User": "Текущ потребител",
     recent: "Скорошни",
-    shrinkDatabaseDescription: "Инициира \"VACUUM\" за \"SQLite\" база данни. Ако Вашата база данни е създадена след версия 1.10.0, \"AUTO_VACUUM\" функцията е активна и това действие не е нужно.",
+    shrinkDatabaseDescription: "Инициира \"VACUUM\" за \"SQLite\" база данни. Ако Вашата база данни е създадена след версия 1.10.0, \"AUTO_VACUUM\" функцията е активна и това действие не нужно.",
     Done: "Готово",
     Info: "Информация",
     Security: "Сигурност",
     "Steam API Key": "Steam API ключ",
-    "Shrink Database": "Редуцирай базата данни",
+    "Shrink Database": "Редуциране база данни",
     "Pick a RR-Type...": "Изберете вида на ресурсния запис за мониторитане...",
     "Pick Accepted Status Codes...": "Изберете статус кодове, които да се считат за успешен отговор...",
     Default: "По подразбиране",
@@ -355,8 +353,8 @@ export default {
     serwersmsSenderName: "SMS Подател име (регистриран през клиентския портал)",
     stackfield: "Stackfield",
     smtpDkimSettings: "DKIM Настройки",
-    smtpDkimDesc: "Моля, вижте {0} на Nodemailer DKIM за инструкции.",
-    documentation: "документацията",
+    smtpDkimDesc: "Моля, вижте Nodemailer DKIM {0} за инструкции.",
+    documentation: "документация",
     smtpDkimDomain: "Домейн",
     smtpDkimKeySelector: "Селектор на ключ",
     smtpDkimPrivateKey: "Частен ключ",
@@ -373,12 +371,12 @@ export default {
     alertaAlertState: "Състояние на тревога",
     alertaRecoverState: "Състояние на възстановяване",
     deleteStatusPageMsg: "Сигурни ли сте, че желаете да изтриете тази статус страница?",
-    Proxies: "Прокси",
+    Proxies: "Проксита",
     default: "По подразбиране",
     enabled: "Включено",
     setAsDefault: "Зададен по подразбиране",
     deleteProxyMsg: "Сигурни ли сте, че желаете да изтриете това прокси за всички монитори?",
-    proxyDescription: "За да функционират трябва да бъдат зададени към монитор.",
+    proxyDescription: "Прокситата трябва да бъдат зададени към монитор за да функционират.",
     enableProxyDescription: "Това прокси няма да има ефект върху заявките за мониторинг, докато не бъде активирано. Може да контролирате временното деактивиране на проксито от всички монитори чрез статуса на активиране.",
     setAsDefaultProxyDescription: "Това проки ще бъде включено по подразбиране за новите монитори. Може да го изключите по отделно за всеки един монитор.",
     "Certificate Chain": "Верига на сертификата",
@@ -403,7 +401,7 @@ export default {
     Retry: "Повтори",
     Topic: "Тема",
     "WeCom Bot Key": "WeCom бот ключ",
-    "Setup Proxy": "Настрой прокси",
+    "Setup Proxy": "Настройка за прокси",
     "Proxy Protocol": "Прокси протокол",
     "Proxy Server": "Прокси сървър",
     "Proxy server has authentication": "Прокси сървърът е с удостоверяване",
@@ -413,8 +411,8 @@ export default {
     Running: "Работи",
     "Not running": "Не работи",
     "Remove Token": "Премахни токен",
-    Start: "Стартирай",
-    Stop: "Спри",
+    Start: "Старт",
+    Stop: "Стоп",
     "Uptime Kuma": "Uptime Kuma",
     "Add New Status Page": "Добави нова статус страница",
     Slug: "Слъг",
@@ -424,7 +422,6 @@ export default {
     Next: "Следващ",
     "The slug is already taken. Please choose another slug.": "Този слъг вече се използва. Моля изберете друг.",
     "No Proxy": "Без прокси",
-    Authentication: "Удостоверяване",
     "HTTP Basic Auth": "HTTP основно удостоверяване",
     "New Status Page": "Нова статус страница",
     "Page Not Found": "Страницата не е открита",
@@ -445,14 +442,7 @@ export default {
     "Issuer:": "Издател:",
     "Fingerprint:": "Пръстов отпечатък:",
     "No status pages": "Няма статус страници",
-    topic: "Тема",
-    topicExplanation: "MQTT тема за мониториране",
-    successMessage: "Съобщение при успех",
-    successMessageExplanation: "MQTT съобщение, което ще бъде считано за успех",
-    Customize: "Персонализирай",
-    "Custom Footer": "Персонализиран долен колонтитул",
-    "Custom CSS": "Потребителски CSS",
-    "Domain Name Expiry Notification": "Известие при изтичащ домейн",
+    "Domain Name Expiry Notification": "Известие за изтичащ домейн",
     Proxy: "Прокси",
     "Date Created": "Дата на създаване",
     onebotHttpAddress: "OneBot HTTP адрес",
@@ -460,80 +450,6 @@ export default {
     onebotGroupMessage: "Група",
     onebotPrivateMessage: "Лично",
     onebotUserOrGroupId: "Група/Потребител ID",
-    onebotSafetyTips: "С цел безопасност трябва да зададете токен код за достъп",
+    onebotSafetyTips: "С цел сигурност, трябва да зададете токен код за достъп",
     "PushDeer Key": "PushDeer ключ",
-    "Footer Text": "Текст долен колонтитул",
-    "Show Powered By": "Покажи \"Създадено чрез\"",
-    "Domain Names": "Домейни",
-    signedInDisp: "Вписан като {0}",
-    signedInDispDisabled: "Удостоверяването е изключено.",
-    "Certificate Expiry Notification": "Известие за изтичане валидността на сертификата",
-    "API Username": "API Потребител",
-    "API Key": "API Ключ",
-    "Recipient Number": "Номер на получателя",
-    "From Name/Number": "От Име/Номер",
-    "Leave blank to use a shared sender number.": "Оставете празно, за да използвате споделен номер на подател.",
-    "Octopush API Version": "Octopush API версия",
-    "Legacy Octopush-DM": "Octopush-DM старa версия",
-    endpoint: "крайна точка",
-    octopushAPIKey: "\"API ключ\" от HTTP API удостоверяване в контролния панел",
-    octopushLogin: "\"Вписване\" от HTTP API удостоверяване в контролния панел",
-    promosmsLogin: "API Потребителско име",
-    promosmsPassword: "API Парола",
-    "pushoversounds pushover": "Pushover (по подразбиране)",
-    "pushoversounds bike": "Велосипед",
-    "pushoversounds bugle": "Тромпет",
-    "pushoversounds cashregister": "Касов апарат",
-    "pushoversounds classical": "Класическа музика",
-    "pushoversounds cosmic": "Космически",
-    "pushoversounds falling": "Падащ",
-    "pushoversounds gamelan": "Игра в мрежа",
-    "pushoversounds incoming": "Входящ",
-    "pushoversounds intermission": "Прекъсване",
-    "pushoversounds magic": "Магия",
-    "pushoversounds mechanical": "Механичен",
-    "pushoversounds pianobar": "Пиано бар",
-    "pushoversounds siren": "Сирена",
-    "pushoversounds spacealarm": "Космическа аларма",
-    "pushoversounds tugboat": "Буксир",
-    "pushoversounds alien": "Извънземна аларма (дълъг)",
-    "pushoversounds climb": "Изкачване (дълъг)",
-    "pushoversounds persistent": "Постоянен (дълъг)",
-    "pushoversounds echo": "Pushover ехо (дълъг)",
-    "pushoversounds updown": "Горе долу (дълъг)",
-    "pushoversounds vibrate": "Само вибрация",
-    "pushoversounds none": "Без (тих)",
-    pushyAPIKey: "Таен API ключ",
-    pushyToken: "Токен на устройство",
-    "Show update if available": "Покажи актуализация, ако е налична",
-    "Also check beta release": "Проверявай и за бета версии",
-    "Using a Reverse Proxy?": "Използвате ревърс прокси?",
-    "Check how to config it for WebSocket": "Проверете как да го конфигурирате за WebSocket",
-    "Steam Game Server": "Steam Game сървър",
-    "Most likely causes:": "Най-вероятни причини:",
-    "The resource is no longer available.": "Ресурсът вече не е наличен.",
-    "There might be a typing error in the address.": "Възможно е да е допусната грешка при изписването на адреса.",
-    "What you can try:": "Може да опитате:",
-    "Retype the address.": "Повторно въвеждане на адреса.",
-    "Go back to the previous page.": "Да се върнете към предишната страница.",
-    "Coming Soon": "Очаквайте скоро",
-    wayToGetClickSendSMSToken: "Може да получите API потребителско име и API ключ от {0} .",
-    dnsPortDescription: "DNS порт на сървъра. По подразбиране е 53, но може да бъде променен по всяко време.",
-    error: "грешка",
-    critical: "критично",
-    wayToGetPagerDutyKey: "Може да го получите като посетите Service -> Service Directory -> (Select a service) -> Integrations -> Add integration. Тук трябва да потърсите \"Events API V2\". Повече информация {0}",
-    "Integration Key": "Ключ за интегриране",
-    "Integration URL": "URL адрес за интеграция",
-    "Auto resolve or acknowledged": "Автоматично разрешаване или потвърждаване",
-    "do nothing": "не прави нищо",
-    "auto acknowledged": "автоматично потвърждаване",
-    "auto resolve": "автоматично разрешаване",
-    "Connection String": "Стринг за връзка",
-    Query: "Заявка",
-    settingsCertificateExpiry: "Изтичане валидността на TLS сертификата",
-    certificationExpiryDescription: "HTTPS мониторите ще задействат известие, ако е наличен изтичащ TLS сертификат, през следващите:",
-    "ntfy Topic": "ntfy Тема",
-    Domain: "Домейн",
-    Workstation: "Работна станция",
-    disableCloudflaredNoAuthMsg: "Тъй като сте в режим \"No Auth mode\", парола не се изисква.",
 };

From 96971f6776ecc800cab1589888e6463471ed470e Mon Sep 17 00:00:00 2001
From: MrEddX <66828538+MrEddX@users.noreply.github.com>
Date: Fri, 29 Jul 2022 21:23:41 +0300
Subject: [PATCH 85/99] Update bg-BG.js

- Translation Update
- Fixed Some Accidentally Deleted Fields
---
 src/languages/bg-BG.js | 143 ++++++++++++++++++++++++++++++++---------
 1 file changed, 114 insertions(+), 29 deletions(-)

diff --git a/src/languages/bg-BG.js b/src/languages/bg-BG.js
index 15d330c1..56035295 100644
--- a/src/languages/bg-BG.js
+++ b/src/languages/bg-BG.js
@@ -12,15 +12,15 @@ export default {
     keywordDescription: "Търси ключова дума в чист html или JSON отговор - чувствителна е към регистъра",
     pauseDashboardHome: "Пауза",
     deleteMonitorMsg: "Наистина ли желаете да изтриете този монитор?",
-    deleteNotificationMsg: "Наистина ли желаете да изтриете това известяване за всички монитори?",
+    deleteNotificationMsg: "Наистина ли желаете да изтриете това известие за всички монитори?",
     resolverserverDescription: "Cloudflare е сървърът по подразбиране, но можете да го промените по всяко време.",
     rrtypeDescription: "Изберете ресурсния запис, който желаете да наблюдавате",
     pauseMonitorMsg: "Наистина ли желаете да поставите в режим пауза?",
-    enableDefaultNotificationDescription: "За всеки нов монитор това известяване ще бъде активирано по подразбиране. Можете да го изключите за всеки отделен монитор.",
+    enableDefaultNotificationDescription: "За всеки нов монитор това известие ще бъде активирано по подразбиране. Можете да го изключите за всеки отделен монитор.",
     clearEventsMsg: "Наистина ли желаете да изтриете всички събития за този монитор?",
     clearHeartbeatsMsg: "Наистина ли желаете да изтриете всички записи за честотни проверки на този монитор?",
     confirmClearStatisticsMsg: "Наистина ли желаете да изтриете всички статистически данни?",
-    importHandleDescription: "Изберете 'Пропусни съществуващите', ако желаете да пропуснете всеки монитор или известяване със същото име. 'Презапис' ще изтрие всеки съществуващ монитор и известяване.",
+    importHandleDescription: "Изберете 'Пропусни съществуващите', ако желаете да пропуснете всеки монитор или известие със същото име. 'Презапис' ще изтрие всеки съществуващ монитор и известие.",
     confirmImportMsg: "Сигурни ли сте, че желаете импортирането на архива? Моля, уверете се, че сте избрали правилната опция за импортиране.",
     twoFAVerifyLabel: "Моля, въведете вашия токен код, за да проверите дали 2FA работи",
     tokenValidSettingsMsg: "Токен кодът е валиден! Вече можете да запазите настройките за 2FA.",
@@ -55,8 +55,7 @@ export default {
     Current: "Текущ",
     Uptime: "Достъпност",
     "Cert Exp.": "Вал. сертификат",
-    days: "дни",
-    day: "ден",
+    day: "ден | дни",
     "-day": "-дни",
     hour: "час",
     "-hour": "-часa",
@@ -76,9 +75,9 @@ export default {
     "Max. Redirects": "Макс. брой пренасочвания",
     "Accepted Status Codes": "Допустими статус кодове",
     Save: "Запази",
-    Notifications: "Известявания",
+    Notifications: "Известия",
     "Not available, please setup.": "Не са налични. Моля, настройте.",
-    "Setup Notification": "Настройки за известявания",
+    "Setup Notification": "Настрой известие",
     Light: "Светла",
     Dark: "Тъмна",
     Auto: "Автоматично",
@@ -90,13 +89,16 @@ export default {
     "Search Engine Visibility": "Видимост за търсачки",
     "Allow indexing": "Разреши индексиране",
     "Discourage search engines from indexing site": "Не позволявай на търсачките да индексират този сайт",
-    "Change Password": "Промени парола",
+    "Change Password": "Промяна на парола",
     "Current Password": "Текуща парола",
     "New Password": "Нова парола",
     "Repeat New Password": "Повторете новата парола",
-    "Update Password": "Актуализирай парола",
+    "Update Password": "Актуализирай паролата",
     "Disable Auth": "Изключи удостоверяване",
     "Enable Auth": "Включи удостоверяване",
+    "disableauth.message1": "Сигурни ли сте, че желаете да <strong>изключите удостоверяването</strong>?",
+    "disableauth.message2": "Използва се в случаите, когато <strong>има настроен алтернативен метод за удостоверяване</strong> преди Uptime Kuma, например Cloudflare Access, Authelia или друг механизъм за удостоверяване.",
+    "Please use this option carefully!": "Моля, използвайте с повишено внимание.",
     Logout: "Изход от профила",
     Leave: "Отказ",
     "I understand, please disable": "Разбирам. Моля, изключи",
@@ -109,7 +111,7 @@ export default {
     Login: "Вход",
     "No Monitors, please": "Все още няма монитори. Моля, добавете поне ",
     "add one": "един.",
-    "Notification Type": "Тип известяване",
+    "Notification Type": "Тип известие",
     Email: "Имейл",
     Test: "Тест",
     "Certificate Info": "Информация за сертификат",
@@ -131,9 +133,9 @@ export default {
     Events: "Събития",
     Heartbeats: "Проверки",
     "Auto Get": "Авт. попълване",
-    backupDescription: "Можете да архивирате всички монитори и всички известявания в JSON файл.",
+    backupDescription: "Можете да архивирате всички монитори и всички известия в JSON файл.",
     backupDescription2: "PS: Имайте предвид, че данните за история и събития няма да бъдат включени.",
-    backupDescription3: "Чувствителни данни, като токен кодове за известяване, се съдържат в експортирания файл. Моля, бъдете внимателни с неговото съхранение.",
+    backupDescription3: "Чувствителни данни, като токен кодове за известия, се съдържат в експортирания файл. Моля, бъдете внимателни с неговото съхранение.",
     alertNoFile: "Моля, изберете файл за импортиране.",
     alertWrongFileType: "Моля, изберете JSON файл.",
     "Clear all statistics": "Изтрий цялата статистика",
@@ -145,7 +147,7 @@ export default {
     "Setup 2FA": "Настройка 2FA",
     "Enable 2FA": "Включи 2FA",
     "Disable 2FA": "Изключи 2FA",
-    "2FA Settings": "Настройки 2FA",
+    "2FA Settings": "Настройка за 2FA",
     "Two Factor Authentication": "Двуфакторно удостоверяване",
     Active: "Активно",
     Inactive: "Неактивно",
@@ -202,7 +204,7 @@ export default {
     "Push URL": "Генериран Push URL адрес",
     needPushEvery: "Необходимо е да извършвате заявка към този URL адрес на всеки {0} секунди",
     pushOptionalParams: "Допълнителни, но не задължителни параметри: {0}",
-    defaultNotificationName: "Моето {notification} известяване ({number})",
+    defaultNotificationName: "Моето {notification} известие ({number})",
     here: "тук",
     Required: "Задължително поле",
     "Bot Token": "Бот токен",
@@ -252,7 +254,7 @@ export default {
     "Notification Sound": "Звуков сигнал",
     "More info on:": "Повече информация на: {0}",
     pushoverDesc1: "Приоритет Спешно (2) по подразбиране изчаква 30 секунди между повторните опити и изтича след 1 час.",
-    pushoverDesc2: "Ако желаете да изпратите известявания до различни устройства, попълнете полето Устройство.",
+    pushoverDesc2: "Ако желаете да изпратите известия до различни устройства, попълнете полето Устройство.",
     "SMS Type": "SMS тип",
     octopushTypePremium: "Премиум (Бърз - препоръчителен в случай на тревога)",
     octopushTypeLowCost: "Евтин (Бавен - понякога бива блокиран от оператора)",
@@ -275,7 +277,7 @@ export default {
     lineDevConsoleTo: "Line - Конзола за разработчици - {0}",
     "Basic Settings": "Основни настройки",
     "User ID": "Потребител ID",
-    "Messaging API": "API за известяване",
+    "Messaging API": "API за съобщаване",
     wayToGetLineChannelToken: "Необходимо е първо да посетите {0}, за да създадете (Messaging API) за доставчик и канал, след което може да вземете токен кода за канал и потребителско ID от споменатите по-горе елементи на менюто.",
     "Icon URL": "URL адрес за иконка",
     aboutIconURL: "Може да предоставите линк към картинка в поле \"URL Адрес за иконка\" за да отмените картинката на профила по подразбиране. Няма да се използва, ако вече сте настроили емотикон.",
@@ -291,7 +293,7 @@ export default {
     matrixHomeserverURL: "Сървър URL адрес (започва с http(s):// и порт по желание)",
     "Internal Room Id": "ID на вътрешна стая",
     matrixDesc1: "Може да намерите \"ID на вътрешна стая\" в разширените настройки на стаята във вашия Matrix клиент. Примерен изглед: !QMdRCpUIfLwsfjxye6:home.server.",
-    matrixDesc2: "Силно препоръчваме да създадете НОВ потребител и да НЕ използвате токен кодът на вашия личен Matrix потребирел, т.к. той позволява пълен достъп до вашия акаунт и всички стаи към които сте се присъединили. Вместо това създайте нов потребител и го поканете само в стаята, където желаете да получавате известяванията. Токен код за достъп ще получите изпълнявайки {0}",
+    matrixDesc2: "Силно препоръчваме да създадете НОВ потребител и да НЕ използвате токен кодът на вашия личен Matrix потребирел, т.к. той позволява пълен достъп до вашия акаунт и всички стаи към които сте се присъединили. Вместо това създайте нов потребител и го поканете само в стаята, където желаете да получавате известията. Токен код за достъп ще получите изпълнявайки {0}",
     Method: "Метод",
     Body: "Съобщение",
     Headers: "Хедъри",
@@ -299,7 +301,7 @@ export default {
     HeadersInvalidFormat: "Заявените хедъри не са валидни JSON: ",
     BodyInvalidFormat: "Заявеното съобщение не е валиден JSON: ",
     "Monitor History": "История на мониторите",
-    clearDataOlderThan: "Ще се съхранява {0} дни.",
+    clearDataOlderThan: "Ще се съхранява за {0} дни.",
     records: "записа",
     "One record": "Един запис",
     steamApiKeyDescription: "За да мониторирате Steam Gameserver се нуждаете от Steam Web-API ключ. Може да регистрирате Вашия API ключ тук: ",
@@ -308,12 +310,12 @@ export default {
     PasswordsDoNotMatch: "Паролите не съвпадат.",
     "Current User": "Текущ потребител",
     recent: "Скорошни",
-    shrinkDatabaseDescription: "Инициира \"VACUUM\" за \"SQLite\" база данни. Ако Вашата база данни е създадена след версия 1.10.0, \"AUTO_VACUUM\" функцията е активна и това действие не нужно.",
+    shrinkDatabaseDescription: "Инициира \"VACUUM\" за \"SQLite\" база данни. Ако Вашата база данни е създадена след версия 1.10.0, \"AUTO_VACUUM\" функцията е активна и това действие не е нужно.",
     Done: "Готово",
     Info: "Информация",
     Security: "Сигурност",
     "Steam API Key": "Steam API ключ",
-    "Shrink Database": "Редуциране база данни",
+    "Shrink Database": "Редуцирай базата данни",
     "Pick a RR-Type...": "Изберете вида на ресурсния запис за мониторитане...",
     "Pick Accepted Status Codes...": "Изберете статус кодове, които да се считат за успешен отговор...",
     Default: "По подразбиране",
@@ -353,8 +355,8 @@ export default {
     serwersmsSenderName: "SMS Подател име (регистриран през клиентския портал)",
     stackfield: "Stackfield",
     smtpDkimSettings: "DKIM Настройки",
-    smtpDkimDesc: "Моля, вижте Nodemailer DKIM {0} за инструкции.",
-    documentation: "документация",
+    smtpDkimDesc: "Моля, вижте {0} на Nodemailer DKIM за инструкции.",
+    documentation: "документацията",
     smtpDkimDomain: "Домейн",
     smtpDkimKeySelector: "Селектор на ключ",
     smtpDkimPrivateKey: "Частен ключ",
@@ -371,12 +373,12 @@ export default {
     alertaAlertState: "Състояние на тревога",
     alertaRecoverState: "Състояние на възстановяване",
     deleteStatusPageMsg: "Сигурни ли сте, че желаете да изтриете тази статус страница?",
-    Proxies: "Проксита",
+    Proxies: "Прокси",
     default: "По подразбиране",
     enabled: "Включено",
     setAsDefault: "Зададен по подразбиране",
     deleteProxyMsg: "Сигурни ли сте, че желаете да изтриете това прокси за всички монитори?",
-    proxyDescription: "Прокситата трябва да бъдат зададени към монитор за да функционират.",
+    proxyDescription: "За да функционират трябва да бъдат зададени към монитор.",
     enableProxyDescription: "Това прокси няма да има ефект върху заявките за мониторинг, докато не бъде активирано. Може да контролирате временното деактивиране на проксито от всички монитори чрез статуса на активиране.",
     setAsDefaultProxyDescription: "Това проки ще бъде включено по подразбиране за новите монитори. Може да го изключите по отделно за всеки един монитор.",
     "Certificate Chain": "Верига на сертификата",
@@ -401,7 +403,7 @@ export default {
     Retry: "Повтори",
     Topic: "Тема",
     "WeCom Bot Key": "WeCom бот ключ",
-    "Setup Proxy": "Настройка за прокси",
+    "Setup Proxy": "Настрой прокси",
     "Proxy Protocol": "Прокси протокол",
     "Proxy Server": "Прокси сървър",
     "Proxy server has authentication": "Прокси сървърът е с удостоверяване",
@@ -411,8 +413,8 @@ export default {
     Running: "Работи",
     "Not running": "Не работи",
     "Remove Token": "Премахни токен",
-    Start: "Старт",
-    Stop: "Стоп",
+    Start: "Стартирай",
+    Stop: "Спри",
     "Uptime Kuma": "Uptime Kuma",
     "Add New Status Page": "Добави нова статус страница",
     Slug: "Слъг",
@@ -422,6 +424,7 @@ export default {
     Next: "Следващ",
     "The slug is already taken. Please choose another slug.": "Този слъг вече се използва. Моля изберете друг.",
     "No Proxy": "Без прокси",
+    Authentication: "Удостоверяване",
     "HTTP Basic Auth": "HTTP основно удостоверяване",
     "New Status Page": "Нова статус страница",
     "Page Not Found": "Страницата не е открита",
@@ -442,7 +445,14 @@ export default {
     "Issuer:": "Издател:",
     "Fingerprint:": "Пръстов отпечатък:",
     "No status pages": "Няма статус страници",
-    "Domain Name Expiry Notification": "Известие за изтичащ домейн",
+    topic: "Тема",
+    topicExplanation: "MQTT тема за мониториране",
+    successMessage: "Съобщение при успех",
+    successMessageExplanation: "MQTT съобщение, което ще бъде считано за успех",
+    Customize: "Персонализирай",
+    "Custom Footer": "Персонализиран долен колонтитул",
+    "Custom CSS": "Потребителски CSS",
+    "Domain Name Expiry Notification": "Известие при изтичащ домейн",
     Proxy: "Прокси",
     "Date Created": "Дата на създаване",
     onebotHttpAddress: "OneBot HTTP адрес",
@@ -450,6 +460,81 @@ export default {
     onebotGroupMessage: "Група",
     onebotPrivateMessage: "Лично",
     onebotUserOrGroupId: "Група/Потребител ID",
-    onebotSafetyTips: "С цел сигурност, трябва да зададете токен код за достъп",
+    onebotSafetyTips: "С цел безопасност трябва да зададете токен код за достъп",
     "PushDeer Key": "PushDeer ключ",
+    "Footer Text": "Текст долен колонтитул",
+    "Show Powered By": "Покажи \"Създадено чрез\"",
+    "Domain Names": "Домейни",
+    signedInDisp: "Вписан като {0}",
+    signedInDispDisabled: "Удостоверяването е изключено.",
+    "Certificate Expiry Notification": "Известие за изтичане валидността на сертификата",
+    "API Username": "API Потребител",
+    "API Key": "API Ключ",
+    "Recipient Number": "Номер на получателя",
+    "From Name/Number": "От Име/Номер",
+    "Leave blank to use a shared sender number.": "Оставете празно, за да използвате споделен номер на подател.",
+    "Octopush API Version": "Octopush API версия",
+    "Legacy Octopush-DM": "Octopush-DM старa версия",
+    endpoint: "крайна точка",
+    octopushAPIKey: "\"API ключ\" от HTTP API удостоверяване в контролния панел",
+    octopushLogin: "\"Вписване\" от HTTP API удостоверяване в контролния панел",
+    promosmsLogin: "API Потребителско име",
+    promosmsPassword: "API Парола",
+    "pushoversounds pushover": "Pushover (по подразбиране)",
+    "pushoversounds bike": "Велосипед",
+    "pushoversounds bugle": "Тромпет",
+    "pushoversounds cashregister": "Касов апарат",
+    "pushoversounds classical": "Класическа музика",
+    "pushoversounds cosmic": "Космически",
+    "pushoversounds falling": "Падащ",
+    "pushoversounds gamelan": "Игра в мрежа",
+    "pushoversounds incoming": "Входящ",
+    "pushoversounds intermission": "Прекъсване",
+    "pushoversounds magic": "Магия",
+    "pushoversounds mechanical": "Механичен",
+    "pushoversounds pianobar": "Пиано бар",
+    "pushoversounds siren": "Сирена",
+    "pushoversounds spacealarm": "Космическа аларма",
+    "pushoversounds tugboat": "Буксир",
+    "pushoversounds alien": "Извънземна аларма (дълъг)",
+    "pushoversounds climb": "Изкачване (дълъг)",
+    "pushoversounds persistent": "Постоянен (дълъг)",
+    "pushoversounds echo": "Pushover ехо (дълъг)",
+    "pushoversounds updown": "Горе долу (дълъг)",
+    "pushoversounds vibrate": "Само вибрация",
+    "pushoversounds none": "Без (тих)",
+    pushyAPIKey: "Таен API ключ",
+    pushyToken: "Токен на устройство",
+    "Show update if available": "Покажи актуализация, ако е налична",
+    "Also check beta release": "Проверявай и за бета версии",
+    "Using a Reverse Proxy?": "Използвате ревърс прокси?",
+    "Check how to config it for WebSocket": "Проверете как да го конфигурирате за WebSocket",
+    "Steam Game Server": "Steam Game сървър",
+    "Most likely causes:": "Най-вероятни причини:",
+    "The resource is no longer available.": "Ресурсът вече не е наличен.",
+    "There might be a typing error in the address.": "Възможно е да е допусната грешка при изписването на адреса.",
+    "What you can try:": "Може да опитате:",
+    "Retype the address.": "Повторно въвеждане на адреса.",
+    "Go back to the previous page.": "Да се върнете към предишната страница.",
+    "Coming Soon": "Очаквайте скоро",
+    wayToGetClickSendSMSToken: "Може да получите API потребителско име и API ключ от {0} .",
+    dnsPortDescription: "DNS порт на сървъра. По подразбиране е 53, но може да бъде променен по всяко време.",
+    error: "грешка",
+    critical: "критично",
+    wayToGetPagerDutyKey: "Може да го получите като посетите Service -> Service Directory -> (Select a service) -> Integrations -> Add integration. Тук трябва да потърсите \"Events API V2\". Повече информация {0}",
+    "Integration Key": "Ключ за интегриране",
+    "Integration URL": "URL адрес за интеграция",
+    "Auto resolve or acknowledged": "Автоматично разрешаване или потвърждаване",
+    "do nothing": "не прави нищо",
+    "auto acknowledged": "автоматично потвърждаване",
+    "auto resolve": "автоматично разрешаване",
+    "Connection String": "Стринг за връзка",
+    Query: "Заявка",
+    settingsCertificateExpiry: "Изтичане валидността на TLS сертификата",
+    certificationExpiryDescription: "HTTPS мониторите ще задействат известие, ако е наличен изтичащ TLS сертификат, през следващите:",
+    "ntfy Topic": "ntfy Тема",
+    Domain: "Домейн",
+    Workstation: "Работна станция",
+    disableCloudflaredNoAuthMsg: "Тъй като сте в режим \"No Auth mode\", парола не се изисква.",
+    wayToGetLineNotifyToken: "Може да получите токен код за достъп от {0}",
 };

From 96ec46765b68c23843dae3317ca70fb843da5b3a Mon Sep 17 00:00:00 2001
From: MrEddX <66828538+MrEddX@users.noreply.github.com>
Date: Fri, 29 Jul 2022 21:24:57 +0300
Subject: [PATCH 86/99] Update en.js

Just a typo.
---
 src/languages/en.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/languages/en.js b/src/languages/en.js
index 15bdcc8a..e9b5e2d3 100644
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -536,5 +536,5 @@ export default {
     "Domain": "Domain",
     "Workstation": "Workstation",
     disableCloudflaredNoAuthMsg: "You are in No Auth mode, password is not require.",
-    wayToGetLineNotifyToken: "You can get a access token from {0}",
+    wayToGetLineNotifyToken: "You can get an access token from {0}",
 };

From 16d6885a88aec8fff716c4b0577ce59bb3f7305a Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Sun, 31 Jul 2022 18:11:40 +0800
Subject: [PATCH 87/99] Fix radio button and add description

---
 src/components/settings/ReverseProxy.vue | 12 ++++++++----
 src/languages/en.js                      |  1 +
 2 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/src/components/settings/ReverseProxy.vue b/src/components/settings/ReverseProxy.vue
index 85046cc2..0fab7629 100644
--- a/src/components/settings/ReverseProxy.vue
+++ b/src/components/settings/ReverseProxy.vue
@@ -103,12 +103,12 @@
                     v-model="settings.trustProxy"
                     class="form-check-input"
                     type="radio"
-                    name="flexRadioDefault"
+                    name="trustProxyYes"
                     :value="true"
                     required
                 />
                 <label class="form-check-label" for="trustProxyYes">
-                    {{ $t("Trust 'X-Forwarded-*' headers") }}
+                    {{ $t("Yes") }}
                 </label>
             </div>
             <div class="form-check">
@@ -121,10 +121,14 @@
                     :value="false"
                     required
                 />
-                <label class="form-check-label" for="trustProxyYes">
-                    {{ $t("Don't trust 'X-Forwarded-*' headers") }}
+                <label class="form-check-label" for="trustProxyNo">
+                    {{ $t("No") }}
                 </label>
             </div>
+
+            <div class="form-text">
+                {{ $t("trustProxyDescription") }}
+            </div>
         </div>
 
         <div>
diff --git a/src/languages/en.js b/src/languages/en.js
index 3c3cdff4..2d66154d 100644
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -540,4 +540,5 @@ export default {
     "Domain": "Domain",
     "Workstation": "Workstation",
     disableCloudflaredNoAuthMsg: "You are in No Auth mode, password is not require.",
+    trustProxyDescription: "Trust 'X-Forwarded-*' headers. If you want to get the correct client IP and your Uptime Kuma is behind such as Nginx or Apache, you should enable this.",
 };

From a0843745f977b6a11ffcff426e2efa78ebe6be97 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Sun, 31 Jul 2022 18:12:41 +0800
Subject: [PATCH 88/99] Remove unused language key

---
 src/languages/en.js | 2 --
 1 file changed, 2 deletions(-)

diff --git a/src/languages/en.js b/src/languages/en.js
index 2d66154d..9f20cd5d 100644
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -455,8 +455,6 @@ export default {
     "The current connection may be lost if you are currently connecting via Cloudflare Tunnel. Are you sure want to stop it? Type your current password to confirm it.": "The current connection may be lost if you are currently connecting via Cloudflare Tunnel. Are you sure want to stop it? Type your current password to confirm it.",
     "HTTP Headers": "HTTP Headers",
     "Trust Proxy": "Trust Proxy",
-    "Trust 'X-Forwarded-*' headers": "Trust 'X-Forwarded-*' headers",
-    "Don't trust 'X-Forwarded-*' headers": "Don't trust 'X-Forwarded-*' headers",
     "Other Software": "Other Software",
     "For example: nginx, Apache and Traefik.": "For example: nginx, Apache and Traefik.",
     "Please read": "Please read",

From a3b16129381b0e90a6914ce774ac47c05e976581 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Sun, 31 Jul 2022 23:36:33 +0800
Subject: [PATCH 89/99] getClientIP respect trustProxy setting

---
 server/server.js             | 44 +++++++++++++++++++-----------------
 server/uptime-kuma-server.js | 13 +++++++++++
 2 files changed, 36 insertions(+), 21 deletions(-)

diff --git a/server/server.js b/server/server.js
index 8a0dd871..61bd9d93 100644
--- a/server/server.js
+++ b/server/server.js
@@ -254,7 +254,9 @@ let needSetup = false;
         // ***************************
 
         socket.on("loginByToken", async (token, callback) => {
-            log.info("auth", `Login by token. IP=${getClientIp(socket)}`);
+            const clientIP = await server.getClientIP(socket);
+
+            log.info("auth", `Login by token. IP=${clientIP}`);
 
             try {
                 let decoded = jwt.verify(token, jwtSecret);
@@ -270,14 +272,14 @@ let needSetup = false;
                     afterLogin(socket, user);
                     log.debug("auth", "afterLogin ok");
 
-                    log.info("auth", `Successfully logged in user ${decoded.username}. IP=${getClientIp(socket)}`);
+                    log.info("auth", `Successfully logged in user ${decoded.username}. IP=${clientIP}`);
 
                     callback({
                         ok: true,
                     });
                 } else {
 
-                    log.info("auth", `Inactive or deleted user ${decoded.username}. IP=${getClientIp(socket)}`);
+                    log.info("auth", `Inactive or deleted user ${decoded.username}. IP=${clientIP}`);
 
                     callback({
                         ok: false,
@@ -286,7 +288,7 @@ let needSetup = false;
                 }
             } catch (error) {
 
-                log.error("auth", `Invalid token. IP=${getClientIp(socket)}`);
+                log.error("auth", `Invalid token. IP=${clientIP}`);
 
                 callback({
                     ok: false,
@@ -297,7 +299,9 @@ let needSetup = false;
         });
 
         socket.on("login", async (data, callback) => {
-            log.info("auth", `Login by username + password. IP=${getClientIp(socket)}`);
+            const clientIP = await server.getClientIP(socket);
+
+            log.info("auth", `Login by username + password. IP=${clientIP}`);
 
             // Checking
             if (typeof callback !== "function") {
@@ -310,7 +314,7 @@ let needSetup = false;
 
             // Login Rate Limit
             if (! await loginRateLimiter.pass(callback)) {
-                log.info("auth", `Too many failed requests for user ${data.username}. IP=${getClientIp(socket)}`);
+                log.info("auth", `Too many failed requests for user ${data.username}. IP=${clientIP}`);
                 return;
             }
 
@@ -320,7 +324,7 @@ let needSetup = false;
                 if (user.twofa_status === 0) {
                     afterLogin(socket, user);
 
-                    log.info("auth", `Successfully logged in user ${data.username}. IP=${getClientIp(socket)}`);
+                    log.info("auth", `Successfully logged in user ${data.username}. IP=${clientIP}`);
 
                     callback({
                         ok: true,
@@ -332,7 +336,7 @@ let needSetup = false;
 
                 if (user.twofa_status === 1 && !data.token) {
 
-                    log.info("auth", `2FA token required for user ${data.username}. IP=${getClientIp(socket)}`);
+                    log.info("auth", `2FA token required for user ${data.username}. IP=${clientIP}`);
 
                     callback({
                         tokenRequired: true,
@@ -350,7 +354,7 @@ let needSetup = false;
                             socket.userID,
                         ]);
 
-                        log.info("auth", `Successfully logged in user ${data.username}. IP=${getClientIp(socket)}`);
+                        log.info("auth", `Successfully logged in user ${data.username}. IP=${clientIP}`);
 
                         callback({
                             ok: true,
@@ -360,7 +364,7 @@ let needSetup = false;
                         });
                     } else {
 
-                        log.warn("auth", `Invalid token provided for user ${data.username}. IP=${getClientIp(socket)}`);
+                        log.warn("auth", `Invalid token provided for user ${data.username}. IP=${clientIP}`);
 
                         callback({
                             ok: false,
@@ -370,7 +374,7 @@ let needSetup = false;
                 }
             } else {
 
-                log.warn("auth", `Incorrect username or password for user ${data.username}. IP=${getClientIp(socket)}`);
+                log.warn("auth", `Incorrect username or password for user ${data.username}. IP=${clientIP}`);
 
                 callback({
                     ok: false,
@@ -442,6 +446,8 @@ let needSetup = false;
         });
 
         socket.on("save2FA", async (currentPassword, callback) => {
+            const clientIP = await server.getClientIP(socket);
+
             try {
                 if (! await twoFaRateLimiter.pass(callback)) {
                     return;
@@ -454,7 +460,7 @@ let needSetup = false;
                     socket.userID,
                 ]);
 
-                log.info("auth", `Saved 2FA token. IP=${getClientIp(socket)}`);
+                log.info("auth", `Saved 2FA token. IP=${clientIP}`);
 
                 callback({
                     ok: true,
@@ -462,7 +468,7 @@ let needSetup = false;
                 });
             } catch (error) {
 
-                log.error("auth", `Error changing 2FA token. IP=${getClientIp(socket)}`);
+                log.error("auth", `Error changing 2FA token. IP=${clientIP}`);
 
                 callback({
                     ok: false,
@@ -472,6 +478,8 @@ let needSetup = false;
         });
 
         socket.on("disable2FA", async (currentPassword, callback) => {
+            const clientIP = await server.getClientIP(socket);
+
             try {
                 if (! await twoFaRateLimiter.pass(callback)) {
                     return;
@@ -481,7 +489,7 @@ let needSetup = false;
                 await doubleCheckPassword(socket, currentPassword);
                 await TwoFA.disable2FA(socket.userID);
 
-                log.info("auth", `Disabled 2FA token. IP=${getClientIp(socket)}`);
+                log.info("auth", `Disabled 2FA token. IP=${clientIP}`);
 
                 callback({
                     ok: true,
@@ -489,7 +497,7 @@ let needSetup = false;
                 });
             } catch (error) {
 
-                log.error("auth", `Error disabling 2FA token. IP=${getClientIp(socket)}`);
+                log.error("auth", `Error disabling 2FA token. IP=${clientIP}`);
 
                 callback({
                     ok: false,
@@ -1684,12 +1692,6 @@ async function shutdownFunction(signal) {
     await cloudflaredStop();
 }
 
-function getClientIp(socket) {
-    return socket.client.conn.request.headers["x-forwarded-for"]
-            || socket.client.conn.request.headers["x-real-ip"]
-            || socket.client.conn.remoteAddress.replace(/^.*:/, "");
-}
-
 /** Final function called before application exits */
 function finalFunction() {
     log.info("server", "Graceful shutdown successful!");
diff --git a/server/uptime-kuma-server.js b/server/uptime-kuma-server.js
index 3362f72c..0f32017f 100644
--- a/server/uptime-kuma-server.js
+++ b/server/uptime-kuma-server.js
@@ -8,6 +8,7 @@ const { log } = require("../src/util");
 const Database = require("./database");
 const util = require("util");
 const { CacheableDnsHttpAgent } = require("./cacheable-dns-http-agent");
+const { setting } = require("./util-server");
 
 /**
  * `module.exports` (alias: `server`) should be inside this class, in order to avoid circular dependency issue.
@@ -128,6 +129,18 @@ class UptimeKumaServer {
 
         errorLogStream.end();
     }
+
+    async getClientIP(socket) {
+        const clientIP = socket.client.conn.remoteAddress.replace(/^.*:/, "");
+
+        if (await setting("trustProxy")) {
+            return socket.client.conn.request.headers["x-forwarded-for"]
+                || socket.client.conn.request.headers["x-real-ip"]
+                || clientIP;
+        } else {
+            return clientIP;
+        }
+    }
 }
 
 module.exports = {

From 2389b604fe609cbc650d2761a52278cac0173391 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Sun, 31 Jul 2022 23:41:29 +0800
Subject: [PATCH 90/99] Use Settings.get

---
 server/uptime-kuma-server.js | 4 ++--
 server/util-server.js        | 1 +
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/server/uptime-kuma-server.js b/server/uptime-kuma-server.js
index 0f32017f..67b6ed0a 100644
--- a/server/uptime-kuma-server.js
+++ b/server/uptime-kuma-server.js
@@ -8,7 +8,7 @@ const { log } = require("../src/util");
 const Database = require("./database");
 const util = require("util");
 const { CacheableDnsHttpAgent } = require("./cacheable-dns-http-agent");
-const { setting } = require("./util-server");
+const { Settings } = require("./settings");
 
 /**
  * `module.exports` (alias: `server`) should be inside this class, in order to avoid circular dependency issue.
@@ -133,7 +133,7 @@ class UptimeKumaServer {
     async getClientIP(socket) {
         const clientIP = socket.client.conn.remoteAddress.replace(/^.*:/, "");
 
-        if (await setting("trustProxy")) {
+        if (await Settings.get("trustProxy")) {
             return socket.client.conn.request.headers["x-forwarded-for"]
                 || socket.client.conn.request.headers["x-real-ip"]
                 || clientIP;
diff --git a/server/util-server.js b/server/util-server.js
index 84244b02..df711cf0 100644
--- a/server/util-server.js
+++ b/server/util-server.js
@@ -289,6 +289,7 @@ exports.postgresQuery = function (connectionString, query) {
  * Retrieve value of setting based on key
  * @param {string} key Key of setting to retrieve
  * @returns {Promise<any>} Value
+ * @deprecated Use await Settings.get(key)
  */
 exports.setting = async function (key) {
     return await Settings.get(key);

From f016caa51300f7b127950b5c8e81463d3fbdeafc Mon Sep 17 00:00:00 2001
From: Joseph Benguira <z51biz@gmail.com>
Date: Sun, 31 Jul 2022 18:51:53 +0300
Subject: [PATCH 91/99] Avoid error "SQLITE_BUSY: database is locked"

Avoid error "SQLITE_BUSY: database is locked" by allowing SQLITE to wait up to 5 seconds to do a write
---
 server/database.js | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/server/database.js b/server/database.js
index 00fd48d9..b58daf87 100644
--- a/server/database.js
+++ b/server/database.js
@@ -146,6 +146,9 @@ class Database {
         }
         await R.exec("PRAGMA cache_size = -12000");
         await R.exec("PRAGMA auto_vacuum = FULL");
+        
+        //Avoid error "SQLITE_BUSY: database is locked" by allowing SQLITE to wait up to 5 seconds to do a write
+        await R.exec("PRAGMA busy_timeout = 5000;");
 
         // This ensures that an operating system crash or power failure will not corrupt the database.
         // FULL synchronous is very safe, but it is also slower.

From 82b9bfc5a0a4fdc6492bfedbd11a0bd7bfa10b38 Mon Sep 17 00:00:00 2001
From: Joseph Benguira <z51biz@gmail.com>
Date: Sun, 31 Jul 2022 18:59:02 +0300
Subject: [PATCH 92/99] fixed Trailing spaces not allowed lint issue

---
 server/database.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/server/database.js b/server/database.js
index b58daf87..4c374ca6 100644
--- a/server/database.js
+++ b/server/database.js
@@ -146,7 +146,7 @@ class Database {
         }
         await R.exec("PRAGMA cache_size = -12000");
         await R.exec("PRAGMA auto_vacuum = FULL");
-        
+
         //Avoid error "SQLITE_BUSY: database is locked" by allowing SQLITE to wait up to 5 seconds to do a write
         await R.exec("PRAGMA busy_timeout = 5000;");
 

From 71d62ee1510a5c336d3d22901e231814bba0187f Mon Sep 17 00:00:00 2001
From: Joseph Benguira <z51biz@gmail.com>
Date: Sun, 31 Jul 2022 19:00:19 +0300
Subject: [PATCH 93/99] removed ; after the PRAGMA command

---
 server/database.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/server/database.js b/server/database.js
index 4c374ca6..5f87352b 100644
--- a/server/database.js
+++ b/server/database.js
@@ -148,7 +148,7 @@ class Database {
         await R.exec("PRAGMA auto_vacuum = FULL");
 
         //Avoid error "SQLITE_BUSY: database is locked" by allowing SQLITE to wait up to 5 seconds to do a write
-        await R.exec("PRAGMA busy_timeout = 5000;");
+        await R.exec("PRAGMA busy_timeout = 5000");
 
         // This ensures that an operating system crash or power failure will not corrupt the database.
         // FULL synchronous is very safe, but it is also slower.

From fb3fe17c2824e62a731270257746b9b214d17912 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Mon, 1 Aug 2022 15:42:58 +0800
Subject: [PATCH 94/99] Fix getClientIP

Co-authored-by: Mateusz Hajder <6783135+mhajder@users.noreply.github.com>
---
 server/uptime-kuma-server.js | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/server/uptime-kuma-server.js b/server/uptime-kuma-server.js
index 67b6ed0a..98de65a4 100644
--- a/server/uptime-kuma-server.js
+++ b/server/uptime-kuma-server.js
@@ -131,14 +131,18 @@ class UptimeKumaServer {
     }
 
     async getClientIP(socket) {
-        const clientIP = socket.client.conn.remoteAddress.replace(/^.*:/, "");
+        let clientIP = socket.client.conn.remoteAddress;
+
+        if (clientIP === undefined) {
+            clientIP = "";
+        }
 
         if (await Settings.get("trustProxy")) {
             return socket.client.conn.request.headers["x-forwarded-for"]
                 || socket.client.conn.request.headers["x-real-ip"]
-                || clientIP;
+                || clientIP.replace(/^.*:/, "");
         } else {
-            return clientIP;
+            return clientIP.replace(/^.*:/, "");
         }
     }
 }

From d6a113396a0a3512c7ac5975eaafb5fba2c46a36 Mon Sep 17 00:00:00 2001
From: Joseph Benguira <z51biz@gmail.com>
Date: Mon, 1 Aug 2022 13:18:19 +0300
Subject: [PATCH 95/99] Update server/database.js

Co-authored-by: Adam Stachowicz <saibamenppl@gmail.com>
---
 server/database.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/server/database.js b/server/database.js
index 5f87352b..b4865b1f 100644
--- a/server/database.js
+++ b/server/database.js
@@ -147,7 +147,7 @@ class Database {
         await R.exec("PRAGMA cache_size = -12000");
         await R.exec("PRAGMA auto_vacuum = FULL");
 
-        //Avoid error "SQLITE_BUSY: database is locked" by allowing SQLITE to wait up to 5 seconds to do a write
+        // Avoid error "SQLITE_BUSY: database is locked" by allowing SQLITE to wait up to 5 seconds to do a write
         await R.exec("PRAGMA busy_timeout = 5000");
 
         // This ensures that an operating system crash or power failure will not corrupt the database.

From 2af754b5e85e36b8f1f3b4b376cd970c116d7c3f Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Thu, 11 Aug 2022 21:09:16 +0800
Subject: [PATCH 96/99] Update package-lock.json

---
 package-lock.json | 114 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 114 insertions(+)

diff --git a/package-lock.json b/package-lock.json
index 778e6bc3..0cf62fa7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -39,6 +39,7 @@
                 "mqtt": "^4.2.8",
                 "mssql": "^8.1.0",
                 "node-cloudflared-tunnel": "~1.0.9",
+                "node-radius-client": "^1.0.0",
                 "nodemailer": "~6.6.5",
                 "notp": "~2.0.3",
                 "password-hash": "~1.2.2",
@@ -8215,6 +8216,12 @@
                 "readable-stream": "^3.6.0"
             }
         },
+        "node_modules/hoek": {
+            "version": "6.1.3",
+            "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz",
+            "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==",
+            "deprecated": "This module has moved and is now available at @hapi/hoek. Please update your dependencies as this version is no longer maintained an may contain bugs and security issues."
+        },
         "node_modules/homedir-polyfill": {
             "version": "1.0.3",
             "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
@@ -8915,6 +8922,17 @@
             "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
             "devOptional": true
         },
+        "node_modules/isemail": {
+            "version": "3.2.0",
+            "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz",
+            "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==",
+            "dependencies": {
+                "punycode": "2.x.x"
+            },
+            "engines": {
+                "node": ">=4.0.0"
+            }
+        },
         "node_modules/isexe": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -12151,6 +12169,32 @@
             "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
             "dev": true
         },
+        "node_modules/node-radius-client": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/node-radius-client/-/node-radius-client-1.0.0.tgz",
+            "integrity": "sha512-FkR9cMV5hNoX+kKDUTzuagvEixlLiaEJQ1/ywOdhahsihKrGDhVZmnCvmrCStA589MT3yuC/J2eKc6z68IGdBw==",
+            "dependencies": {
+                "joi": "^14.3.1",
+                "node-radius-utils": "^1.2.0",
+                "radius": "^1.1.4"
+            }
+        },
+        "node_modules/node-radius-client/node_modules/joi": {
+            "version": "14.3.1",
+            "resolved": "https://registry.npmjs.org/joi/-/joi-14.3.1.tgz",
+            "integrity": "sha512-LQDdM+pkOrpAn4Lp+neNIFV3axv1Vna3j38bisbQhETPMANYRbFJFUyOZcOClYvM/hppMhGWuKSFEK9vjrB+bQ==",
+            "deprecated": "This module has moved and is now available at @hapi/joi. Please update your dependencies as this version is no longer maintained an may contain bugs and security issues.",
+            "dependencies": {
+                "hoek": "6.x.x",
+                "isemail": "3.x.x",
+                "topo": "3.x.x"
+            }
+        },
+        "node_modules/node-radius-utils": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/node-radius-utils/-/node-radius-utils-1.2.0.tgz",
+            "integrity": "sha512-i3Sf6khnenl0aXumo0whAlfPWTaBqHxEnVBBxpu3dZ7q69NkPPv71rvPjlDZ5wkeKCTNNUTECljerS5kcYQxRw=="
+        },
         "node_modules/node-releases": {
             "version": "2.0.5",
             "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz",
@@ -13429,6 +13473,14 @@
                 "node": ">=8"
             }
         },
+        "node_modules/radius": {
+            "version": "1.1.4",
+            "resolved": "https://registry.npmjs.org/radius/-/radius-1.1.4.tgz",
+            "integrity": "sha512-UWuzdF6xf3NpsXFZZmUEkxtEalDXj8hdmMXgbGzn7vOk6zXNsiIY2I6SJ1euHt7PTQuMoz2qDEJB+AfJDJgQYw==",
+            "engines": {
+                "node": ">=0.8.0"
+            }
+        },
         "node_modules/range-parser": {
             "version": "1.2.1",
             "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -15261,6 +15313,15 @@
                 "node": ">=0.6"
             }
         },
+        "node_modules/topo": {
+            "version": "3.0.3",
+            "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.3.tgz",
+            "integrity": "sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ==",
+            "deprecated": "This module has moved and is now available at @hapi/topo. Please update your dependencies as this version is no longer maintained an may contain bugs and security issues.",
+            "dependencies": {
+                "hoek": "6.x.x"
+            }
+        },
         "node_modules/toposort": {
             "version": "2.0.2",
             "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
@@ -22641,6 +22702,11 @@
                 "readable-stream": "^3.6.0"
             }
         },
+        "hoek": {
+            "version": "6.1.3",
+            "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz",
+            "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ=="
+        },
         "homedir-polyfill": {
             "version": "1.0.3",
             "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
@@ -23123,6 +23189,14 @@
             "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
             "devOptional": true
         },
+        "isemail": {
+            "version": "3.2.0",
+            "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz",
+            "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==",
+            "requires": {
+                "punycode": "2.x.x"
+            }
+        },
         "isexe": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -25618,6 +25692,33 @@
             "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
             "dev": true
         },
+        "node-radius-client": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/node-radius-client/-/node-radius-client-1.0.0.tgz",
+            "integrity": "sha512-FkR9cMV5hNoX+kKDUTzuagvEixlLiaEJQ1/ywOdhahsihKrGDhVZmnCvmrCStA589MT3yuC/J2eKc6z68IGdBw==",
+            "requires": {
+                "joi": "^14.3.1",
+                "node-radius-utils": "^1.2.0",
+                "radius": "^1.1.4"
+            },
+            "dependencies": {
+                "joi": {
+                    "version": "14.3.1",
+                    "resolved": "https://registry.npmjs.org/joi/-/joi-14.3.1.tgz",
+                    "integrity": "sha512-LQDdM+pkOrpAn4Lp+neNIFV3axv1Vna3j38bisbQhETPMANYRbFJFUyOZcOClYvM/hppMhGWuKSFEK9vjrB+bQ==",
+                    "requires": {
+                        "hoek": "6.x.x",
+                        "isemail": "3.x.x",
+                        "topo": "3.x.x"
+                    }
+                }
+            }
+        },
+        "node-radius-utils": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/node-radius-utils/-/node-radius-utils-1.2.0.tgz",
+            "integrity": "sha512-i3Sf6khnenl0aXumo0whAlfPWTaBqHxEnVBBxpu3dZ7q69NkPPv71rvPjlDZ5wkeKCTNNUTECljerS5kcYQxRw=="
+        },
         "node-releases": {
             "version": "2.0.5",
             "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz",
@@ -26532,6 +26633,11 @@
             "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==",
             "dev": true
         },
+        "radius": {
+            "version": "1.1.4",
+            "resolved": "https://registry.npmjs.org/radius/-/radius-1.1.4.tgz",
+            "integrity": "sha512-UWuzdF6xf3NpsXFZZmUEkxtEalDXj8hdmMXgbGzn7vOk6zXNsiIY2I6SJ1euHt7PTQuMoz2qDEJB+AfJDJgQYw=="
+        },
         "range-parser": {
             "version": "1.2.1",
             "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -27967,6 +28073,14 @@
             "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
             "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
         },
+        "topo": {
+            "version": "3.0.3",
+            "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.3.tgz",
+            "integrity": "sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ==",
+            "requires": {
+                "hoek": "6.x.x"
+            }
+        },
         "toposort": {
             "version": "2.0.2",
             "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",

From a6007adce38755cb924d980fccf5250a9b040adc Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Sat, 13 Aug 2022 13:32:16 +0800
Subject: [PATCH 97/99] Update to 1.18.0-beta.1

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index bac451d5..68a5863d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
     "name": "uptime-kuma",
-    "version": "1.17.1",
+    "version": "1.18.0-beta.1",
     "license": "MIT",
     "repository": {
         "type": "git",

From 728e811969a2ecc4c3550d37fb75e1c893750d3c Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Sat, 13 Aug 2022 13:35:03 +0800
Subject: [PATCH 98/99] Update Apprise to 1.0.0

---
 docker/alpine-base.dockerfile | 2 +-
 docker/debian-base.dockerfile | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/docker/alpine-base.dockerfile b/docker/alpine-base.dockerfile
index cde65bb6..1d74de05 100644
--- a/docker/alpine-base.dockerfile
+++ b/docker/alpine-base.dockerfile
@@ -4,5 +4,5 @@ WORKDIR /app
 
 # Install apprise, iputils for non-root ping, setpriv
 RUN apk add --no-cache iputils setpriv dumb-init python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib && \
-    pip3 --no-cache-dir install apprise==0.9.9 && \
+    pip3 --no-cache-dir install apprise==1.0.0 && \
     rm -rf /root/.cache
diff --git a/docker/debian-base.dockerfile b/docker/debian-base.dockerfile
index f90968a8..20bef3dd 100644
--- a/docker/debian-base.dockerfile
+++ b/docker/debian-base.dockerfile
@@ -11,7 +11,7 @@ WORKDIR /app
 RUN apt update && \
     apt --yes --no-install-recommends install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \
         sqlite3 iputils-ping util-linux dumb-init && \
-    pip3 --no-cache-dir install apprise==0.9.9 && \
+    pip3 --no-cache-dir install apprise==1.0.0 && \
     rm -rf /var/lib/apt/lists/* && \
     apt --yes autoremove
 

From af944242839616ed0a990fdba2e2f43fd043d4c4 Mon Sep 17 00:00:00 2001
From: Louis Lam <louislam@users.noreply.github.com>
Date: Sat, 13 Aug 2022 14:04:17 +0800
Subject: [PATCH 99/99] Update to 1.18.0-beta.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 68a5863d..981ca191 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
     "name": "uptime-kuma",
-    "version": "1.18.0-beta.1",
+    "version": "1.18.0-beta.0",
     "license": "MIT",
     "repository": {
         "type": "git",