From 934d633d4d693b808db4c6afa524f7b7f51595ff Mon Sep 17 00:00:00 2001 From: Juan Cruz Vincenti Date: Thu, 11 Nov 2021 20:06:32 -0300 Subject: [PATCH 001/803] Add description to monitor * Add description to monitor model * Add description field to database * Add english and spanish translation for description * Closes: #482 --- db/patch-add-description-monitor.sql | 7 +++++++ server/database.js | 1 + server/model/monitor.js | 2 ++ server/server.js | 2 ++ src/components/MonitorList.vue | 3 +++ src/icon.js | 2 ++ src/languages/en.js | 1 + src/languages/es-ES.js | 1 + src/pages/Details.vue | 1 + src/pages/EditMonitor.vue | 6 ++++++ 10 files changed, 26 insertions(+) create mode 100644 db/patch-add-description-monitor.sql diff --git a/db/patch-add-description-monitor.sql b/db/patch-add-description-monitor.sql new file mode 100644 index 00000000..da1aa55b --- /dev/null +++ b/db/patch-add-description-monitor.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 description TEXT default null; + +COMMIT; diff --git a/server/database.js b/server/database.js index afcace70..55f08a0c 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-description-monitor.sql": true, } /** diff --git a/server/model/monitor.js b/server/model/monitor.js index 6aa614b7..50f11e87 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -31,6 +31,7 @@ class Monitor extends BeanModel { return { id: this.id, name: this.name, + description: this.description, }; } @@ -54,6 +55,7 @@ class Monitor extends BeanModel { return { id: this.id, name: this.name, + description: this.description, url: this.url, method: this.method, body: this.body, diff --git a/server/server.js b/server/server.js index 868bbd5e..794f67b4 100644 --- a/server/server.js +++ b/server/server.js @@ -568,6 +568,7 @@ exports.entryPage = "dashboard"; } bean.name = monitor.name; + bean.description = monitor.description; bean.type = monitor.type; bean.url = monitor.url; bean.method = monitor.method; @@ -1134,6 +1135,7 @@ exports.entryPage = "dashboard"; let monitor = { // Define the new variable from earlier here name: monitorListData[i].name, + description: monitorListData[i].description, type: monitorListData[i].type, url: monitorListData[i].url, method: monitorListData[i].method || "GET", diff --git a/src/components/MonitorList.vue b/src/components/MonitorList.vue index ef51e89c..29d14d1f 100644 --- a/src/components/MonitorList.vue +++ b/src/components/MonitorList.vue @@ -23,6 +23,9 @@
{{ item.name }} + + +
diff --git a/src/icon.js b/src/icon.js index e78992f2..55e66291 100644 --- a/src/icon.js +++ b/src/icon.js @@ -33,6 +33,7 @@ import { faFile, faAward, faLink, + faInfoCircle, } from "@fortawesome/free-solid-svg-icons"; library.add( @@ -65,6 +66,7 @@ library.add( faFile, faAward, faLink, + faInfoCircle, ); export { FontAwesomeIcon }; diff --git a/src/languages/en.js b/src/languages/en.js index fee80a76..d238bb66 100644 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -46,6 +46,7 @@ export default { Unknown: "Unknown", Pause: "Pause", Name: "Name", + Description: "Description", Status: "Status", DateTime: "DateTime", Message: "Message", diff --git a/src/languages/es-ES.js b/src/languages/es-ES.js index d772db06..fe04d86a 100644 --- a/src/languages/es-ES.js +++ b/src/languages/es-ES.js @@ -34,6 +34,7 @@ export default { Unknown: "Desconocido", Pause: "Pausar", Name: "Nombre", + Description: "Descripción", Status: "Estado", DateTime: "Fecha y Hora", Message: "Mensaje", diff --git a/src/pages/Details.vue b/src/pages/Details.vue index d40561fe..12e86df4 100644 --- a/src/pages/Details.vue +++ b/src/pages/Details.vue @@ -2,6 +2,7 @@

{{ monitor.name }}

+

{{ monitor.description }}

diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index 4a0d0408..a0e68676 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -41,6 +41,12 @@
+ +
+ + +
+
From fbf2df9e7af19bee7f02ea41d2ead5b90ccd2882 Mon Sep 17 00:00:00 2001 From: Juan Cruz Vincenti Date: Thu, 25 Nov 2021 18:11:13 -0300 Subject: [PATCH 002/803] Add conditional rendering to description * Modify Details component * Modify MonitorList component --- src/components/MonitorList.vue | 2 +- src/pages/Details.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MonitorList.vue b/src/components/MonitorList.vue index 29d14d1f..19f2c837 100644 --- a/src/components/MonitorList.vue +++ b/src/components/MonitorList.vue @@ -23,7 +23,7 @@
{{ item.name }} - +
diff --git a/src/pages/Details.vue b/src/pages/Details.vue index 12e86df4..d96e42eb 100644 --- a/src/pages/Details.vue +++ b/src/pages/Details.vue @@ -2,7 +2,7 @@

{{ monitor.name }}

-

{{ monitor.description }}

+

{{ monitor.description }}

From 0d3414c6d6089f7b41f6bb4b1729f01ab2cb5e62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Kr=C3=BDda?= Date: Sun, 23 Jan 2022 15:22:00 +0100 Subject: [PATCH 003/803] A complete maintenance planning system has been created --- db/patch-maintenance-table.sql | 25 +++ package-lock.json | 54 ++++--- server/database.js | 1 + server/model/heartbeat.js | 1 + server/model/maintenance.js | 38 +++++ server/model/monitor.js | 64 +++++++- server/routers/api-router.js | 38 ++++- server/server.js | 217 +++++++++++++++++++++++++ src/assets/app.scss | 1 + src/assets/vars.scss | 1 + src/components/HeartbeatBar.vue | 6 +- src/components/MonitorList.vue | 91 ++++++++++- src/components/PingChart.vue | 10 +- src/components/PublicGroupList.vue | 4 + src/components/Status.vue | 8 + src/components/Uptime.vue | 8 + src/icon.js | 2 + src/languages/en.js | 2 + src/languages/zh-TW.js | 1 - src/layouts/Layout.vue | 26 ++- src/mixins/datetime.js | 17 ++ src/mixins/socket.js | 36 ++++- src/pages/Dashboard.vue | 1 + src/pages/DashboardHome.vue | 22 ++- src/pages/Details.vue | 4 + src/pages/EditMaintenance.vue | 247 +++++++++++++++++++++++++++++ src/pages/EditMonitor.vue | 2 +- src/pages/MaintenanceDetails.vue | 141 ++++++++++++++++ src/pages/StatusPage.vue | 64 +++++++- src/router.js | 22 ++- src/util.js | 10 +- src/util.ts | 8 +- 32 files changed, 1121 insertions(+), 51 deletions(-) create mode 100644 db/patch-maintenance-table.sql create mode 100644 server/model/maintenance.js create mode 100644 src/pages/EditMaintenance.vue create mode 100644 src/pages/MaintenanceDetails.vue diff --git a/db/patch-maintenance-table.sql b/db/patch-maintenance-table.sql new file mode 100644 index 00000000..ee4a7f88 --- /dev/null +++ b/db/patch-maintenance-table.sql @@ -0,0 +1,25 @@ +-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. +BEGIN TRANSACTION; + +CREATE TABLE maintenance +( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + title VARCHAR(150), + description TEXT, + user_id INTEGER REFERENCES user ON UPDATE CASCADE ON DELETE SET NULL, + start_date DATETIME, + end_date DATETIME +); + +CREATE TABLE monitor_maintenance +( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + monitor_id INTEGER NOT NULL, + maintenance_id INTEGER NOT NULL, + CONSTRAINT FK_maintenance FOREIGN KEY (maintenance_id) REFERENCES maintenance (id) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT FK_monitor FOREIGN KEY (monitor_id) REFERENCES monitor (id) ON DELETE CASCADE ON UPDATE CASCADE +); + +create index maintenance_user_id on maintenance (user_id); + +COMMIT; diff --git a/package-lock.json b/package-lock.json index fc21a63f..7ab00b75 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14914,7 +14914,8 @@ "@fortawesome/vue-fontawesome": { "version": "3.0.0-5", "resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-3.0.0-5.tgz", - "integrity": "sha512-aNmBT4bOecrFsZTog1l6AJDQHPP3ocXV+WQ3Ogy8WZCqstB/ahfhH4CPu5i4N9Hw0MBKXqE+LX+NbUxcj8cVTw==" + "integrity": "sha512-aNmBT4bOecrFsZTog1l6AJDQHPP3ocXV+WQ3Ogy8WZCqstB/ahfhH4CPu5i4N9Hw0MBKXqE+LX+NbUxcj8cVTw==", + "requires": {} }, "@gar/promisify": { "version": "1.1.2", @@ -16117,7 +16118,8 @@ "version": "1.9.4", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.9.4.tgz", "integrity": "sha512-0CZqaCoChriPTTtGkERy1LGPcYjGFpi2uYRhBPIkqJqUGV5JnJFhQAgh6oH9j5XZHfrRaisX8W0xSpO4T7S78A==", - "dev": true + "dev": true, + "requires": {} }, "@vue/compiler-core": { "version": "3.2.22", @@ -16277,7 +16279,8 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true + "dev": true, + "requires": {} }, "acorn-walk": { "version": "7.2.0", @@ -16766,7 +16769,8 @@ "bootstrap": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.3.tgz", - "integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==" + "integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==", + "requires": {} }, "brace-expansion": { "version": "1.1.11", @@ -16958,7 +16962,8 @@ "chartjs-adapter-dayjs": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/chartjs-adapter-dayjs/-/chartjs-adapter-dayjs-1.0.0.tgz", - "integrity": "sha512-EnbVqTJGFKLpg1TROLdCEufrzbmIa2oeLGx8O2Wdjw2EoMudoOo9+YFu+6CM0Z0hQ/v3yq/e/Y6efQMu22n8Jg==" + "integrity": "sha512-EnbVqTJGFKLpg1TROLdCEufrzbmIa2oeLGx8O2Wdjw2EoMudoOo9+YFu+6CM0Z0hQ/v3yq/e/Y6efQMu22n8Jg==", + "requires": {} }, "check-password-strength": { "version": "2.0.3", @@ -17548,7 +17553,8 @@ "ws": { "version": "8.2.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==" + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "requires": {} } } }, @@ -17571,7 +17577,8 @@ "ws": { "version": "8.2.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==" + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "requires": {} } } }, @@ -20015,7 +20022,8 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true + "dev": true, + "requires": {} }, "jest-puppeteer": { "version": "6.0.0", @@ -21774,12 +21782,14 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", - "dev": true + "dev": true, + "requires": {} }, "postcss-scss": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.2.tgz", - "integrity": "sha512-xfdkU128CkKKKVAwkyt0M8OdnelJ3MRcIRAPPQkRpoPeuzWY3RIeg7piRCpZ79MK7Q16diLXMMAD9dN5mauPlQ==" + "integrity": "sha512-xfdkU128CkKKKVAwkyt0M8OdnelJ3MRcIRAPPQkRpoPeuzWY3RIeg7piRCpZ79MK7Q16diLXMMAD9dN5mauPlQ==", + "requires": {} }, "postcss-selector-parser": { "version": "6.0.8", @@ -21979,7 +21989,8 @@ "version": "7.4.6", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", - "dev": true + "dev": true, + "requires": {} } } }, @@ -23080,7 +23091,8 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-6.0.0.tgz", "integrity": "sha512-ZorSSdyMcxWpROYUvLEMm0vSZud2uB7tX1hzBZwvVY9SV/uly4AvvJPPhCcymZL3fcQhEQG5AELmrxWqtmzacw==", - "dev": true + "dev": true, + "requires": {} }, "stylelint-config-standard": { "version": "24.0.0", @@ -23653,17 +23665,20 @@ "vue-confirm-dialog": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/vue-confirm-dialog/-/vue-confirm-dialog-1.0.2.tgz", - "integrity": "sha512-gTo1bMDWOLd/6ihmWv8VlPxhc9QaKoE5YqlsKydUOfrrQ3Q3taljF6yI+1TMtAtJLrvZ8DYrePhgBhY1VCJzbQ==" + "integrity": "sha512-gTo1bMDWOLd/6ihmWv8VlPxhc9QaKoE5YqlsKydUOfrrQ3Q3taljF6yI+1TMtAtJLrvZ8DYrePhgBhY1VCJzbQ==", + "requires": {} }, "vue-contenteditable": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/vue-contenteditable/-/vue-contenteditable-3.0.4.tgz", - "integrity": "sha512-CmtqT4zHQwLoJEyNVaXUjjUFPUVYlXXBHfSbRCHCUjODMqrn6L293LM1nc1ELx8epitZZvecTfIqOLlSzB3d+w==" + "integrity": "sha512-CmtqT4zHQwLoJEyNVaXUjjUFPUVYlXXBHfSbRCHCUjODMqrn6L293LM1nc1ELx8epitZZvecTfIqOLlSzB3d+w==", + "requires": {} }, "vue-demi": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.10.1.tgz", - "integrity": "sha512-L6Oi+BvmMv6YXvqv5rJNCFHEKSVu7llpWWJczqmAQYOdmPPw5PNYoz1KKS//Fxhi+4QP64dsPjtmvnYGo1jemA==" + "integrity": "sha512-L6Oi+BvmMv6YXvqv5rJNCFHEKSVu7llpWWJczqmAQYOdmPPw5PNYoz1KKS//Fxhi+4QP64dsPjtmvnYGo1jemA==", + "requires": {} }, "vue-eslint-parser": { "version": "7.11.0", @@ -23735,7 +23750,8 @@ "vue-demi": { "version": "0.11.4", "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.11.4.tgz", - "integrity": "sha512-/3xFwzSykLW2HiiLie43a+FFgNOcokbBJ+fzvFXd0r2T8MYohqvphUyDQ8lbAwzQ3Dlcrb1c9ykifGkhSIAk6A==" + "integrity": "sha512-/3xFwzSykLW2HiiLie43a+FFgNOcokbBJ+fzvFXd0r2T8MYohqvphUyDQ8lbAwzQ3Dlcrb1c9ykifGkhSIAk6A==", + "requires": {} } } }, @@ -23750,7 +23766,8 @@ "vue-toastification": { "version": "2.0.0-rc.5", "resolved": "https://registry.npmjs.org/vue-toastification/-/vue-toastification-2.0.0-rc.5.tgz", - "integrity": "sha512-q73e5jy6gucEO/U+P48hqX+/qyXDozAGmaGgLFm5tXX4wJBcVsnGp4e/iJqlm9xzHETYOilUuwOUje2Qg1JdwA==" + "integrity": "sha512-q73e5jy6gucEO/U+P48hqX+/qyXDozAGmaGgLFm5tXX4wJBcVsnGp4e/iJqlm9xzHETYOilUuwOUje2Qg1JdwA==", + "requires": {} }, "vuedraggable": { "version": "4.1.0", @@ -23929,7 +23946,8 @@ "version": "7.5.5", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz", "integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==", - "dev": true + "dev": true, + "requires": {} }, "xml-name-validator": { "version": "3.0.0", diff --git a/server/database.js b/server/database.js index afcace70..6645e537 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-maintenance-table.sql": true, } /** diff --git a/server/model/heartbeat.js b/server/model/heartbeat.js index e0a77c06..617ac598 100644 --- a/server/model/heartbeat.js +++ b/server/model/heartbeat.js @@ -10,6 +10,7 @@ const { BeanModel } = require("redbean-node/dist/bean-model"); * 0 = DOWN * 1 = UP * 2 = PENDING + * 3 = MAINTENANCE */ class Heartbeat extends BeanModel { diff --git a/server/model/maintenance.js b/server/model/maintenance.js new file mode 100644 index 00000000..4958a203 --- /dev/null +++ b/server/model/maintenance.js @@ -0,0 +1,38 @@ +const dayjs = require("dayjs"); +const utc = require("dayjs/plugin/utc"); +let timezone = require("dayjs/plugin/timezone"); +dayjs.extend(utc); +dayjs.extend(timezone); +const { BeanModel } = require("redbean-node/dist/bean-model"); + +class Maintenance extends BeanModel { + + /** + * Return a object that ready to parse to JSON for public + * Only show necessary data to public + */ + async toPublicJSON() { + return { + id: this.id, + title: this.title, + description: this.description, + start_date: this.start_date, + end_date: this.end_date + }; + } + + /** + * Return a object that ready to parse to JSON + */ + async toJSON() { + return { + id: this.id, + title: this.title, + description: this.description, + start_date: this.start_date, + end_date: this.end_date + }; + } +} + +module.exports = Maintenance; diff --git a/server/model/monitor.js b/server/model/monitor.js index c4441d63..cd62ec6b 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -6,7 +6,7 @@ dayjs.extend(utc); dayjs.extend(timezone); const axios = require("axios"); const { Prometheus } = require("../prometheus"); -const { debug, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util"); +const { debug, UP, DOWN, PENDING, MAINTENANCE, flipStatus, TimeLogger} = require("../../src/util"); const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, errorLog } = require("../util-server"); const { R } = require("redbean-node"); const { BeanModel } = require("redbean-node/dist/bean-model"); @@ -20,6 +20,7 @@ const apicache = require("../modules/apicache"); * 0 = DOWN * 1 = UP * 2 = PENDING + * 3 = MAINTENANCE */ class Monitor extends BeanModel { @@ -28,9 +29,12 @@ class Monitor extends BeanModel { * Only show necessary data to public */ async toPublicJSON() { + const maintenance = await R.getAll("SELECT mm.*, maintenance.start_date, maintenance.end_date FROM monitor_maintenance mm JOIN maintenance ON mm.maintenance_id = maintenance.id WHERE mm.monitor_id = ? AND datetime(maintenance.start_date) <= datetime('now', 'localtime') AND datetime(maintenance.end_date) >= datetime('now', 'localtime')", [this.id]); + return { id: this.id, name: this.name, + maintenance: (maintenance.length !== 0), }; } @@ -50,6 +54,7 @@ class Monitor extends BeanModel { } const tags = await R.getAll("SELECT mt.*, tag.name, tag.color FROM monitor_tag mt JOIN tag ON mt.tag_id = tag.id WHERE mt.monitor_id = ?", [this.id]); + const maintenance = await R.getAll("SELECT mm.*, maintenance.start_date, maintenance.end_date FROM monitor_maintenance mm JOIN maintenance ON mm.maintenance_id = maintenance.id WHERE mm.monitor_id = ? AND datetime(maintenance.start_date) <= datetime('now', 'localtime') AND datetime(maintenance.end_date) >= datetime('now', 'localtime')", [this.id]); return { id: this.id, @@ -79,6 +84,7 @@ class Monitor extends BeanModel { pushToken: this.pushToken, notificationIDList, tags: tags, + maintenance: (maintenance.length !== 0), }; } @@ -136,6 +142,8 @@ class Monitor extends BeanModel { bean.time = R.isoDateTime(dayjs.utc()); bean.status = DOWN; + const maintenance = await R.getAll("SELECT mm.*, maintenance.start_date, maintenance.end_date FROM monitor_maintenance mm JOIN maintenance ON mm.maintenance_id = maintenance.id WHERE mm.monitor_id = ? AND datetime(maintenance.start_date) <= datetime('now', 'localtime') AND datetime(maintenance.end_date) >= datetime('now', 'localtime')", [this.id]); + if (this.isUpsideDown()) { bean.status = flipStatus(bean.status); } @@ -148,7 +156,11 @@ class Monitor extends BeanModel { } try { - if (this.type === "http" || this.type === "keyword") { + if (maintenance.length !== 0) { + bean.msg = "Monitor under maintenance"; + bean.status = MAINTENANCE; + } + else if (this.type === "http" || this.type === "keyword") { // Do not do any queries/high loading things before the "bean.ping" let startTime = dayjs().valueOf(); @@ -387,8 +399,13 @@ class Monitor extends BeanModel { if (isImportant) { bean.important = true; - debug(`[${this.name}] sendNotification`); - await Monitor.sendNotification(isFirstBeat, this, bean); + if (Monitor.isImportantForNotification(isFirstBeat, previousBeat?.status, bean.status)) { + debug(`[${this.name}] sendNotification`); + await Monitor.sendNotification(isFirstBeat, this, bean); + } + else { + debug(`[${this.name}] will not sendNotification because it is (or was) under maintenance`); + } // Clear Status Page Cache debug(`[${this.name}] apicache clear`); @@ -405,6 +422,8 @@ class Monitor extends BeanModel { beatInterval = this.retryInterval; } console.warn(`Monitor #${this.id} '${this.name}': Pending: ${bean.msg} | Max retries: ${this.maxretries} | Retry: ${retries} | Retry Interval: ${beatInterval} seconds | Type: ${this.type}`); + } else if (bean.status === MAINTENANCE) { + console.warn(`Monitor #${this.id} '${this.name}': Under Maintenance | Type: ${this.type}`); } else { console.warn(`Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Interval: ${beatInterval} seconds | Type: ${this.type}`); } @@ -598,7 +617,7 @@ class Monitor extends BeanModel { -- SUM all uptime duration, also trim off the beat out of time window SUM( CASE - WHEN (status = 1) + WHEN (status = 1 OR status = 3) THEN CASE WHEN (JULIANDAY(\`time\`) - JULIANDAY(?)) * 86400 < duration @@ -659,11 +678,42 @@ class Monitor extends BeanModel { // DOWN -> PENDING = this case not exists // DOWN -> DOWN = not important // * DOWN -> UP = important - let isImportant = isFirstBeat || + // MAINTENANCE -> MAINTENANCE = not important + // * MAINTENANCE -> UP = important + // * MAINTENANCE -> DOWN = important + // * DOWN -> MAINTENANCE = important + // * UP -> MAINTENANCE = important + return isFirstBeat || + (previousBeatStatus === DOWN && currentBeatStatus === MAINTENANCE) || + (previousBeatStatus === UP && currentBeatStatus === MAINTENANCE) || + (previousBeatStatus === MAINTENANCE && currentBeatStatus === DOWN) || + (previousBeatStatus === MAINTENANCE && currentBeatStatus === UP) || + (previousBeatStatus === UP && currentBeatStatus === DOWN) || + (previousBeatStatus === DOWN && currentBeatStatus === UP) || + (previousBeatStatus === PENDING && currentBeatStatus === DOWN); + } + + static isImportantForNotification(isFirstBeat, previousBeatStatus, currentBeatStatus) { + // * ? -> ANY STATUS = important [isFirstBeat] + // UP -> PENDING = not important + // * UP -> DOWN = important + // UP -> UP = not important + // PENDING -> PENDING = not important + // * PENDING -> DOWN = important + // PENDING -> UP = not important + // DOWN -> PENDING = this case not exists + // DOWN -> DOWN = not important + // * DOWN -> UP = important + // MAINTENANCE -> MAINTENANCE = not important + // MAINTENANCE -> UP = not important + // * MAINTENANCE -> DOWN = important + // DOWN -> MAINTENANCE = not important + // UP -> MAINTENANCE = not important + return isFirstBeat || + (previousBeatStatus === MAINTENANCE && currentBeatStatus === DOWN) || (previousBeatStatus === UP && currentBeatStatus === DOWN) || (previousBeatStatus === DOWN && currentBeatStatus === UP) || (previousBeatStatus === PENDING && currentBeatStatus === DOWN); - return isImportant; } static async sendNotification(isFirstBeat, monitor, bean) { diff --git a/server/routers/api-router.js b/server/routers/api-router.js index 1920cef7..19e4fcad 100644 --- a/server/routers/api-router.js +++ b/server/routers/api-router.js @@ -5,7 +5,7 @@ const server = require("../server"); const apicache = require("../modules/apicache"); const Monitor = require("../model/monitor"); const dayjs = require("dayjs"); -const { UP, flipStatus, debug } = require("../../src/util"); +const { UP, MAINTENANCE, flipStatus, debug} = require("../../src/util"); let router = express.Router(); let cache = apicache.middleware; @@ -51,6 +51,12 @@ router.get("/api/push/:pushToken", async (request, response) => { duration = dayjs(bean.time).diff(dayjs(previousHeartbeat.time), "second"); } + const maintenance = await R.getAll("SELECT mm.*, maintenance.start_date, maintenance.end_date FROM monitor_maintenance mm JOIN maintenance ON mm.maintenance_id = maintenance.id WHERE mm.monitor_id = ? AND datetime(maintenance.start_date) <= datetime('now', 'localtime') AND datetime(maintenance.end_date) >= datetime('now', 'localtime')", [monitor.id]); + if (maintenance.length !== 0) { + msg = "Monitor under maintenance"; + status = MAINTENANCE; + } + debug("PreviousStatus: " + previousStatus); debug("Current Status: " + status); @@ -70,7 +76,7 @@ router.get("/api/push/:pushToken", async (request, response) => { ok: true, }); - if (bean.important) { + if (Monitor.isImportantForNotification(isFirstBeat, previousStatus, status)) { await Monitor.sendNotification(isFirstBeat, monitor, bean); } @@ -131,6 +137,34 @@ router.get("/api/status-page/incident", async (_, response) => { } }); +// Status Page - Maintenance List +// Can fetch only if published +router.get("/api/status-page/maintenance-list", async (_request, response) => { + allowDevAllOrigin(response); + + try { + await checkPublished(); + const publicMaintenanceList = []; + + let maintenanceBeanList = R.convertToBeans("maintenance", await R.getAll(` + SELECT maintenance.* + FROM maintenance + WHERE datetime(maintenance.start_date) <= datetime('now', 'localtime') + AND datetime(maintenance.end_date) >= datetime('now', 'localtime') + ORDER BY maintenance.end_date + `)); + + for (const bean of maintenanceBeanList) { + publicMaintenanceList.push(await bean.toPublicJSON()); + } + + response.json(publicMaintenanceList); + + } catch (error) { + send403(response, error.message); + } +}); + // Status Page - Monitor List // Can fetch only if published router.get("/api/status-page/monitor-list", cache("5 minutes"), async (_request, response) => { diff --git a/server/server.js b/server/server.js index 153cac4f..2b6933d7 100644 --- a/server/server.js +++ b/server/server.js @@ -132,6 +132,7 @@ const { sendNotificationList, sendHeartbeatList, sendImportantHeartbeatList, sen const { statusPageSocketHandler } = require("./socket-handlers/status-page-socket-handler"); const databaseSocketHandler = require("./socket-handlers/database-socket-handler"); const TwoFA = require("./2fa"); +const apicache = require("./modules/apicache"); app.use(express.json()); @@ -162,6 +163,12 @@ let jwtSecret = null; */ let monitorList = {}; +/** +* Main maintenance list +* @type {{}} +*/ +let maintenanceList = {}; + /** * Show Setup Page * @type {boolean} @@ -625,6 +632,101 @@ exports.entryPage = "dashboard"; } }); + // Add a new maintenance + socket.on("addMaintenance", async (maintenance, callback) => { + try { + checkLogin(socket); + let bean = R.dispense("maintenance"); + + bean.import(maintenance); + bean.user_id = socket.userID; + let maintenanceID = await R.store(bean); + + await sendMaintenanceList(socket); + + callback({ + ok: true, + msg: "Added Successfully.", + maintenanceID, + }); + + } catch (e) { + callback({ + ok: false, + msg: e.message, + }); + } + }); + + // Edit a maintenance + socket.on("editMaintenance", async (maintenance, callback) => { + try { + checkLogin(socket); + + let bean = await R.findOne("maintenance", " id = ? ", [ maintenance.id ]); + + if (bean.user_id !== socket.userID) { + throw new Error("Permission denied."); + } + + bean.title = maintenance.title; + bean.description = maintenance.description; + bean.start_date = maintenance.start_date; + bean.end_date = maintenance.end_date; + + await R.store(bean); + + await sendMaintenanceList(socket); + + callback({ + ok: true, + msg: "Saved.", + maintenanceID: bean.id, + }); + + } catch (e) { + console.error(e); + callback({ + ok: false, + msg: e.message, + }); + } + }); + + // Add a new monitor_maintenance + socket.on("addMonitorMaintenance", async (maintenanceID, monitors, callback) => { + try { + checkLogin(socket); + + await R.exec("DELETE FROM monitor_maintenance WHERE maintenance_id = ?", [ + maintenanceID + ]); + + for await (const monitor of monitors) { + let bean = R.dispense("monitor_maintenance"); + + bean.import({ + monitor_id: monitor.id, + maintenance_id: maintenanceID + }); + await R.store(bean); + } + + apicache.clear(); + + callback({ + ok: true, + msg: "Added Successfully.", + }); + + } catch (e) { + callback({ + ok: false, + msg: e.message, + }); + } + }); + socket.on("getMonitorList", async (callback) => { try { checkLogin(socket); @@ -641,6 +743,22 @@ exports.entryPage = "dashboard"; } }); + socket.on("getMaintenanceList", async (callback) => { + try { + checkLogin(socket); + await sendMaintenanceList(socket); + callback({ + ok: true, + }); + } catch (e) { + console.error(e); + callback({ + ok: false, + msg: e.message, + }); + } + }); + socket.on("getMonitor", async (monitorID, callback) => { try { checkLogin(socket); @@ -665,6 +783,54 @@ exports.entryPage = "dashboard"; } }); + socket.on("getMaintenance", async (maintenanceID, callback) => { + try { + checkLogin(socket); + + console.log(`Get Maintenance: ${maintenanceID} User ID: ${socket.userID}`); + + let bean = await R.findOne("maintenance", " id = ? AND user_id = ? ", [ + maintenanceID, + socket.userID, + ]); + + callback({ + ok: true, + maintenance: await bean.toJSON(), + }); + + } catch (e) { + callback({ + ok: false, + msg: e.message, + }); + } + }); + + socket.on("getMonitorMaintenance", async (maintenanceID, callback) => { + try { + checkLogin(socket); + + console.log(`Get Monitors for Maintenance: ${maintenanceID} User ID: ${socket.userID}`); + + let monitors = await R.getAll("SELECT monitor.id, monitor.name FROM monitor_maintenance mm JOIN monitor ON mm.monitor_id = monitor.id WHERE mm.maintenance_id = ? ", [ + maintenanceID, + ]); + + callback({ + ok: true, + monitors, + }); + + } catch (e) { + console.error(e); + callback({ + ok: false, + msg: e.message, + }); + } + }); + socket.on("getMonitorBeats", async (monitorID, period, callback) => { try { checkLogin(socket); @@ -769,6 +935,36 @@ exports.entryPage = "dashboard"; } }); + socket.on("deleteMaintenance", async (maintenanceID, callback) => { + try { + checkLogin(socket); + + console.log(`Delete Maintenance: ${maintenanceID} User ID: ${socket.userID}`); + + if (maintenanceID in maintenanceList) { + delete maintenanceList[maintenanceID]; + } + + await R.exec("DELETE FROM maintenance WHERE id = ? AND user_id = ? ", [ + maintenanceID, + socket.userID, + ]); + + callback({ + ok: true, + msg: "Deleted Successfully.", + }); + + await sendMaintenanceList(socket); + + } catch (e) { + callback({ + ok: false, + msg: e.message, + }); + } + }); + socket.on("getTags", async (callback) => { try { checkLogin(socket); @@ -1394,11 +1590,18 @@ async function sendMonitorList(socket) { return list; } +async function sendMaintenanceList(socket) { + let list = await getMaintenanceJSONList(socket.userID); + io.to(socket.userID).emit("maintenanceList", list); + return list; +} + async function afterLogin(socket, user) { socket.userID = user.id; socket.join(user.id); let monitorList = await sendMonitorList(socket); + sendMaintenanceList(socket); sendNotificationList(socket); await sleep(500); @@ -1430,6 +1633,20 @@ async function getMonitorJSONList(userID) { return result; } +async function getMaintenanceJSONList(userID) { + let result = {}; + + let maintenanceList = await R.find("maintenance", " user_id = ? ORDER BY end_date DESC, title", [ + userID, + ]); + + for (let maintenance of maintenanceList) { + result[maintenance.id] = await maintenance.toJSON(); + } + + return result; +} + async function initDatabase(testMode = false) { if (! fs.existsSync(Database.path)) { console.log("Copying Database"); diff --git a/src/assets/app.scss b/src/assets/app.scss index cec64467..73b9d631 100644 --- a/src/assets/app.scss +++ b/src/assets/app.scss @@ -273,6 +273,7 @@ textarea.form-control { &.bg-info, &.bg-warning, &.bg-danger, + &.bg-maintenance, &.bg-light { color: $dark-font-color2; } diff --git a/src/assets/vars.scss b/src/assets/vars.scss index 91ab917e..e48a6efb 100644 --- a/src/assets/vars.scss +++ b/src/assets/vars.scss @@ -1,6 +1,7 @@ $primary: #5cdd8b; $danger: #dc3545; $warning: #f8a306; +$maintenance: #1747f5; $link-color: #111; $border-radius: 50rem; diff --git a/src/components/HeartbeatBar.vue b/src/components/HeartbeatBar.vue index be0b122e..abeed7cb 100644 --- a/src/components/HeartbeatBar.vue +++ b/src/components/HeartbeatBar.vue @@ -5,7 +5,7 @@ v-for="(beat, index) in shortBeatList" :key="index" class="beat" - :class="{ 'empty' : (beat === 0), 'down' : (beat.status === 0), 'pending' : (beat.status === 2) }" + :class="{ 'empty' : (beat === 0), 'down' : (beat.status === 0), 'pending' : (beat.status === 2), 'maintenance' : (beat.status === 3) }" :style="beatStyle" :title="getBeatTitle(beat)" /> @@ -200,6 +200,10 @@ export default { background-color: $warning; } + &.maintenance { + background-color: $maintenance; + } + &:not(.empty):hover { transition: all ease-in-out 0.15s; opacity: 0.8; diff --git a/src/components/MonitorList.vue b/src/components/MonitorList.vue index ef51e89c..d943efff 100644 --- a/src/components/MonitorList.vue +++ b/src/components/MonitorList.vue @@ -1,7 +1,12 @@ @@ -282,7 +298,6 @@ export default { margin-top: 5px; } - .bg-maintenance { background-color: $maintenance; } diff --git a/src/components/Uptime.vue b/src/components/Uptime.vue index 226dfae3..d663db88 100644 --- a/src/components/Uptime.vue +++ b/src/components/Uptime.vue @@ -15,7 +15,7 @@ export default { computed: { uptime() { - + if (this.type === "maintenance") { return this.$t("Maintenance"); } @@ -31,9 +31,9 @@ export default { color() { if (this.type === "maintenance" || this.monitor.maintenance) { - return "maintenance" + return "maintenance"; } - + if (this.lastHeartBeat.status === 0) { return "danger"; } diff --git a/src/mixins/datetime.js b/src/mixins/datetime.js index 3f4749af..c6461562 100644 --- a/src/mixins/datetime.js +++ b/src/mixins/datetime.js @@ -34,10 +34,11 @@ export default { const inputDate = new Date(value); const now = new Date(Date.now()); - if (inputDate.getFullYear() === now.getUTCFullYear() && inputDate.getMonth() === now.getUTCMonth() && inputDate.getDay() === now.getUTCDay()) + if (inputDate.getFullYear() === now.getUTCFullYear() && inputDate.getMonth() === now.getUTCMonth() && inputDate.getDay() === now.getUTCDay()) { return this.datetimeFormat(value, "HH:mm"); - else + } else { return this.datetimeFormat(value, "YYYY-MM-DD HH:mm"); + } }, date(value) { diff --git a/src/mixins/socket.js b/src/mixins/socket.js index 8d419706..6d4311b1 100644 --- a/src/mixins/socket.js +++ b/src/mixins/socket.js @@ -464,8 +464,7 @@ export default { text: this.$t("Maintenance"), color: "maintenance", }; - } - else if (! lastHeartBeat) { + } else if (! lastHeartBeat) { result[monitorID] = unknown; } else if (lastHeartBeat.status === 1) { result[monitorID] = { @@ -505,8 +504,7 @@ export default { if (monitor && monitor.maintenance) { result.maintenance++; - } - else if (monitor && ! monitor.active) { + } else if (monitor && ! monitor.active) { result.pause++; } else if (beat) { if (beat.status === 1) { diff --git a/src/pages/Dashboard.vue b/src/pages/Dashboard.vue index e99422a4..207e5b3a 100644 --- a/src/pages/Dashboard.vue +++ b/src/pages/Dashboard.vue @@ -8,12 +8,12 @@
@@ -78,7 +89,7 @@ @@ -251,7 +339,7 @@ textarea { min-height: 200px; } -.darkCalendar::-webkit-calendar-picker-indicator { +.dark-calendar::-webkit-calendar-picker-indicator { filter: invert(1); } diff --git a/src/pages/MaintenanceDetails.vue b/src/pages/MaintenanceDetails.vue index 4f9779f6..ae4b5a7b 100644 --- a/src/pages/MaintenanceDetails.vue +++ b/src/pages/MaintenanceDetails.vue @@ -8,7 +8,7 @@ {{ $t("End") }}: {{ $root.datetimeMaintenance(maintenance.end_date) }}

-
+
{{ $t("Edit") }} @@ -17,14 +17,21 @@
- + - +
- +
+ + +
+ {{ $t("deleteMaintenanceMsg") }} @@ -45,6 +52,7 @@ export default { data() { return { affectedMonitors: [], + selectedStatusPages: [], }; }, computed: { @@ -65,6 +73,14 @@ export default { toast.error(res.msg); } }); + + this.$root.getSocket().emit("getMaintenanceStatusPage", this.$route.params.id, (res) => { + if (res.ok) { + this.selectedStatusPages = Object.values(res.statusPages).map(statusPage => statusPage.title); + } else { + toast.error(res.msg); + } + }); }, deleteDialog() { diff --git a/src/pages/StatusPage.vue b/src/pages/StatusPage.vue index 015a57be..4d021dc4 100644 --- a/src/pages/StatusPage.vue +++ b/src/pages/StatusPage.vue @@ -247,7 +247,7 @@
- + {{ $t("Maintenance") }}
@@ -595,7 +595,7 @@ export default { } this.incident = res.data.incident; - this.maintenance = res.data.maintenance; + this.maintenance = res.data.maintenance || []; this.$root.publicGroupList = res.data.publicGroupList; }); @@ -956,7 +956,7 @@ footer { background-color: #0d1117; } -.statusMaintenance { +.status-maintenance { color: $maintenance; margin-right: 5px; } From 98ee9caf2cdc54a2f5edb864906d620e84196317 Mon Sep 17 00:00:00 2001 From: OidaTiftla Date: Thu, 5 May 2022 15:55:33 +0200 Subject: [PATCH 030/803] Add variable for currentTime Co-authored-by: Adam Stachowicz --- 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 Date: Thu, 5 May 2022 16:01:10 +0200 Subject: [PATCH 031/803] 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 ed218e73bb17d5f3895bf56aa4d994498ebeedb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Kr=C3=BDda?= Date: Sun, 8 May 2022 20:03:24 +0200 Subject: [PATCH 032/803] UI improvements --- src/components/MonitorList.vue | 9 +++++---- src/icon.js | 2 ++ src/languages/en.js | 3 +-- src/pages/Dashboard.vue | 29 +++++++++++++++++++++++++++-- src/pages/MaintenanceDetails.vue | 8 ++++++-- 5 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src/components/MonitorList.vue b/src/components/MonitorList.vue index 8662dfee..01f1c0fa 100644 --- a/src/components/MonitorList.vue +++ b/src/components/MonitorList.vue @@ -1,10 +1,11 @@ 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 041/803] 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 042/803] 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 043/803] 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 @@
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
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 045/803] 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 @@
-
-

*{{ $t("Required") }}

-
Date: Mon, 13 Jun 2022 01:30:27 +0800 Subject: [PATCH 046/803] 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 047/803] Update server/notification-providers/bark.js Co-authored-by: Adam Stachowicz --- 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 048/803] Update server/notification-providers/bark.js Co-authored-by: Adam Stachowicz --- 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 049/803] 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 050/803] 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 Date: Wed, 15 Jun 2022 16:56:26 +0200 Subject: [PATCH 051/803] Rename feature to: Resend Notification if Down X times consequently Co-authored-by: Louis Lam --- 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 @@
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 052/803] 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 @@ + 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 053/803] 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 42e30de2099f95f3076d65f78c5e517ff56da952 Mon Sep 17 00:00:00 2001 From: Thomas Christlieb Date: Mon, 4 Jul 2022 10:16:33 +0200 Subject: [PATCH 054/803] change page title to " - Login" when on Login Form --- src/components/Login.vue | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/components/Login.vue b/src/components/Login.vue index 3a821881..0a0266e7 100644 --- a/src/components/Login.vue +++ b/src/components/Login.vue @@ -54,6 +54,15 @@ export default { tokenRequired: false, }; }, + + mounted() { + document.title += " - Login"; + }, + + unmounted() { + document.title = document.title.replace(" - Login", ""); + }, + methods: { /** Submit the user details and attempt to log in */ submit() { From 219b00f660790f04285224a33fc36d70b38ad620 Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Wed, 13 Jul 2022 23:08:35 +0100 Subject: [PATCH 055/803] [empty commit] pull request for #1891 set ping size From a54e58b4d6bc5f6eabe161fbdc0709dfadd69189 Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Thu, 14 Jul 2022 08:32:51 +0100 Subject: [PATCH 056/803] Added Ping packet size #1891 This should fully implement #1891 by adding an extra field to the edit monitor page and an extra column to the database. The user can now set the size of the packet to send, it defaults to 56. A maximum limit of 65500 was chosen to ensure that the total size of the packet does not exceed the IPv4 maximum packet size and to comply with the limit imposed by Windows. Signed-off-by: Matthew Nickson --- db/patch-ping-packet-size.sql | 5 +++++ server/database.js | 1 + server/model/monitor.js | 5 +++-- server/ping-lite.js | 8 ++++---- server/server.js | 1 + server/util-server.js | 13 ++++++++----- src/languages/en.js | 1 + src/pages/EditMonitor.vue | 7 +++++++ 8 files changed, 30 insertions(+), 11 deletions(-) create mode 100644 db/patch-ping-packet-size.sql diff --git a/db/patch-ping-packet-size.sql b/db/patch-ping-packet-size.sql new file mode 100644 index 00000000..d65ec8ed --- /dev/null +++ b/db/patch-ping-packet-size.sql @@ -0,0 +1,5 @@ +-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. +BEGIN TRANSACTION; +ALTER TABLE monitor + ADD packet_size INTEGER DEFAULT 56 NOT NULL; +COMMIT; diff --git a/server/database.js b/server/database.js index 00fd48d9..3d5508f7 100644 --- a/server/database.js +++ b/server/database.js @@ -61,6 +61,7 @@ class Database { "patch-add-clickable-status-page-link.sql": true, "patch-add-sqlserver-monitor.sql": true, "patch-add-other-auth.sql": { parents: [ "patch-monitor-basic-auth.sql" ] }, + "patch-ping-packet-size.sql": true, }; /** diff --git a/server/model/monitor.js b/server/model/monitor.js index b8733a0b..6ce7322b 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -82,6 +82,7 @@ class Monitor extends BeanModel { expiryNotification: this.isEnabledExpiryNotification(), ignoreTls: this.getIgnoreTls(), upsideDown: this.isUpsideDown(), + packetSize: this.packetSize, maxredirects: this.maxredirects, accepted_statuscodes: this.getAcceptedStatuscodes(), dns_resolve_type: this.dns_resolve_type, @@ -352,7 +353,7 @@ class Monitor extends BeanModel { bean.status = UP; } else if (this.type === "ping") { - bean.ping = await ping(this.hostname); + bean.ping = await ping(this.hostname, this.packetSize); bean.msg = ""; bean.status = UP; } else if (this.type === "dns") { @@ -459,7 +460,7 @@ class Monitor extends BeanModel { bean.msg = res.data.response.servers[0].name; try { - bean.ping = await ping(this.hostname); + bean.ping = await ping(this.hostname, this.packetSize); } catch (_) { } } else { throw new Error("Server not found on Steam"); diff --git a/server/ping-lite.js b/server/ping-lite.js index b7d003b8..c1686dd9 100644 --- a/server/ping-lite.js +++ b/server/ping-lite.js @@ -28,13 +28,13 @@ function Ping(host, options) { if (util.WIN) { this._bin = "c:/windows/system32/ping.exe"; - this._args = (options.args) ? options.args : [ "-n", "1", "-w", timeout * 1000, host ]; + this._args = (options.args) ? options.args : [ "-n", "1", "-w", timeout * 1000, "-l", this._options.size, host ]; this._regmatch = /[><=]([0-9.]+?)ms/; } else if (util.LIN) { this._bin = "/bin/ping"; - const defaultArgs = [ "-n", "-w", timeout, "-c", "1", host ]; + const defaultArgs = [ "-n", "-w", timeout, "-c", "1", "-s", this._options.size, host ]; if (net.isIPv6(host) || options.ipv6) { defaultArgs.unshift("-6"); @@ -51,13 +51,13 @@ function Ping(host, options) { this._bin = "/sbin/ping"; } - this._args = (options.args) ? options.args : [ "-n", "-t", timeout, "-c", "1", host ]; + this._args = (options.args) ? options.args : [ "-n", "-t", timeout, "-c", "1", "-s", this._options.size, host ]; this._regmatch = /=([0-9.]+?) ms/; } else if (util.BSD) { this._bin = "/sbin/ping"; - const defaultArgs = [ "-n", "-t", timeout, "-c", "1", host ]; + const defaultArgs = [ "-n", "-t", timeout, "-c", "1", "-s", this._options.size, host ]; if (net.isIPv6(host) || options.ipv6) { defaultArgs.unshift("-6"); diff --git a/server/server.js b/server/server.js index 2d3f37ee..72adbb2a 100644 --- a/server/server.js +++ b/server/server.js @@ -659,6 +659,7 @@ let needSetup = false; bean.ignoreTls = monitor.ignoreTls; bean.expiryNotification = monitor.expiryNotification; bean.upsideDown = monitor.upsideDown; + bean.packetSize = monitor.packetSize; bean.maxredirects = monitor.maxredirects; bean.accepted_statuscodes_json = JSON.stringify(monitor.accepted_statuscodes); bean.dns_resolve_type = monitor.dns_resolve_type; diff --git a/server/util-server.js b/server/util-server.js index f6a0e396..bc49a6c7 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -70,15 +70,16 @@ exports.tcping = function (hostname, port) { /** * Ping the specified machine * @param {string} hostname Hostname / address of machine + * @param {number} [size=56] Size of packet to send * @returns {Promise} Time for ping in ms rounded to nearest integer */ -exports.ping = async (hostname) => { +exports.ping = async (hostname, size = 56) => { try { - return await exports.pingAsync(hostname); + return await exports.pingAsync(hostname, false, size ); } catch (e) { // If the host cannot be resolved, try again with ipv6 if (e.message.includes("service not known")) { - return await exports.pingAsync(hostname, true); + return await exports.pingAsync(hostname, true, size); } else { throw e; } @@ -89,12 +90,14 @@ exports.ping = async (hostname) => { * Ping the specified machine * @param {string} hostname Hostname / address of machine to ping * @param {boolean} ipv6 Should IPv6 be used? + * @param {number} [size=56] Size of ping packet to send * @returns {Promise} Time for ping in ms rounded to nearest integer */ -exports.pingAsync = function (hostname, ipv6 = false) { +exports.pingAsync = function (hostname, ipv6 = false, size = 56) { return new Promise((resolve, reject) => { const ping = new Ping(hostname, { - ipv6 + ipv6, + size }); ping.send(function (err, ms, stdout) { diff --git a/src/languages/en.js b/src/languages/en.js index 9aeedd9d..4f3b2abb 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.", + "Packet Size": "Packet Size", }; diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index f8791d3f..737afa70 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -230,6 +230,12 @@
+ +
+ + +
+ @@ -357,6 +356,9 @@ export default { }, methods: { init() { + // Use browser's timezone! + let timezone = dayjs.tz.guess(); + this.affectedMonitors = []; this.selectedStatusPages = []; @@ -380,10 +382,8 @@ export default { daysOfMonth: [], }; } else if (this.isEdit) { - this.$root.getSocket().emit("getMaintenance", this.$route.params.id, (res) => { + this.$root.getSocket().emit("getMaintenance", this.$route.params.id, timezone, (res) => { if (res.ok) { - res.maintenance.start_date = this.$root.datetimeFormat(res.maintenance.start_date, "YYYY-MM-DDTHH:mm"); - res.maintenance.end_date = this.$root.datetimeFormat(res.maintenance.end_date, "YYYY-MM-DDTHH:mm"); this.maintenance = res.maintenance; this.$root.getSocket().emit("getMonitorMaintenance", this.$route.params.id, (res) => { @@ -441,8 +441,11 @@ export default { this.maintenance.end_date = this.$root.toUTC(this.maintenance.end_date); */ + // Use browser's timezone! + let timezone = dayjs.tz.guess(); + if (this.isAdd) { - this.$root.addMaintenance(this.maintenance, async (res) => { + this.$root.addMaintenance(this.maintenance, timezone, async (res) => { if (res.ok) { await this.addMonitorMaintenance(res.maintenanceID, async () => { await this.addMaintenanceStatusPage(res.maintenanceID, () => { @@ -459,7 +462,7 @@ export default { }); } else { - this.$root.getSocket().emit("editMaintenance", this.maintenance, async (res) => { + this.$root.getSocket().emit("editMaintenance", this.maintenance, timezone, async (res) => { if (res.ok) { await this.addMonitorMaintenance(res.maintenanceID, async () => { await this.addMaintenanceStatusPage(res.maintenanceID, () => { diff --git a/src/util.js b/src/util.js index 73f5369d..15427cdd 100644 --- a/src/util.js +++ b/src/util.js @@ -7,7 +7,7 @@ // Backend uses the compiled file util.js // Frontend uses util.ts Object.defineProperty(exports, "__esModule", { value: true }); -exports.parseTimeFormatFromVueDatePicker = exports.parseVueDatePickerTimeFormat = exports.getMaintenanceRelativeURL = exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.log = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.STATUS_PAGE_MAINTENANCE = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.MAINTENANCE = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0; +exports.parseTimeFromTimeObject = exports.parseTimeObject = exports.getMaintenanceRelativeURL = exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.log = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.STATUS_PAGE_MAINTENANCE = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.MAINTENANCE = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0; const _dayjs = require("dayjs"); const dayjs = _dayjs; exports.isDev = process.env.NODE_ENV === "development"; @@ -314,7 +314,7 @@ exports.getMaintenanceRelativeURL = getMaintenanceRelativeURL; * @param {string} time E.g. 12:00 * @returns object */ -function parseVueDatePickerTimeFormat(time) { +function parseTimeObject(time) { if (!time) { return { hours: 0, @@ -335,11 +335,11 @@ function parseVueDatePickerTimeFormat(time) { } return obj; } -exports.parseVueDatePickerTimeFormat = parseVueDatePickerTimeFormat; +exports.parseTimeObject = parseTimeObject; /** * @returns string e.g. 12:00 */ -function parseTimeFormatFromVueDatePicker(obj) { +function parseTimeFromTimeObject(obj) { if (!obj) { return obj; } @@ -350,4 +350,4 @@ function parseTimeFormatFromVueDatePicker(obj) { } return result; } -exports.parseTimeFormatFromVueDatePicker = parseTimeFormatFromVueDatePicker; +exports.parseTimeFromTimeObject = parseTimeFromTimeObject; diff --git a/src/util.ts b/src/util.ts index 92da0fd5..cb51250b 100644 --- a/src/util.ts +++ b/src/util.ts @@ -348,7 +348,7 @@ export function getMaintenanceRelativeURL(id: string) { * @param {string} time E.g. 12:00 * @returns object */ -export function parseVueDatePickerTimeFormat(time: string) { +export function parseTimeObject(time: string) { if (!time) { return { hours: 0, @@ -376,7 +376,7 @@ export function parseVueDatePickerTimeFormat(time: string) { /** * @returns string e.g. 12:00 */ -export function parseTimeFormatFromVueDatePicker(obj : any) { +export function parseTimeFromTimeObject(obj : any) { if (!obj) { return obj; } @@ -391,3 +391,4 @@ export function parseTimeFormatFromVueDatePicker(obj : any) { return result; } + From 4157c7d5463a7467c849dd71fd2d17b846b81c95 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 26 Sep 2022 22:16:34 +1300 Subject: [PATCH 168/803] Add support for Squadcast incoming webhook --- server/notification-providers/squadcast.js | 76 ++++++++++++++++++++++ server/notification.js | 2 + src/components/notifications/Squadcast.vue | 6 ++ src/components/notifications/index.js | 2 + src/languages/en.js | 1 + 5 files changed, 87 insertions(+) create mode 100644 server/notification-providers/squadcast.js create mode 100644 src/components/notifications/Squadcast.vue diff --git a/server/notification-providers/squadcast.js b/server/notification-providers/squadcast.js new file mode 100644 index 00000000..0302cb6f --- /dev/null +++ b/server/notification-providers/squadcast.js @@ -0,0 +1,76 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); +const { DOWN, UP } = require("../../src/util"); + +class Squadcast extends NotificationProvider { + + name = "squadcast"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully."; + + try { + + let config = {}; + let data = { + message: msg, + description: '', + tags: {}, + heartbeat: heartbeatJSON, + source: 'uptime-kuma' + } + + if (heartbeatJSON !== null) { + data.description = heartbeatJSON["msg"]; + data.event_id = heartbeatJSON["monitorID"]; + + if (heartbeatJSON["status"] === DOWN) { + data.message = `${monitorJSON['name']} is DOWN`; + data.status = "trigger"; + } else { + data.message = `${monitorJSON['name']} is UP`; + data.status = "resolve"; + } + + let address; + switch (monitorJSON["type"]) { + case "ping": + address = monitorJSON["hostname"]; + break; + case "port": + case "dns": + case "steam": + address = monitorJSON["hostname"]; + if (monitorJSON["port"]) { + address += ":" + monitorJSON["port"]; + } + break; + default: + address = monitorJSON["url"]; + break; + } + + data.tags["AlertAddress"] = address; + + monitorJSON["tags"].forEach(tag => { + data.tags[tag["name"]] = { + value: tag["value"] + }; + if (tag["color"] !== null) { + data.tags[tag["name"]]["color"] = tag["color"] + } + }); + } + + await axios.post(notification.squadcastWebhookURL, data, config); + return okMsg; + + } catch (error) { + this.throwGeneralAxiosError(error); + } + + } + +} + +module.exports = Squadcast; \ No newline at end of file diff --git a/server/notification.js b/server/notification.js index 3bf51243..7a4b4f29 100644 --- a/server/notification.js +++ b/server/notification.js @@ -32,6 +32,7 @@ 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 Squadcast = require("./notification-providers/squadcast"); const Stackfield = require("./notification-providers/stackfield"); const Teams = require("./notification-providers/teams"); const TechulusPush = require("./notification-providers/techulus-push"); @@ -87,6 +88,7 @@ class Notification { new SMSManager(), new Slack(), new SMTP(), + new Squadcast(), new Stackfield(), new Teams(), new TechulusPush(), diff --git a/src/components/notifications/Squadcast.vue b/src/components/notifications/Squadcast.vue new file mode 100644 index 00000000..6650c44d --- /dev/null +++ b/src/components/notifications/Squadcast.vue @@ -0,0 +1,6 @@ + diff --git a/src/components/notifications/index.js b/src/components/notifications/index.js index 6add06ea..319a7922 100644 --- a/src/components/notifications/index.js +++ b/src/components/notifications/index.js @@ -31,6 +31,7 @@ import SerwerSMS from "./SerwerSMS.vue"; import Signal from "./Signal.vue"; import SMSManager from "./SMSManager.vue"; import Slack from "./Slack.vue"; +import Squadcast from "./Squadcast.vue"; import Stackfield from "./Stackfield.vue"; import STMP from "./SMTP.vue"; import Teams from "./Teams.vue"; @@ -79,6 +80,7 @@ const NotificationFormList = { "signal": Signal, "SMSManager": SMSManager, "slack": Slack, + "squadcast": Squadcast, "smtp": STMP, "stackfield": Stackfield, "teams": Teams, diff --git a/src/languages/en.js b/src/languages/en.js index 7d980f63..1f20c7ea 100644 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -582,4 +582,5 @@ export default { goAlert: "GoAlert", backupOutdatedWarning: "Deprecated: Since a lot of features added and this backup feature is a bit unmaintained, it cannot generate or restore a complete backup.", backupRecommend: "Please backup the volume or the data folder (./data/) directly instead.", + squadcast: "Squadcast", }; From bef9cb6a5fb06dbd2fb76c6ce6214b3c3b54b45f Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 26 Sep 2022 22:30:43 +1300 Subject: [PATCH 169/803] Linting fixes --- server/notification-providers/squadcast.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/server/notification-providers/squadcast.js b/server/notification-providers/squadcast.js index 0302cb6f..15553ff7 100644 --- a/server/notification-providers/squadcast.js +++ b/server/notification-providers/squadcast.js @@ -1,6 +1,6 @@ const NotificationProvider = require("./notification-provider"); const axios = require("axios"); -const { DOWN, UP } = require("../../src/util"); +const { DOWN } = require("../../src/util"); class Squadcast extends NotificationProvider { @@ -14,21 +14,21 @@ class Squadcast extends NotificationProvider { let config = {}; let data = { message: msg, - description: '', + description: "", tags: {}, heartbeat: heartbeatJSON, - source: 'uptime-kuma' - } + source: "uptime-kuma" + }; if (heartbeatJSON !== null) { data.description = heartbeatJSON["msg"]; data.event_id = heartbeatJSON["monitorID"]; if (heartbeatJSON["status"] === DOWN) { - data.message = `${monitorJSON['name']} is DOWN`; + data.message = `${monitorJSON["name"]} is DOWN`; data.status = "trigger"; } else { - data.message = `${monitorJSON['name']} is UP`; + data.message = `${monitorJSON["name"]} is UP`; data.status = "resolve"; } @@ -57,7 +57,7 @@ class Squadcast extends NotificationProvider { value: tag["value"] }; if (tag["color"] !== null) { - data.tags[tag["name"]]["color"] = tag["color"] + data.tags[tag["name"]]["color"] = tag["color"]; } }); } @@ -73,4 +73,4 @@ class Squadcast extends NotificationProvider { } -module.exports = Squadcast; \ No newline at end of file +module.exports = Squadcast; From 5809088f27eb9604b043b782ac375f1feecc2e5a Mon Sep 17 00:00:00 2001 From: Justin Tisdale Date: Mon, 26 Sep 2022 15:52:43 -0400 Subject: [PATCH 170/803] Don't override a user-defined content-type header --- server/model/monitor.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/server/model/monitor.js b/server/model/monitor.js index 509b841c..c541ecf3 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -249,20 +249,28 @@ class Monitor extends BeanModel { log.debug("monitor", `[${this.name}] Prepare Options for axios`); - // Set content-type header and body values based on the httpBodyEncoding type selected - // TODO: Check if this.headers already contains a content-type header set by the user; if so, don't inject one - let bodyValue = null; - let contentType = null; + + // Check if this.headers already contains a content-type header set by the user; if so, don't inject one + let contentTypeUserDefinedHeader = this.headers.find(function(header) { + return header[0].toLowerCase() == "content-type"; + }); + + let contentType = contentTypeUserDefinedHeader ? + contentTypeUserDefinedHeader[1] : + null; + + let bodyValue = null; + if (this.body && !this.httpBodyEncoding || this.httpBodyEncoding === "json") { bodyValue = JSON.parse(this.body); - contentType = "application/json"; + contentType = contentType ? contentType : "application/json"; } else if (this.body && (this.httpBodyEncoding === "xml")) { bodyValue = this.body; - contentType = "text/xml"; + contentType = contentType ? contentType : "text/xml"; } else if (this.body && (this.httpBodyEncoding === "form")) { bodyValue = this.body; - contentType = "application/x-www-form-urlencoded"; + contentType = contentType ? contentType : "application/x-www-form-urlencoded"; } const options = { From 6537f4fe746c157c8a87ff25bbcefeb7a62522f3 Mon Sep 17 00:00:00 2001 From: Justin Tisdale Date: Mon, 26 Sep 2022 17:09:10 -0400 Subject: [PATCH 171/803] content-type change --- server/model/monitor.js | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/server/model/monitor.js b/server/model/monitor.js index c541ecf3..48b0b1d3 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -249,28 +249,18 @@ class Monitor extends BeanModel { log.debug("monitor", `[${this.name}] Prepare Options for axios`); - - - // Check if this.headers already contains a content-type header set by the user; if so, don't inject one - let contentTypeUserDefinedHeader = this.headers.find(function(header) { - return header[0].toLowerCase() == "content-type"; - }); - - let contentType = contentTypeUserDefinedHeader ? - contentTypeUserDefinedHeader[1] : - null; - + let contentType = null; let bodyValue = null; if (this.body && !this.httpBodyEncoding || this.httpBodyEncoding === "json") { bodyValue = JSON.parse(this.body); - contentType = contentType ? contentType : "application/json"; + contentType = "application/json"; } else if (this.body && (this.httpBodyEncoding === "xml")) { bodyValue = this.body; - contentType = contentType ? contentType : "text/xml"; + contentType = "text/xml"; } else if (this.body && (this.httpBodyEncoding === "form")) { bodyValue = this.body; - contentType = contentType ? contentType : "application/x-www-form-urlencoded"; + contentType = "application/x-www-form-urlencoded"; } const options = { From f6919aef1d3b59dd23229feef6f2815004f34a80 Mon Sep 17 00:00:00 2001 From: Justin Tisdale Date: Mon, 26 Sep 2022 17:10:56 -0400 Subject: [PATCH 172/803] remove TODO --- src/pages/EditMonitor.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index ba667670..ea246c60 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -741,7 +741,6 @@ export default { * @returns {boolean} Is the form input valid? */ isInputValid() { - //TODO: Handle validation for all 3 possible options + null value if (this.monitor.body && (!this.monitor.httpBodyEncoding || this.monitor.httpBodyEncoding === "json")) { try { JSON.parse(this.monitor.body); From 4002b9f57787220eda4863b76417bbc33aaed81a Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Tue, 27 Sep 2022 20:44:44 +0800 Subject: [PATCH 173/803] [WIP] Checking maintenance time using maintenance_timeslot table --- server/model/maintenance_timeslot.js | 46 +++++++++++++++++++ server/model/monitor.js | 12 ++++- server/model/status_page.js | 18 ++++---- .../maintenance-socket-handler.js | 3 ++ src/components/Status.vue | 2 +- src/components/Uptime.vue | 2 +- src/languages/en.js | 1 + src/languages/zh-HK.js | 2 + src/mixins/socket.js | 2 +- 9 files changed, 75 insertions(+), 13 deletions(-) create mode 100644 server/model/maintenance_timeslot.js diff --git a/server/model/maintenance_timeslot.js b/server/model/maintenance_timeslot.js new file mode 100644 index 00000000..f749caa5 --- /dev/null +++ b/server/model/maintenance_timeslot.js @@ -0,0 +1,46 @@ +const { BeanModel } = require("redbean-node/dist/bean-model"); +const { R } = require("redbean-node"); +const dayjs = require("dayjs"); + +class MaintenanceTimeslot extends BeanModel { + + async toPublicJSON() { + + } + + async toJSON() { + + } + + /** + * + * @param {Maintenance} maintenance + * @param {dayjs} startFrom (For recurring type only) Generate Timeslot from this date, if it is smaller than the current date, it will use the current date instead. As generating a passed timeslot is meaningless. + * @param {boolean} removeExist Remove existing timeslot before create + * @returns {Promise} + */ + static async generateTimeslot(maintenance, startFrom = null, removeExist = false) { + if (!startFrom) { + startFrom = dayjs(); + } + + if (removeExist) { + await R.exec("DELETE FROM maintenance_timeslot WHERE maintenance_id = ? ", [ + maintenance.id + ]); + } + + if (maintenance.strategy === "single") { + let bean = R.dispense("maintenance_timeslot"); + bean.maintenance_id = maintenance.id; + bean.start_date = maintenance.start_datetime; + bean.end_date = maintenance.end_datetime; + bean.generated_next = true; + await R.store(bean); + } else { + throw new Error("Unknown maintenance strategy"); + } + } +} + +module.exports = MaintenanceTimeslot; diff --git a/server/model/monitor.js b/server/model/monitor.js index 5a74215e..a13d7051 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -1105,7 +1105,17 @@ class Monitor extends BeanModel { * @returns {Promise} */ static async isUnderMaintenance(monitorID) { - const maintenance = await R.getRow("SELECT COUNT(*) AS count FROM monitor_maintenance mm JOIN maintenance ON mm.maintenance_id = maintenance.id WHERE mm.monitor_id = ? AND datetime(maintenance.start_date) <= datetime('now') AND datetime(maintenance.end_date) >= datetime('now') LIMIT 1", [ monitorID ]); + const maintenance = await R.getRow(` + SELECT COUNT(*) AS count + FROM monitor_maintenance mm + JOIN maintenance + ON mm.maintenance_id = maintenance.id + JOIN maintenance_timeslot + ON maintenance_timeslot.maintenance_id = maintenance.id + WHERE mm.monitor_id = ? + AND maintenance_timeslot.start_date <= DATETIME('now') + AND maintenance_timeslot.end_date >= DATETIME('now') + LIMIT 1`, [ monitorID ]); return maintenance.count !== 0; } } diff --git a/server/model/status_page.js b/server/model/status_page.js index d296470d..4351db58 100644 --- a/server/model/status_page.js +++ b/server/model/status_page.js @@ -272,15 +272,15 @@ class StatusPage extends BeanModel { const publicMaintenanceList = []; let maintenanceBeanList = R.convertToBeans("maintenance", await R.getAll(` - SELECT m.* - FROM maintenance m - JOIN maintenance_status_page msp - ON msp.maintenance_id = m.id - WHERE datetime(m.start_date) <= datetime('now') - AND datetime(m.end_date) >= datetime('now') - AND msp.status_page_id = ? - ORDER BY m.end_date - `, [ statusPageId ])); + SELECT m.* + FROM maintenance m, maintenance_status_page msp, maintenance_timeslot + WHERE msp.maintenance_id = m.id + AND maintenance_timeslot.maintenance.id = m.id + AND maintenance_timeslot.start_date <= DATETIME('now') + AND maintenance_timeslot.end_date >= DATETIME('now') + AND msp.status_page_id = ? + ORDER BY m.end_date + `, [ statusPageId ])); for (const bean of maintenanceBeanList) { publicMaintenanceList.push(await bean.toPublicJSON()); diff --git a/server/socket-handlers/maintenance-socket-handler.js b/server/socket-handlers/maintenance-socket-handler.js index 604f07bd..5358b53e 100644 --- a/server/socket-handlers/maintenance-socket-handler.js +++ b/server/socket-handlers/maintenance-socket-handler.js @@ -8,6 +8,7 @@ const server = UptimeKumaServer.getInstance(); const dayjs = require("dayjs"); const utc = require("dayjs/plugin/utc"); let timezone = require("dayjs/plugin/timezone"); +const MaintenanceTimeslot = require("../model/maintenance_timeslot"); dayjs.extend(utc); dayjs.extend(timezone); @@ -26,6 +27,7 @@ module.exports.maintenanceSocketHandler = (socket) => { let bean = Maintenance.jsonToBean(R.dispense("maintenance"), maintenance, timezone); bean.user_id = socket.userID; let maintenanceID = await R.store(bean); + await MaintenanceTimeslot.generateTimeslot(bean); await server.sendMaintenanceList(socket); @@ -57,6 +59,7 @@ module.exports.maintenanceSocketHandler = (socket) => { Maintenance.jsonToBean(bean, maintenance, timezone); await R.store(bean); + await MaintenanceTimeslot.generateTimeslot(bean, null, true); await server.sendMaintenanceList(socket); diff --git a/src/components/Status.vue b/src/components/Status.vue index 391fb6d5..92ed8a6b 100644 --- a/src/components/Status.vue +++ b/src/components/Status.vue @@ -47,7 +47,7 @@ export default { } if (this.status === 3) { - return this.$t("Maintenance"); + return this.$t("statusMaintenance"); } return this.$t("Unknown"); diff --git a/src/components/Uptime.vue b/src/components/Uptime.vue index f089d4c1..8565975c 100644 --- a/src/components/Uptime.vue +++ b/src/components/Uptime.vue @@ -26,7 +26,7 @@ export default { uptime() { if (this.type === "maintenance") { - return this.$t("Maintenance"); + return this.$t("statusMaintenance"); } let key = this.monitor.id + "_" + this.type; diff --git a/src/languages/en.js b/src/languages/en.js index e77a31f4..fdcaf98e 100644 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -10,6 +10,7 @@ export default { maxRedirectDescription: "Maximum number of redirects to follow. Set to 0 to disable redirects.", acceptedStatusCodesDescription: "Select status codes which are considered as a successful response.", Maintenance: "Maintenance", + statusMaintenance: "Maintenance", "Schedule maintenance": "Schedule maintenance", "Affected Monitors": "Affected Monitors", "Pick Affected Monitors...": "Pick Affected Monitors...", diff --git a/src/languages/zh-HK.js b/src/languages/zh-HK.js index a55f4fb6..cd82be84 100644 --- a/src/languages/zh-HK.js +++ b/src/languages/zh-HK.js @@ -380,4 +380,6 @@ export default { proxyDescription: "必須將代理伺服器指派給監測器才能運作。", enableProxyDescription: "此代理伺服器在啟用前不會在監測器上生效,您可以藉由控制啟用狀態來暫時對所有的監測器停用代理伺服器。", setAsDefaultProxyDescription: "預設情況下,新監測器將啟用此代理伺服器。您仍可分別停用各監測器的代理伺服器。", + Maintenance: "維護", + statusMaintenance: "維護中", }; diff --git a/src/mixins/socket.js b/src/mixins/socket.js index 6da6ee64..74522ffe 100644 --- a/src/mixins/socket.js +++ b/src/mixins/socket.js @@ -588,7 +588,7 @@ export default { if (this.monitorList[monitorID].maintenance) { result[monitorID] = { - text: this.$t("Maintenance"), + text: this.$t("statusMaintenance"), color: "maintenance", }; } else if (! lastHeartBeat) { From b1465c0282dde17c34171b3978c96cdac2c7dace Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Wed, 28 Sep 2022 00:20:17 +0800 Subject: [PATCH 174/803] - Maintenance standardize datetime format to YYYY-MM-DD hh:mm:ss - Import dayjs extensions one time only - Maintenance activeCondition centralize --- server/model/heartbeat.js | 4 -- server/model/maintenance.js | 38 +++++++++++++------ server/model/maintenance_timeslot.js | 5 ++- server/model/monitor.js | 9 ++--- server/model/status_page.js | 9 +++-- server/server.js | 5 +++ .../maintenance-socket-handler.js | 4 -- server/util-server.js | 4 -- src/components/Datetime.vue | 6 --- src/components/PingChart.vue | 6 +-- src/components/settings/General.vue | 4 -- src/main.js | 7 ++++ src/mixins/datetime.js | 6 --- src/util-frontend.js | 5 --- 14 files changed, 51 insertions(+), 61 deletions(-) diff --git a/server/model/heartbeat.js b/server/model/heartbeat.js index 8c9d8645..fa02cae8 100644 --- a/server/model/heartbeat.js +++ b/server/model/heartbeat.js @@ -1,8 +1,4 @@ const dayjs = require("dayjs"); -const utc = require("dayjs/plugin/utc"); -let timezone = require("dayjs/plugin/timezone"); -dayjs.extend(utc); -dayjs.extend(timezone); const { BeanModel } = require("redbean-node/dist/bean-model"); /** diff --git a/server/model/maintenance.js b/server/model/maintenance.js index 3b07d5f7..945e4d97 100644 --- a/server/model/maintenance.js +++ b/server/model/maintenance.js @@ -1,12 +1,9 @@ const dayjs = require("dayjs"); -const utc = require("dayjs/plugin/utc"); -let timezone = require("dayjs/plugin/timezone"); -dayjs.extend(utc); -dayjs.extend(timezone); const { BeanModel } = require("redbean-node/dist/bean-model"); const { parseTimeObject, parseTimeFromTimeObject } = require("../../src/util"); const { isArray } = require("chart.js/helpers"); const { timeObjectToUTC, timeObjectToLocal } = require("../util-server"); +const { R } = require("redbean-node"); class Maintenance extends BeanModel { @@ -20,17 +17,18 @@ class Maintenance extends BeanModel { let dateTimeRange = []; if (this.start_datetime) { - dateTimeRange.push( this.start_datetime); + + dateTimeRange.push(dayjs.utc(this.start_datetime).toISOString()); if (this.end_datetime) { - dateTimeRange.push( this.end_datetime); + dateTimeRange.push(dayjs.utc(this.end_datetime).toISOString()); } } let dateRange = []; if (this.start_date) { - dateRange.push( this.start_date); + dateRange.push(dayjs.utc(this.start_date).toISOString()); if (this.end_date) { - dateRange.push( this.end_date); + dateRange.push(dayjs.utc(this.end_date).toISOString()); } } @@ -106,18 +104,18 @@ class Maintenance extends BeanModel { bean.active = obj.active; if (obj.dateRange[0]) { - bean.start_date = obj.dateRange[0]; + bean.start_date = R.isoDate(dayjs(obj.dateRange[0]).utc()); if (obj.dateRange[1]) { - bean.end_date = obj.dateRange[1]; + bean.end_date = R.isoDate(dayjs(obj.dateRange[1]).utc()); } } if (obj.dateTimeRange[0]) { - bean.start_datetime = obj.dateTimeRange[0]; + bean.start_datetime = R.isoDateTime(dayjs(obj.dateTimeRange[0]).utc()); if (obj.dateTimeRange[1]) { - bean.end_datetime = obj.dateTimeRange[1]; + bean.end_datetime = R.isoDateTime(dayjs(obj.dateTimeRange[1]).utc()); } } @@ -129,6 +127,22 @@ class Maintenance extends BeanModel { return bean; } + + /** + * SQL conditions for active maintenance + * @returns {string} + */ + static getActiveMaintenanceSQLCondition() { + return ` + + (maintenance_timeslot.start_date <= DATETIME('now') + AND maintenance_timeslot.end_date >= DATETIME('now') + AND maintenance.active = 1) + AND + (maintenance.strategy = 'manual' AND active = 1) + + `; + } } module.exports = Maintenance; diff --git a/server/model/maintenance_timeslot.js b/server/model/maintenance_timeslot.js index f749caa5..7917dd69 100644 --- a/server/model/maintenance_timeslot.js +++ b/server/model/maintenance_timeslot.js @@ -1,6 +1,7 @@ const { BeanModel } = require("redbean-node/dist/bean-model"); const { R } = require("redbean-node"); const dayjs = require("dayjs"); +const { log } = require("../../src/util"); class MaintenanceTimeslot extends BeanModel { @@ -30,7 +31,9 @@ class MaintenanceTimeslot extends BeanModel { ]); } - if (maintenance.strategy === "single") { + if (maintenance.strategy === "manual") { + log.debug("maintenance", "No need to generate timeslot for manual type"); + } else if (maintenance.strategy === "single") { let bean = R.dispense("maintenance_timeslot"); bean.maintenance_id = maintenance.id; bean.start_date = maintenance.start_datetime; diff --git a/server/model/monitor.js b/server/model/monitor.js index a13d7051..9df127b0 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -1,9 +1,5 @@ const https = require("https"); const dayjs = require("dayjs"); -const utc = require("dayjs/plugin/utc"); -let timezone = require("dayjs/plugin/timezone"); -dayjs.extend(utc); -dayjs.extend(timezone); const axios = require("axios"); const { Prometheus } = require("../prometheus"); const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, TimeLogger } = require("../../src/util"); @@ -17,6 +13,7 @@ const version = require("../../package.json").version; const apicache = require("../modules/apicache"); const { UptimeKumaServer } = require("../uptime-kuma-server"); const { CacheableDnsHttpAgent } = require("../cacheable-dns-http-agent"); +const Maintenance = require("./maintenance"); /** * status: @@ -1105,6 +1102,7 @@ class Monitor extends BeanModel { * @returns {Promise} */ static async isUnderMaintenance(monitorID) { + let activeCondition = Maintenance.getActiveMaintenanceSQLCondition(); const maintenance = await R.getRow(` SELECT COUNT(*) AS count FROM monitor_maintenance mm @@ -1113,8 +1111,7 @@ class Monitor extends BeanModel { JOIN maintenance_timeslot ON maintenance_timeslot.maintenance_id = maintenance.id WHERE mm.monitor_id = ? - AND maintenance_timeslot.start_date <= DATETIME('now') - AND maintenance_timeslot.end_date >= DATETIME('now') + AND ${activeCondition} LIMIT 1`, [ monitorID ]); return maintenance.count !== 0; } diff --git a/server/model/status_page.js b/server/model/status_page.js index 4351db58..0620a1ee 100644 --- a/server/model/status_page.js +++ b/server/model/status_page.js @@ -2,6 +2,7 @@ const { BeanModel } = require("redbean-node/dist/bean-model"); const { R } = require("redbean-node"); const cheerio = require("cheerio"); const { UptimeKumaServer } = require("../uptime-kuma-server"); +const Maintenance = require("./maintenance"); class StatusPage extends BeanModel { @@ -271,14 +272,14 @@ class StatusPage extends BeanModel { try { const publicMaintenanceList = []; + let activeCondition = Maintenance.getActiveMaintenanceSQLCondition(); let maintenanceBeanList = R.convertToBeans("maintenance", await R.getAll(` SELECT m.* FROM maintenance m, maintenance_status_page msp, maintenance_timeslot WHERE msp.maintenance_id = m.id - AND maintenance_timeslot.maintenance.id = m.id - AND maintenance_timeslot.start_date <= DATETIME('now') - AND maintenance_timeslot.end_date >= DATETIME('now') - AND msp.status_page_id = ? + AND maintenance_timeslot.maintenance.id = m.id + AND msp.status_page_id = ? + AND ${activeCondition} ORDER BY m.end_date `, [ statusPageId ])); diff --git a/server/server.js b/server/server.js index 4aec2b27..1ad99899 100644 --- a/server/server.js +++ b/server/server.js @@ -33,6 +33,11 @@ log.info("server", "Importing Node libraries"); const fs = require("fs"); log.info("server", "Importing 3rd-party libraries"); + +const dayjs = require("dayjs"); +dayjs.extend(require("dayjs/plugin/utc")); +dayjs.extend(require("dayjs/plugin/timezone")); + log.debug("server", "Importing express"); const express = require("express"); const expressStaticGzip = require("express-static-gzip"); diff --git a/server/socket-handlers/maintenance-socket-handler.js b/server/socket-handlers/maintenance-socket-handler.js index 5358b53e..9ae36b5c 100644 --- a/server/socket-handlers/maintenance-socket-handler.js +++ b/server/socket-handlers/maintenance-socket-handler.js @@ -6,11 +6,7 @@ const { UptimeKumaServer } = require("../uptime-kuma-server"); const Maintenance = require("../model/maintenance"); const server = UptimeKumaServer.getInstance(); const dayjs = require("dayjs"); -const utc = require("dayjs/plugin/utc"); -let timezone = require("dayjs/plugin/timezone"); const MaintenanceTimeslot = require("../model/maintenance_timeslot"); -dayjs.extend(utc); -dayjs.extend(timezone); /** * Handlers for Maintenance diff --git a/server/util-server.js b/server/util-server.js index cc5e478d..1c5f5914 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -22,10 +22,6 @@ const { }, } = require("node-radius-utils"); const dayjs = require("dayjs"); -const utc = require("dayjs/plugin/utc"); -let timezone = require("dayjs/plugin/timezone"); -dayjs.extend(utc); -dayjs.extend(timezone); // From ping-lite exports.WIN = /^win/.test(process.platform); diff --git a/src/components/Datetime.vue b/src/components/Datetime.vue index b24ab0b3..84bae503 100644 --- a/src/components/Datetime.vue +++ b/src/components/Datetime.vue @@ -4,12 +4,6 @@ + + diff --git a/src/languages/en.js b/src/languages/en.js index 4bf92e92..4c7338d2 100644 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -206,6 +206,8 @@ export default { "Content Type": "Content Type", webhookJsonDesc: "{0} is good for any modern HTTP servers such as Express.js", webhookFormDataDesc: "{multipart} is good for PHP. The JSON will need to be parsed with {decodeFunction}", + webhookAdditionalHeadersTitle: "Additional Headers", + webhookAdditionalHeadersDesc: "Sets additional headers sent with the webhook.", smtp: "Email (SMTP)", secureOptionNone: "None / STARTTLS (25, 587)", secureOptionTLS: "TLS (465)", From 4a7e96f9ea6235d797b3a0f68b8300d82aadfea6 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Thu, 6 Oct 2022 12:08:10 +0800 Subject: [PATCH 210/803] Init C# Project for building exe --- .dockerignore | 5 +- .gitignore | 3 + extra/exe-builder/App.config | 10 ++ extra/exe-builder/Program.cs | 59 +++++++++ extra/exe-builder/Properties/AssemblyInfo.cs | 36 ++++++ .../Properties/Resources.Designer.cs | 62 ++++++++++ extra/exe-builder/Properties/Resources.resx | 117 ++++++++++++++++++ .../Properties/Settings.Designer.cs | 23 ++++ .../exe-builder/Properties/Settings.settings | 7 ++ extra/exe-builder/UptimeKuma.csproj | 79 ++++++++++++ extra/exe-builder/UptimeKuma.sln | 16 +++ .../UptimeKuma.sln.DotSettings.user | 3 + 12 files changed, 418 insertions(+), 2 deletions(-) create mode 100644 extra/exe-builder/App.config create mode 100644 extra/exe-builder/Program.cs create mode 100644 extra/exe-builder/Properties/AssemblyInfo.cs create mode 100644 extra/exe-builder/Properties/Resources.Designer.cs create mode 100644 extra/exe-builder/Properties/Resources.resx create mode 100644 extra/exe-builder/Properties/Settings.Designer.cs create mode 100644 extra/exe-builder/Properties/Settings.settings create mode 100644 extra/exe-builder/UptimeKuma.csproj create mode 100644 extra/exe-builder/UptimeKuma.sln create mode 100644 extra/exe-builder/UptimeKuma.sln.DotSettings.user diff --git a/.dockerignore b/.dockerignore index babc429a..22b71b38 100644 --- a/.dockerignore +++ b/.dockerignore @@ -31,6 +31,7 @@ tsconfig.json /tmp /babel.config.js /ecosystem.config.js +extra/exe-builder ### .gitignore content (commented rules are duplicated) @@ -45,6 +46,6 @@ dist-ssr #!/data/.gitkeep #.vscode - - ### End of .gitignore content + + diff --git a/.gitignore b/.gitignore index 8eb05867..8e0edeac 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,6 @@ dist-ssr cypress/videos cypress/screenshots + +extra/exe-builder/bin +extra/exe-builder/obj diff --git a/extra/exe-builder/App.config b/extra/exe-builder/App.config new file mode 100644 index 00000000..09265d8b --- /dev/null +++ b/extra/exe-builder/App.config @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/extra/exe-builder/Program.cs b/extra/exe-builder/Program.cs new file mode 100644 index 00000000..5516a1ff --- /dev/null +++ b/extra/exe-builder/Program.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using System.Windows.Forms; +using UptimeKuma.Properties; + +namespace UptimeKuma { + static class Program { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new UptimeKumaApplicationContext()); + } + } + + public class UptimeKumaApplicationContext : ApplicationContext + { + private NotifyIcon trayIcon; + + public UptimeKumaApplicationContext() + { + // Initialize Tray Icon + trayIcon = new NotifyIcon(); + + trayIcon.Icon = Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location); + trayIcon.ContextMenu = new ContextMenu(new MenuItem[] { + new MenuItem("Check for Update", CheckForUpdate), + new MenuItem("About", About), + new MenuItem("Exit", Exit), + }); + + trayIcon.Visible = true; + } + + void Exit(object sender, EventArgs e) + { + // Hide tray icon, otherwise it will remain shown until user mouses over it + trayIcon.Visible = false; + Application.Exit(); + } + + void About(object sender, EventArgs e) + { + MessageBox.Show("Uptime Kuma v1.0.0" + Environment.NewLine + "© 2022 Louis Lam", "Info"); + } + + void CheckForUpdate(object sneder, EventArgs e) { + + } + } +} + diff --git a/extra/exe-builder/Properties/AssemblyInfo.cs b/extra/exe-builder/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..2552870b --- /dev/null +++ b/extra/exe-builder/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Uptime Kuma")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Uptime Kuma")] +[assembly: AssemblyCopyright("Copyright © 2022 Louis Lam")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("2DB53988-1D93-4AC0-90C4-96ADEAAC5C04")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/extra/exe-builder/Properties/Resources.Designer.cs b/extra/exe-builder/Properties/Resources.Designer.cs new file mode 100644 index 00000000..8c8e559c --- /dev/null +++ b/extra/exe-builder/Properties/Resources.Designer.cs @@ -0,0 +1,62 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace UptimeKuma.Properties { + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", + "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", + "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState + .Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if ((resourceMan == null)) { + global::System.Resources.ResourceManager temp = + new global::System.Resources.ResourceManager("UptimeKuma.Properties.Resources", + typeof(Resources).Assembly); + resourceMan = temp; + } + + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState + .Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { return resourceCulture; } + set { resourceCulture = value; } + } + } +} \ No newline at end of file diff --git a/extra/exe-builder/Properties/Resources.resx b/extra/exe-builder/Properties/Resources.resx new file mode 100644 index 00000000..ffecec85 --- /dev/null +++ b/extra/exe-builder/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/extra/exe-builder/Properties/Settings.Designer.cs b/extra/exe-builder/Properties/Settings.Designer.cs new file mode 100644 index 00000000..6c63b395 --- /dev/null +++ b/extra/exe-builder/Properties/Settings.Designer.cs @@ -0,0 +1,23 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace UptimeKuma.Properties { + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute( + "Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + private static Settings defaultInstance = + ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { return defaultInstance; } + } + } +} \ No newline at end of file diff --git a/extra/exe-builder/Properties/Settings.settings b/extra/exe-builder/Properties/Settings.settings new file mode 100644 index 00000000..abf36c5d --- /dev/null +++ b/extra/exe-builder/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/extra/exe-builder/UptimeKuma.csproj b/extra/exe-builder/UptimeKuma.csproj new file mode 100644 index 00000000..d62166e4 --- /dev/null +++ b/extra/exe-builder/UptimeKuma.csproj @@ -0,0 +1,79 @@ + + + + + Debug + AnyCPU + {2DB53988-1D93-4AC0-90C4-96ADEAAC5C04} + WinExe + UptimeKuma + uptime-kuma + v4.7.2 + 512 + true + true + ..\..\public\favicon.ico + 10 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + favicon.ico + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + \ No newline at end of file diff --git a/extra/exe-builder/UptimeKuma.sln b/extra/exe-builder/UptimeKuma.sln new file mode 100644 index 00000000..201d7e23 --- /dev/null +++ b/extra/exe-builder/UptimeKuma.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UptimeKuma", "UptimeKuma.csproj", "{2DB53988-1D93-4AC0-90C4-96ADEAAC5C04}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2DB53988-1D93-4AC0-90C4-96ADEAAC5C04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2DB53988-1D93-4AC0-90C4-96ADEAAC5C04}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2DB53988-1D93-4AC0-90C4-96ADEAAC5C04}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2DB53988-1D93-4AC0-90C4-96ADEAAC5C04}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/extra/exe-builder/UptimeKuma.sln.DotSettings.user b/extra/exe-builder/UptimeKuma.sln.DotSettings.user new file mode 100644 index 00000000..b4ca9dad --- /dev/null +++ b/extra/exe-builder/UptimeKuma.sln.DotSettings.user @@ -0,0 +1,3 @@ + + True + True \ No newline at end of file From c24b64921d08b6d5c0cfc060fe35e43ecdbce20b Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Thu, 6 Oct 2022 23:28:06 +0800 Subject: [PATCH 211/803] Fix #2183 ntfy issue --- server/notification-providers/ntfy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/notification-providers/ntfy.js b/server/notification-providers/ntfy.js index 17d6d812..5381da46 100644 --- a/server/notification-providers/ntfy.js +++ b/server/notification-providers/ntfy.js @@ -9,7 +9,7 @@ class Ntfy extends NotificationProvider { let okMsg = "Sent Successfully."; try { let headers = {}; - if (notification.ntfyusername.length > 0) { + if (notification.ntfyusername) { headers = { "Authorization": "Basic " + Buffer.from(notification.ntfyusername + ":" + notification.ntfypassword).toString("base64"), }; From da778f05ac30a681eeb71735dd21fc86eaef4a23 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Fri, 7 Oct 2022 00:16:07 +0800 Subject: [PATCH 212/803] Update --- extra/exe-builder/Program.cs | 42 ++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/extra/exe-builder/Program.cs b/extra/exe-builder/Program.cs index 5516a1ff..840bc873 100644 --- a/extra/exe-builder/Program.cs +++ b/extra/exe-builder/Program.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Drawing; using System.Linq; using System.Reflection; @@ -23,6 +24,7 @@ namespace UptimeKuma { public class UptimeKumaApplicationContext : ApplicationContext { private NotifyIcon trayIcon; + private Process process; public UptimeKumaApplicationContext() { @@ -31,19 +33,41 @@ namespace UptimeKuma { trayIcon.Icon = Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location); trayIcon.ContextMenu = new ContextMenu(new MenuItem[] { + new MenuItem("Open", Open), new MenuItem("Check for Update", CheckForUpdate), new MenuItem("About", About), new MenuItem("Exit", Exit), }); trayIcon.Visible = true; + + var startInfo = new ProcessStartInfo(); + startInfo.FileName = "node/node.exe"; + startInfo.Arguments = "server/server.js"; + startInfo.RedirectStandardOutput = true; + startInfo.RedirectStandardError = true; + startInfo.UseShellExecute = false; + startInfo.CreateNoWindow = true; + startInfo.WorkingDirectory = "core"; + + process = new Process(); + process.StartInfo = startInfo; + process.EnableRaisingEvents = true; + try { + process.Start(); + Open(null, null); + } catch (Exception e) { + MessageBox.Show("Startup failed: " + e.Message, "Uptime Kuma Error"); + throw; + } } - void Exit(object sender, EventArgs e) - { - // Hide tray icon, otherwise it will remain shown until user mouses over it - trayIcon.Visible = false; - Application.Exit(); + void Open(object sender, EventArgs e) { + Process.Start("http://localhost:3001"); + } + + void CheckForUpdate(object sender, EventArgs e) { + } void About(object sender, EventArgs e) @@ -51,8 +75,12 @@ namespace UptimeKuma { MessageBox.Show("Uptime Kuma v1.0.0" + Environment.NewLine + "© 2022 Louis Lam", "Info"); } - void CheckForUpdate(object sneder, EventArgs e) { - + void Exit(object sender, EventArgs e) + { + // Hide tray icon, otherwise it will remain shown until user mouses over it + trayIcon.Visible = false; + process.Close(); + Application.Exit(); } } } From 60460442f869f2cc581690bd7c8930b3eb7777b8 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Fri, 7 Oct 2022 00:25:34 +0800 Subject: [PATCH 213/803] Update to 1.18.3 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 1411168d..df0bdbfb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uptime-kuma", - "version": "1.18.2", + "version": "1.18.3", "license": "MIT", "repository": { "type": "git", @@ -38,7 +38,7 @@ "build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain", "build-docker-pr-test": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:pr-test --target pr-test . --push", "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.18.2 && npm ci --production && npm run download-dist", + "setup": "git checkout 1.18.3 && npm ci --production && npm run download-dist", "download-dist": "node extra/download-dist.js", "mark-as-nightly": "node extra/mark-as-nightly.js", "reset-password": "node extra/reset-password.js", From 6e07ed20816969bfd1c6c06eb518171938312782 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Fri, 7 Oct 2022 15:02:19 +0800 Subject: [PATCH 214/803] Fix #2186 --- package-lock.json | 41 +++++++++++++++++++++++++++---------- package.json | 1 + server/model/status_page.js | 15 ++++++++++---- 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index 71827042..4f222171 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "uptime-kuma", - "version": "1.18.2", + "version": "1.18.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "uptime-kuma", - "version": "1.18.2", + "version": "1.18.3", "license": "MIT", "dependencies": { "@louislam/sqlite3": "~15.0.6", @@ -33,6 +33,7 @@ "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "iconv-lite": "^0.6.3", + "jsesc": "^3.0.2", "jsonwebtoken": "~8.5.1", "jwt-decode": "^3.1.2", "limiter": "^2.1.0", @@ -474,6 +475,18 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/generator/node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/helper-annotate-as-pure": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", @@ -11229,15 +11242,14 @@ "dev": true }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/json-parse-even-better-errors": { @@ -16957,6 +16969,14 @@ "@babel/types": "^7.18.13", "@jridgewell/gen-mapping": "^0.3.2", "jsesc": "^2.5.1" + }, + "dependencies": { + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + } } }, "@babel/helper-annotate-as-pure": { @@ -25010,10 +25030,9 @@ } }, "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==" }, "json-parse-even-better-errors": { "version": "2.3.1", diff --git a/package.json b/package.json index df0bdbfb..d478a1d2 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "iconv-lite": "^0.6.3", + "jsesc": "^3.0.2", "jsonwebtoken": "~8.5.1", "jwt-decode": "^3.1.2", "limiter": "^2.1.0", diff --git a/server/model/status_page.js b/server/model/status_page.js index 82d184bf..7682272c 100644 --- a/server/model/status_page.js +++ b/server/model/status_page.js @@ -2,6 +2,7 @@ const { BeanModel } = require("redbean-node/dist/bean-model"); const { R } = require("redbean-node"); const cheerio = require("cheerio"); const { UptimeKumaServer } = require("../uptime-kuma-server"); +const jsesc = require("jsesc"); class StatusPage extends BeanModel { @@ -56,13 +57,19 @@ class StatusPage extends BeanModel { head.append(``); // Preload data - const json = JSON.stringify(await StatusPage.getStatusPageData(statusPage)); - head.append(` - `); + head.append(script); + // manifest.json $("link[rel=manifest]").attr("href", `/api/status-page/${statusPage.slug}/manifest.json`); From 7d3cc002ea8fe95c7ca23885052280110e7f5217 Mon Sep 17 00:00:00 2001 From: Vasilis The Pikachu Date: Fri, 7 Oct 2022 08:55:12 +0000 Subject: [PATCH 215/803] Added greek language --- src/i18n.js | 1 + src/languages/el-GR.js | 585 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 586 insertions(+) create mode 100644 src/languages/el-GR.js diff --git a/src/i18n.js b/src/i18n.js index 8495cd99..4c19eb00 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -34,6 +34,7 @@ const languageList = { "zh-TW": "繁體中文 (台灣)", "uk-UA": "Український", "th-TH": "ไทย", + "el-GR": "Ελληνικά", }; let messages = { diff --git a/src/languages/el-GR.js b/src/languages/el-GR.js new file mode 100644 index 00000000..29af0417 --- /dev/null +++ b/src/languages/el-GR.js @@ -0,0 +1,585 @@ +export default { + languageName: "Ελληνικά", + checkEverySecond: "Έλεγχος κάθε {0} δευτερόλεπτα", + retryCheckEverySecond: "Επανάληψη κάθε {0} δευτερόλεπτα", + resendEveryXTimes: "Επανάληψη αποστολής ειδοποίησης κάθε {0} φορές", + resendDisabled: "Η επανάληψη αποστολής ειδοποίησης είναι απενεργοποιημένη", + retriesDescription: "Μέγιστες επαναλήψεις προτού η υπηρεσία επισημανθεί ως κατω και σταλεί μια ειδοποίηση", + ignoreTLSError: "Παράβλεψη σφάλματος TLS/SSL για ιστότοπους HTTPS", + upsideDownModeDescription: "Αναποδογυρίστε την κατάσταση. Εάν η υπηρεσία είναι προσβάσιμη, είναι ΚΑΤΩ.", + maxRedirectDescription: "Μέγιστος αριθμός redirect που θα ακολουθήσουν. Ρυθμίστε το 0 για να απενεργοποιήσετε τα redirect.", + acceptedStatusCodesDescription: "Επιλέξτε κωδικούς κατάστασης που θεωρούνται επιτυχή.", + passwordNotMatchMsg: "Ο κωδικός δεν ταιριάζει.", + notificationDescription: "Οι ειδοποιήσεις πρέπει να εκχωρηθούν σε μια παρακολούθηση για να λειτουργήσουν.", + keywordDescription: "Αναζήτηση λέξης-κλειδιού σε απλή απόκριση HTML ή JSON. Η αναζήτηση είναι διάκριση πεζών-κεφαλαίων.", + pauseDashboardHome: "Παύση", + deleteMonitorMsg: "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτήν την παρακολούθηση;", + deleteNotificationMsg: "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτήν την ειδοποίηση για όλες τις παρακολούθησης?", + dnsPortDescription: "Θύρα διακομιστή DNS. Προεπιλογή σε 53. Μπορείτε να αλλάξετε τη θύρα ανά πάσα στιγμή.", + resolverserverDescription: "Το Cloudflare είναι ο προεπιλεγμένος διακομιστής. Μπορείτε να αλλάξετε τον διακομιστή επίλυσης ανά πάσα στιγμήhe default server. You can change the resolver server anytime.", + rrtypeDescription: "Επιλέξτε τον τύπο RR που θέλετε να παρακολουθήσετε", + pauseMonitorMsg: "Είστε βέβαιοι ότι θέλετε να κάνετε παύση;", + enableDefaultNotificationDescription: "Αυτή η ειδοποίηση θα είναι ενεργοποιημένη από προεπιλογή για νέες παρακολούθησης. Μπορείτε ακόμα να απενεργοποιήσετε την ειδοποίηση ξεχωριστά για κάθε παρακολούθηση.", + clearEventsMsg: "Είστε βέβαιοι ότι θέλετε να διαγράψετε όλα τα συμβάντα για αυτήν την παρακολούθηση;", + clearHeartbeatsMsg: "Είστε βέβαιοι ότι θέλετε να διαγράψετε όλους τους καρδιακούς παλμούς για αυτήν την παρακολούθηση;", + confirmClearStatisticsMsg: "Είστε βέβαιοι ότι θέλετε να διαγράψετε ΟΛΑ τα στατιστικά στοιχεία;?", + importHandleDescription: "Επιλέξτε «Παράλειψη υπάρχοντος» εάν θέλετε να παραλείψετε κάθε παρακολούθηση ή ειδοποίηση με το ίδιο όνομα. Το 'Overwrite' θα διαγράψει κάθε υπάρχουσα παρακολούθηση και ειδοποίηση.", + confirmImportMsg: "Είστε βέβαιοι ότι θέλετε να εισαγάγετε το αντίγραφο ασφαλείας; Επαληθεύστε ότι έχετε επιλέξει τη σωστή επιλογή.", + twoFAVerifyLabel: "Εισαγάγετε το 2FA κωδικό για να επαληθεύσετε: ", + tokenValidSettingsMsg: "Ο κωδικός 2FA είναι έγκυρο! Τώρα μπορείτε να αποθηκεύσετε τις ρυθμίσεις 2FA", + confirmEnableTwoFAMsg: "Είστε βέβαιοι ότι θέλετε να ενεργοποιήσετε το 2FA;", + confirmDisableTwoFAMsg: "Είστε βέβαιοι ότι θέλετε να απενεργοποιήσετε το 2FA;", + Settings: "Ρυθμίσεις", + Dashboard: "Πίνακας", + "New Update": "Νέα αναβάθμιση", + Language: "Γλώσσα", + Appearance: "Εμφάνιση", + Theme: "Θέμα", + General: "Γενικά", + "Primary Base URL": "Κύρια βασική διεύθυνση URL", + Version: "Εκδοχή", + "Check Update On GitHub": "Ελέγξτε για Ενημέρωση στο GitHub", + List: "Λίστα", + Add: "Προσθήκη", + "Add New Monitor": "Προσθήκη νέας παρακολούθησης", + "Quick Stats": "Γρήγορα στατιστικά", + Up: "Πάνω", + Down: "Κάτω", + Pending: "Εκκρεμεί", + Unknown: "Άγνωστο", + Pause: "Παύση", + Name: "Ονομα", + Status: "Κατάσταση", + DateTime: "ΗμερομηνίαΏρα", + Message: "Μήνυμα", + "No important events": "Δεν υπάρχουν σημαντικά γεγονότα", + Resume: "Συνέχιση", + Edit: "Επεξεργασία", + Delete: "Διαγράφω", + Current: "Current", + Uptime: "Χρόνος λειτουργίας", + "Cert Exp.": "Cert Exp.", + day: "ημέρα | ημέρες", + "-day": "-ημέρα", + hour: "ώρα", + "-hour": "-ώρα", + Response: "Απάντηση", + Ping: "Ping", + "Monitor Type": "Τύπος παρακολούθησης", + Keyword: "Λέξη-κλειδί", + "Friendly Name": "Φιλικό όνομα", + URL: "URL", + Hostname: "Hostname", + Port: "Port", + "Heartbeat Interval": "Διάστημα καρδιακών παλμών", + Retries: "Επαναλήψεις", + "Heartbeat Retry Interval": "Διάστημα επανάληψης παλμών καρδιάς", + "Resend Notification if Down X times consequently": "Αποστολή νέας ειδοποίησης εάν κατω X φορές κατά συνέχεια", + Advanced: "Προχωρημένα", + "Upside Down Mode": "Ανάποδη λειτουργία", + "Max. Redirects": "Μέγιστη. Ανακατευθύνσεις", + "Accepted Status Codes": "Αποδεκτοί Κωδικοί Κατάστασης", + "Push URL": "Push URL", + needPushEvery: "Θα πρέπει να καλείτε αυτήν τη διεύθυνση URL κάθε {0} δευτερόλεπτα.", + pushOptionalParams: "Προαιρετικές παράμετροι: {0}", + Save: "Αποθηκεύση", + Notifications: "Ειδοποιήσεις", + "Not available, please setup.": "Μη διαθέσιμο, παρακαλώ ρυθμίστε.", + "Setup Notification": "Δημιουργία ειδοποίησης", + Light: "Φωτεινό", + Dark: "Σκοτεινό", + Auto: "Αυτόματο", + "Theme - Heartbeat Bar": "Θέμα - Μπάρα καρδιακών παλμών", + Normal: "Κανονικό", + Bottom: "Κάτω μέρος", + None: "Τίποτα", + Timezone: "Ζώνη ώρας", + "Search Engine Visibility": "Ορατότητα μηχανών αναζήτησης", + "Allow indexing": "Να επιτρέπεται η ευρετηρίαση", + "Discourage search engines from indexing site": "Αποθαρρύνετε τις μηχανές αναζήτησης από την ευρετηρίαση ιστότοπου", + "Change Password": "Αλλαγή κωδικού πρόσβασης", + "Current Password": "Τρέχων κωδικός πρόσβασης", + "New Password": "Νέος κωδικός πρόσβασης", + "Repeat New Password": "Επαναλάβετε τον νέο κωδικό πρόσβασης", + "Update Password": "Ενημέρωση κωδικού πρόσβασης", + "Disable Auth": "Απενεργοποίηση ελέγχου ταυτότητας", + "Enable Auth": "Ενεργοποίηση ελέγχου ταυτότητας", + "disableauth.message1": "Είστε βέβαιοι ότι θέλετε να απενεργοποιήσετε τον έλεγχο ταυτότητας;", + "disableauth.message2": "Έχει σχεδιαστεί για σενάρια όπου σκοπεύετε να εφαρμόσετε έλεγχο ταυτότητας τρίτου μέρους μπροστά από το Uptime Kuma, όπως το Cloudflare Access, Authelia ή άλλους μηχανισμούς ελέγχου ταυτότητας.", + "Please use this option carefully!": "Χρησιμοποιήστε αυτή την επιλογή προσεκτικά!", + Logout: "Αποσύνδεση", + Leave: "Φύγετε", + "I understand, please disable": "Καταλαβαίνω, απενεργοποιήστε", + Confirm: "Επιβεβαίωση", + Yes: "Ναί", + No: "Οχι", + Username: "Όνομα χρήστη", + Password: "Κωδικός πρόσβασης", + "Remember me": "Θυμήσου με", + Login: "Σύνδεση", + "No Monitors, please": "Δεν υπάρχουν παρακολούθησης παρακαλώ", + "add one": "προσθέστε ένα", + "Notification Type": "Είδος ειδοποίησης", + Email: "Email", + Test: "Δοκιμή", + "Certificate Info": "Πληροφορίες πιστοποιητικού", + "Resolver Server": "Διακομιστής επίλυσης", + "Resource Record Type": "Τύπος εγγραφής πόρων", + "Last Result": "Τελευταίο Αποτέλεσμα", + "Create your admin account": "Δημιουργήστε τον λογαριασμό διαχειριστή σας", + "Repeat Password": "Επαναλάβετε τον κωδικό πρόσβασης", + "Import Backup": "Εισαγωγή αντιγράφων ασφαλείας", + "Export Backup": "Εξαγωγή αντιγράφων ασφαλείας", + Export: "Εξαγωγή", + Import: "Εισαγωγή", + respTime: "Χρόν. Aπό (ms)", + notAvailableShort: "N/A", + "Default enabled": "Προεπιλογή ενεργοποιημένη", + "Apply on all existing monitors": "Εφαρμόστε σε όλες τις υπάρχουσες παρακολούθησης", + Create: "Δημιουργία", + "Clear Data": "Καθαρισμός δεδομένων", + Events: "Γεγονότα", + Heartbeats: "Παλμοι καρδιας", + "Auto Get": "Αυτόματη λήψη", + backupDescription: "Μπορείτε να δημιουργήσετε αντίγραφα ασφαλείας γία ολλες της παρακολούθησης και ειδοποιήσης σε ένα αρχείο JSON..", + backupDescription2: "Σημείωση: δεν περιλαμβάνονται δεδομένα ιστορικού και συμβάντων.", + backupDescription3: "Στο αρχείο εξαγωγής περιλαμβάνονται ευαίσθητα δεδομένα, όπως token ειδοποιήσεων. Aποθηκεύστε την εξαγωγή με ασφάλεια..", + alertNoFile: "Επιλέξτε ένα αρχείο για εισαγωγή.", + alertWrongFileType: "Επιλέξτε ένα αρχείο JSON.", + "Clear all statistics": "Εκκαθάριση όλων των στατιστικών", + "Skip existing": "Παράβλεψη υπάρχοντος", + Overwrite: "Αντικατάσταση", + Options: "Επιλογές", + "Keep both": "Κράτα και τα δύο", + "Verify Token": "Επαλήθευση Token", + "Setup 2FA": "Ρύθμιση 2FA", + "Enable 2FA": "Ενεργοποίηση 2FA", + "Disable 2FA": "Απενεργοποίηση 2FA", + "2FA Settings": "Ρυθμίσεις 2FA", + "Two Factor Authentication": "Έλεγχος ταυτότητας δύο παραγόντων", + Active: "Ενεργός", + Inactive: "Ανενεργό", + Token: "Token", + "Show URI": "Εμφάνιση URI", + Tags: "Ετικέτες", + "Add New below or Select...": "Προσθήκη νέου παρακάτω ή Επιλέξτε...", + "Tag with this name already exist.": "Υπάρχει ήδη η ετικέτα με αυτό το όνομα.", + "Tag with this value already exist.": "Υπάρχει ήδη ετικέτα με αυτό το value.", + color: "χρώμα", + "value (optional)": "value (optional)", + Gray: "Γκρί", + Red: "Κόκκινο", + Orange: "Πορτοκάλι", + Green: "Πράσινο", + Blue: "Μπλε", + Indigo: "Indigo", + Purple: "Μωβ", + Pink: "Ροζ", + "Search...": "Αναζήτηση...", + "Avg. Ping": "Μέσo.Ping", + "Avg. Response": "Μέσo. Aπάντηση", + "Entry Page": "Σελίδα εισαγωγής", + statusPageNothing: "Δεν υπάρχει τίποτα εδώ, προσθέστε μια ομάδα ή μια παρακολούθηση.", + "No Services": "Δεν υπάρχουν υπηρεσίες", + "All Systems Operational": "Όλα τα συστήματα λειτουργούν", + "Partially Degraded Service": "Μερικώς υποβαθμισμένη υπηρεσία", + "Degraded Service": "Υποβαθμισμένη υπηρεσία", + "Add Group": "Προσθήκη γρουπ", + "Add a monitor": "Προσθήκη παρακολούθησης", + "Edit Status Page": "Επεξεργασία σελίδας κατάστασης", + "Go to Dashboard": "Μεταβείτε στον Πίνακα ελέγχου", + "Status Page": "Σελίδα κατάστασης", + "Status Pages": "Σελίδες κατάστασης", + defaultNotificationName: "Η ειδοποίηση μου {notification} ({number})", + here: "εδώ", + Required: "Απαιτείται", + telegram: "Telegram", + "Bot Token": "Διακριτικό Bot", + wayToGetTelegramToken: "Μπορείτε να πάρετε ένα διακριτικό από {0}.", + "Chat ID": "Chat ID", + supportTelegramChatID: "Support Direct Chat / Group / Channel's Chat ID", + wayToGetTelegramChatID: "Μπορείτε να λάβετε το αναγνωριστικό συνομιλίας σας στέλνοντας ένα μήνυμα στο bot και μεταβαίνοντας σε αυτήν τη διεύθυνση URL για να προβάλετε το chat_id:", + "YOUR BOT TOKEN HERE": "ΤΟ BOT ΣΑΣ ΔΙΑΚΡΙΤΙΚΌ ΕΔΩ", + chatIDNotFound: "Το Chat ID δεν βρέθηκε. Στείλτε πρώτα ένα μήνυμα σε αυτό το bot", + webhook: "Webhook", + "Post URL": "Post URL", + "Content Type": "Τύπος περιεχομένου", + webhookJsonDesc: "{0} είναι καλό για οποιονδήποτε σύγχρονο διακομιστή HTTP όπως το Express.js", + webhookFormDataDesc: "{multipart} είναι καλό για την PHP. Το JSON θα πρέπει να αναλυθεί με {decodeFunction}", + smtp: "Email (SMTP)", + secureOptionNone: "None / STARTTLS (25, 587)", + secureOptionTLS: "TLS (465)", + "Ignore TLS Error": "Παράβλεψη σφάλματος TLS", + "From Email": "Από Email", + emailCustomSubject: "Προσαρμοσμένο θέμα", + "To Email": "Προς Email", + smtpCC: "CC", + smtpBCC: "BCC", + discord: "Discord", + "Discord Webhook URL": "Discord Webhook URL", + wayToGetDiscordURL: "Μπορείτε να το αποκτήσετε μεταβαίνοντας στις Ρυθμίσεις διακομιστή -> Ενσωματώσεις -> Δημιουργία Webhook", + "Bot Display Name": "Εμφανιζόμενο όνομα bot", + "Prefix Custom Message": "Προσαρμοσμένο μήνυμα", + "Hello @everyone is...": "Γεια {'@'}everyone ειναι...", + teams: "Microsoft Teams", + "Webhook URL": "Webhook URL", + wayToGetTeamsURL: "Μπορείτε να μάθετε πώς να δημιουργείτε μια διεύθυνση URL webhook {0}.", + signal: "Signal", + Number: "Αριθμός", + Recipients: "Αποδέκτες", + needSignalAPI: "Πρέπει να έχετε ένα signal client με REST API..", + wayToCheckSignalURL: "Μπορείτε να ελέγξετε αυτό το URL για να δείτε πώς να ρυθμίσετε ένα:", + signalImportant: "ΣΗΜΑΝΤΙΚΟ: Δεν μπορείτε να συνδυάσετε ομάδες και αριθμούς στους παραλήπτες!", + gotify: "Gotify", + "Application Token": "Token εφαρμογής", + "Server URL": "URL διακομιστή", + Priority: "Προτεραιότητα", + slack: "Slack", + "Icon Emoji": "Εικονίδιο Emoji", + "Channel Name": "Όνομα καναλιού", + "Uptime Kuma URL": "Uptime Kuma URL", + aboutWebhooks: "Περισσότερες πληροφορίες σχετικά με τα Webhooks στο: {0}", + aboutChannelName: "Εισαγάγετε το όνομα του καναλιού στο {0} Όνομα καναλιού εάν θέλετε να παρακάμψετε το κανάλι Webhook. Π.χ.: #other-channel", + aboutKumaURL: "Εάν αφήσετε κενό το πεδίο URL Uptime Kuma, θα είναι προεπιλεγμένο στη σελίδα Project GitHub..", + emojiCheatSheet: "Φύλλο εξαπάτησης emoji: {0}", + "rocket.chat": "Rocket.Chat", + pushover: "Pushover", + pushy: "Pushy", + PushByTechulus: "Push by Techulus", + octopush: "Octopush", + promosms: "PromoSMS", + clicksendsms: "ClickSend SMS", + lunasea: "LunaSea", + apprise: "Apprise (Support 50+ Notification services)", + GoogleChat: "Google Chat (Google Workspace only)", + pushbullet: "Pushbullet", + line: "Line Messenger", + mattermost: "Mattermost", + "User Key": "Κλειδί χρήστη", + Device: "Συσκευή", + "Message Title": "Τίτλος μηνύματος", + "Notification Sound": "Ήχος ειδοποίησης", + "More info on:": "Περισσότερες πληροφορίες στο: {0}", + pushoverDesc1: "Η προτεραιότητα έκτακτης ανάγκης (2) έχει προεπιλεγμένο χρονικό όριο 30 δευτερολέπτων μεταξύ των επαναλήψεων και θα λήξει μετά από 1 ώρα.", + pushoverDesc2: "Εάν θέλετε να στέλνετε ειδοποιήσεις σε διαφορετικές συσκευές, συμπληρώστε το πεδίο Συσκευή.", + "SMS Type": "Τύπος SMS", + octopushTypePremium: "Premium (Γρήγορη - συνιστάται για ειδοποίηση)", + octopushTypeLowCost: "Χαμηλό κόστος (Αργό - μερικές φορές μπλοκάρεται από τον χειριστή)", + checkPrice: "Ελέγξτε τις τιμές {0}:", + apiCredentials: "API credentials", + octopushLegacyHint: "Χρησιμοποιείτε την παλαιού τύπου έκδοση του Octopush (2011-2020) ή τη νέα έκδοση;", + "Check octopush prices": "Ελέγξτε τις τιμές OctoPush {0}.", + octopushPhoneNumber: "Αριθμός τηλεφώνου (διεθνής μορφή, π.χ.: +30694345678)", + octopushSMSSender: "Όνομα αποστολέα SMS: 3-11 αλφαριθμητικοί χαρακτήρες και διάστημα (a-zA-Z0-9)", + "LunaSea Device ID": "LunaSea Device ID", + "Apprise URL": "Apprise URL", + "Example:": "Παράδειγμα: {0}", + "Read more:": "Διαβάστε περισσότερα: {0}", + "Status:": "Κατάσταση: {0}", + "Read more": "Διαβάστε περισσότερα", + appriseInstalled: "Το Apprise έχει εγκατασταθεί.", + appriseNotInstalled: "Το Apprise δεν έχει εγκατασταθεί. {0}", + "Access Token": "Access Token", + "Channel access token": "Channel Access Token", + "Line Developers Console": "Line Developers Console", + lineDevConsoleTo: "Line Developers Console - {0}", + "Basic Settings": "Βασικές ρυθμίσεις", + "User ID": "User ID", + "Messaging API": "Messaging API", + wayToGetLineChannelToken: "Πρώτα αποκτήστε πρόσβαση στο {0}, δημιουργήστε έναν πάροχο και ένα κανάλι (Messanging API) και, στη συνέχεια, μπορείτε να λάβετε το channel access token και το user ID από τα παραπάνω στοιχεία μενού.", + "Icon URL": "Διεύθυνση URL εικονιδίου", + aboutIconURL: "Μπορείτε να παρέχετε έναν σύνδεσμο προς μια εικόνα στο \"Icon URL\" για να παρακάμψετε την προεπιλεγμένη εικόνα προφίλ. Δεν θα χρησιμοποιηθεί εάν έχει οριστεί το εικονίδιο Emoji.", + aboutMattermostChannelName: "Μπορείτε να παρακάμψετε το προεπιλεγμένο κανάλι στο οποίο δημοσιεύει το Webhook εισάγοντας το όνομα του καναλιού στο πεδίο \"Όνομα καναλιού\". Αυτό πρέπει να ενεργοποιηθεί στις ρυθμίσεις του Mattermost Webhook. Π.χ.: #other-channel", + matrix: "Matrix", + promosmsTypeEco: "SMS ECO - φθηνό αλλά αργό και συχνά υπερφορτωμένο. Περιορίζεται μόνο σε Πολωνούς παραλήπτες.", + promosmsTypeFlash: "SMS FLASH - Το μήνυμα θα εμφανίζεται αυτόματα στη συσκευή του παραλήπτη. Περιορίζεται μόνο σε Πολωνούς παραλήπτες.", + promosmsTypeFull: "SMS FULL - Premium επίπεδο SMS, Μπορείτε να χρησιμοποιήσετε το Όνομα Αποστολέα σας (Πρέπει πρώτα να καταχωρήσετε το όνομα). Αξιόπιστο για ειδοποιήσεις.", + promosmsTypeSpeed: "SMS SPEED - Υψηλότερη προτεραιότητα στο σύστημα. Πολύ γρήγορο και αξιόπιστο αλλά ακριβό (περίπου διπλάσια τιμή SMS FULL).", + promosmsPhoneNumber: "Αριθμός τηλεφώνου (για πολωνούς παραλήπτες Μπορείτε να παραλείψετε τους κωδικούς περιοχής)", + promosmsSMSSender: "Όνομα αποστολέα SMS: Προεγγεγραμμένο όνομα ή ένα από τα προεπιλεγμένα: InfoSMS, SMS Info, MaxSMS, INFO, SMS", + "Feishu WebHookUrl": "Feishu WebHookURL", + matrixHomeserverURL: "Homeserver URL (με http(s):// και προαιρετικά θύρα)", + "Internal Room Id": "Internal Room ID", + matrixDesc1: "Μπορείτε να βρείτε το internal room ID ανατρέχοντας στην ενότητα για προχωρημένους των ρυθμίσεων δωματίου στο πρόγραμμα-πελάτη Matrix. Θα πρέπει να μοιάζει με !QMdRCpUIfLwsfjxye6:home.server.", + matrixDesc2: "Συνιστάται ανεπιφύλακτα να δημιουργήσετε έναν νέο χρήστη και να μην χρησιμοποιήσετε το διακριτικό πρόσβασης του χρήστη Matrix, καθώς θα επιτρέψει την πλήρη πρόσβαση στον λογαριασμό σας και σε όλα τα δωμάτια στα οποία συμμετέχετε. Αντίθετα, δημιουργήστε έναν νέο χρήστη και προσκαλέστε τον μόνο στο δωμάτιο στο οποίο θέλετε να λαμβάνετε την ειδοποίηση. Μπορείτε να λάβετε το access token εκτελώντας {0}", + Method: "Μέθοδος", + Body: "Σώμα", + Headers: "Headers", + PushUrl: "Push URL", + HeadersInvalidFormat: "The request headers are not valid JSON: ", + BodyInvalidFormat: "The request body is not valid JSON: ", + "Monitor History": "Ιστορικο Παρακολούθησης", + clearDataOlderThan: "Διατηρήστε τα δεδομένα ιστορικού παρακολούθησης για {0} ημέρες.", + PasswordsDoNotMatch: "Οι κωδικοί πρόσβασης δεν ταιριάζουν.", + records: "εγγραφές", + "One record": "Μία εγγραφή", + steamApiKeyDescription: "Για την παρακολούθηση ενός διακομιστή παιχνιδιών Steam χρειάζεστε ένα κλειδί Steam Web-API. Μπορείτε να καταχωρήσετε το κλειδί API σας εδώ: ", + "Current User": "Τρέχων χρήστης", + topic: "Θέμα", + topicExplanation: "Θέμα MQTT προς παρακολούθηση", + successMessage: "Μήνυμα επιτυχίας", + successMessageExplanation: "Μήνυμα MQTT που θα θεωρηθεί επιτυχές", + recent: "Πρόσφατος", + Done: "Ολοκληρώθηκε", + Info: "Πληροφορίες", + Security: "Ασφάλεια", + "Steam API Key": "Steam API Key", + "Shrink Database": "Συρρίκνωση βάσης δεδομένων", + "Pick a RR-Type...": "Επιλέξτε έναν τύπο RR...", + "Pick Accepted Status Codes...": "Επιλέξτε Αποδεκτούς κωδικούς κατάστασης...", + Default: "Προκαθορισμένο", + "HTTP Options": "Επιλογές HTTP", + "Create Incident": "Δημιουργία περιστατικού", + Title: "Τίτλος", + Content: "Περιεχόμενο", + Style: "Στυλ", + info: "πληροφορίες", + warning: "προειδοποίηση", + danger: "κίνδυνος", + error: "σφάλμα", + critical: "κριτικό", + primary: "primary", + light: "light", + dark: "dark", + Post: "Δημοσίευση", + "Please input title and content": "Παρακαλούμε εισαγάγετε τίτλο και περιεχόμενο", + Created: "Δημιουργήθηκε", + "Last Updated": "Τελευταία ενημέρωση", + Unpin: "Ξεκαρφιτσώστε", + "Switch to Light Theme": "Μετάβαση σε Ανιχτό θέμα", + "Switch to Dark Theme": "Μετάβαση σε Σκούρο θέμα", + "Show Tags": "Εμφάνιση ετικετών", + "Hide Tags": "Απόκρυψη ετικετών", + Description: "Περιγραφή", + "No monitors available.": "Δεν υπάρχουν διαθέσιμες παρακολουθήσεις.", + "Add one": "Προσθέστε ένα", + "No Monitors": "Χωρίς παρακολουθήσεις", + "Untitled Group": "Ομάδα χωρίς τίτλο", + Services: "Υπηρεσίες", + Discard: "Απορρίψει", + Cancel: "Ακυρο", + "Powered by": "Με την υποστήριξη του", + shrinkDatabaseDescription: "Ενεργοποίηση βάσης δεδομένων VACUUM για SQLite. Εάν η βάση δεδομένων σας έχει δημιουργηθεί μετά την έκδοση 1.10.0, το AUTO_VACUUM είναι ήδη ενεργοποιημένο και αυτή η ενέργεια δεν χρειάζεται.", + serwersms: "SerwerSMS.pl", + serwersmsAPIUser: "API Username (incl. webapi_ prefix)", + serwersmsAPIPassword: "API κωδικός πρόσβασης", + serwersmsPhoneNumber: "Αριθμός τηλεφώνου", + serwersmsSenderName: "Όνομα αποστολέα SMS (καταχωρήθηκε μέσω της πύλης πελατών)", + stackfield: "Stackfield", + Customize: "Προσαρμογή", + "Custom Footer": "Προσαρμογή Footer", + "Custom CSS": "Προσαρμογή CSS", + smtpDkimSettings: "Ρυθμίσεις DKIM", + smtpDkimDesc: "Ανατρέξτε στο Nodemailer DKIM {0} για χρήση.", + documentation: "documentation", + smtpDkimDomain: "Domain Name", + smtpDkimKeySelector: "Key Selector", + smtpDkimPrivateKey: "Private Key", + smtpDkimHashAlgo: "Hash Algorithm (Optional)", + smtpDkimheaderFieldNames: "Header Keys to sign (Optional)", + smtpDkimskipFields: "Header Keys not to sign (Optional)", + wayToGetPagerDutyKey: "Μπορείτε να το λάβετε μεταβαίνοντας στο Service -> Service Directory -> (Επιλέξτε μια υπηρεσία) -> Integrations -> Add integration. Εδώ μπορείτε να κάνετε αναζήτηση για \"Events API V2\". Περισσότερες πληροφορίες {0}", + "Integration Key": "Integration Key", + "Integration URL": "Integration URL", + "Auto resolve or acknowledged": "Αυτόματη επίλυση ή αναγνώριση", + "do nothing": "μην κάνεις τίποτα", + "auto acknowledged": "αυτόματη αναγνώριση", + "auto resolve": "αυτόματη επίλυση", + gorush: "Gorush", + alerta: "Alerta", + alertaApiEndpoint: "API Endpoint", + alertaEnvironment: "Environment", + alertaApiKey: "API Key", + alertaAlertState: "Alert State", + alertaRecoverState: "Recover State", + deleteStatusPageMsg: "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτήν τη σελίδα κατάστασης?", + Proxies: "Proxies", + default: "Προκαθορισμένο", + enabled: "Ενεργοποιημένο", + setAsDefault: "Ορίσετε ως προεπιλογή", + deleteProxyMsg: "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το proxy για όλες τις παρακολουθήσεις;", + proxyDescription: "Πρέπει να εκχωρηθούν proxies σε μια οθπαρακολουθή για να λειτουργήσουν..", + enableProxyDescription: "Το proxy δεν θα επηρεάσει τα αιτήματα της παρακολουθήσεις μέχρι να ενεργοποιηθεί. Μπορείτε να ελέγξετε την προσωρινή απενεργοποίηση του proxy από όλες τις παρακολουθήσεις βάσει κατάστασης ενεργοποίησης.", + setAsDefaultProxyDescription: "Αυτός το proxy θα είναι ενεργοποιημένο από προεπιλογή για νέες παρακολουθήσεις. Μπορείτε ακόμα να απενεργοποιήσετε το proxy ξεχωριστά για κάθε οθόνη.", + "Certificate Chain": "Certificate Chain", + Valid: "Εγκυρο", + Invalid: "Μη έγκυρο", + AccessKeyId: "AccessKey ID", + SecretAccessKey: "AccessKey Secret", + PhoneNumbers: "PhoneNumbers", + TemplateCode: "TemplateCode", + SignName: "SignName", + "Sms template must contain parameters: ": "Το πρότυπο SMS πρέπει να περιέχει παραμέτρους: ", + "Bark Endpoint": "Bark Endpoint", + "Bark Group": "Bark Ομάδα", + "Bark Sound": "Bark Ήχος", + WebHookUrl: "WebHookUrl", + SecretKey: "SecretKey", + "For safety, must use secret key": "Για ασφάλεια, πρέπει να χρησιμοποιήσετε secret key", + "Device Token": "Device Token", + Platform: "Platform", + iOS: "iOS", + Android: "Android", + Huawei: "Huawei", + High: "High", + Retry: "Ξαναδοκιμάσετε", + Topic: "Θέμα", + "WeCom Bot Key": "WeCom Bot Key", + "Setup Proxy": "Ρύθμιση Proxy", + "Proxy Protocol": "Πρωτόκολλο Proxy", + "Proxy Server": "Proxy Server", + "Proxy server has authentication": "Το Proxy διαθέτει έλεγχο ταυτότητας", + User: "Χρήστης", + Installed: "Εγκατεστημένο", + "Not installed": "Μη εγκατεστημενο", + Running: "Τρέχη", + "Not running": "Δεν τρεχη", + "Remove Token": "Κατάργηση Token", + Start: "Αρχή", + Stop: "Στάση", + "Uptime Kuma": "Uptime Kuma", + "Add New Status Page": "Προσθήκη νέας σελίδας κατάστασης", + Slug: "Slug", + "Accept characters:": "Αποδοχή χαρακτήρων:", + startOrEndWithOnly: "Ξεκινήστε ή τελειώστε μόνο με {0}", + "No consecutive dashes": "Χωρίς διαδοχικές παύλες", + Next: "Επόμενο", + "The slug is already taken. Please choose another slug.": "Ο slug έχει ήδη πιαστεί. Επιλέξτε άλλο slug.", + "No Proxy": "Οχι Proxy", + Authentication: "Authentication", + "HTTP Basic Auth": "HTTP Basic Auth", + "New Status Page": "Σελίδα νέας κατάστασης", + "Page Not Found": "Η σελίδα δεν βρέθηκε", + "Reverse Proxy": "Αντίστροφο Proxy", + Backup: "Αντιγράφων ασφαλείας", + About: "Σχετικά με", + wayToGetCloudflaredURL: "(Λήψη cloudflared από {0})", + cloudflareWebsite: "Ιστοσελίδα Cloudflare", + "Message:": "Μήνυμα:", + "Don't know how to get the token? Please read the guide:": "Δεν ξέρετε πώς να αποκτήσετε το token; Διαβάστε τον οδηγό:", + "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. Θέλετε σίγουρα να το σταματήσετε; Πληκτρολογήστε τον τρέχοντα κωδικό πρόσβασής σας για να τον επιβεβαιώσετε.", + "HTTP Headers": "HTTP Headers", + "Trust Proxy": "Εμπιστοσύνη του Proxy", + "Other Software": "Other Software", + "For example: nginx, Apache and Traefik.": "Για παράδειγμα: nginx, Apache και Traefik.", + "Please read": "Παρακαλώ διαβάστε", + "Subject:": "Θέμα:", + "Valid To:": "Εγκυρο για:", + "Days Remaining:": "Ημέρες που απομένουν:", + "Issuer:": "Εκδότης:", + "Fingerprint:": "Δακτυλικό αποτύπωμα:", + "No status pages": "Δεν υπάρχουν σελίδες κατάστασης", + "Domain Name Expiry Notification": "Ειδοποίηση λήξης ονόματος τομέα", + Proxy: "Proxy", + "Date Created": "Ημερομηνία Δημιουργίας", + HomeAssistant: "Home Assistant", + onebotHttpAddress: "OneBot HTTP Address", + onebotMessageType: "OneBot Message Type", + onebotGroupMessage: "Group", + onebotPrivateMessage: "Private", + onebotUserOrGroupId: "Group/User ID", + onebotSafetyTips: "Για ασφάλεια, πρέπει να ορίσετε το acess token", + "PushDeer Key": "PushDeer Key", + "Footer Text": "Κείμενο υποσέλιδου", + "Show Powered By": "Εμφάνιση Powered By", + "Domain Names": "Ονόματα Τομέα", + signedInDisp: "Συνδεθήκατε ως {0}", + signedInDispDisabled: "Εξουσιοδότηση είναι απενεργοποιημένη.", + RadiusSecret: "Radius Secret", + RadiusSecretDescription: "Shared Secret μεταξύ client και το server", + RadiusCalledStationId: "Called Station Id", + RadiusCalledStationIdDescription: "Identifier της καλούμενης συσκευής", + RadiusCallingStationId: "Calling Station Id", + RadiusCallingStationIdDescription: "Identifier oτης συσκευής κλήσης", + "Certificate Expiry Notification": "Ειδοποίηση Λήξης Πιστοποιητικού", + "API Username": "API Username", + "API Key": "API Key", + "Recipient Number": "Αριθμός Παραλήπτη", + "From Name/Number": "Από Όνομα/Αριθμός", + "Leave blank to use a shared sender number.": "Αφήστε το κενό για να χρησιμοποιήσετε έναν κοινόχρηστο αριθμό αποστολέα.", + "Octopush API Version": "Octopush API Version", + "Legacy Octopush-DM": "Legacy Octopush-DM", + endpoint: "endpoint", + octopushAPIKey: "\"API key\" από το HTTP API credentials στον πίνακα ελέγχου", + octopushLogin: "\"Login\" από το HTTP API credentials στον πίνακα ελέγχου", + promosmsLogin: "API Login Name", + promosmsPassword: "API Password", + "pushoversounds pushover": "Pushover (default)", + "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 Key", + pushyToken: "Τoken Συσκευής", + "Show update if available": "Εμφάνιση ενημέρωσης εάν είναι διαθέσιμη", + "Also check beta release": "Ελέγξτε επίσης την έκδοση beta", + "Using a Reverse Proxy?": "Χρησιμοποιείτε 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 Username και API Key απο {0} .", + "Connection String": "Connection String", + Query: "Query", + settingsCertificateExpiry: "Λήξη πιστοποιητικού TLS", + certificationExpiryDescription: "Οι παρακολουθήσεις HTTPS ενεργοποιούν ειδοποίηση όταν λήξει το πιστοποιητικό TLS σε:", + "Setup Docker Host": "Ρύθμιση Docker Host", + "Connection Type": "Τύπος σύνδεσης", + "Docker Daemon": "Docker Daemon", + deleteDockerHostMsg: "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτόν τον κεντρικό υπολογιστή βάσης για όλες τις παρακολουθήσεις;", + socket: "Socket", + tcp: "TCP / HTTP", + "Docker Container": "Docker Container", + "Container Name / ID": "Container Name / ID", + "Docker Host": "Docker Host", + "Docker Hosts": "Docker Hosts", + "ntfy Topic": "ntfy Topic", + Domain: "Domain", + Workstation: "Workstation", + disableCloudflaredNoAuthMsg: "Βρίσκεστε σε λειτουργία No Auth, δεν απαιτείται κωδικός πρόσβασης.", + trustProxyDescription: "Εμπιστευτείτε τις κεφαλίδες 'X-Forwarded-*'. Εάν θέλετε να λάβετε τη σωστή IP πελάτη και το Uptime Kuma σας βρίσκεται πίσω το Nginx ή το Apache, θα πρέπει να το ενεργοποιήσετε.", + wayToGetLineNotifyToken: "Μπορείτε να λάβετε ένα access token από το {0}", + Examples: "Παραδείγματα", + "Home Assistant URL": "Home Assistant URL", + "Long-Lived Access Token": "Long-Lived Access Token", + "Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "Long-Lived Access Token μπορεί να δημιουργηθεί κάνοντας κλικ στο όνομα του προφίλ σας (κάτω αριστερά) και κάνοντας κύλιση προς τα κάτω και, στη συνέχεια, κάντε κλικ στο Create Token. ", + "Notification Service": "Υπηρεσία ειδοποιήσεων", + "default: notify all devices": "προεπιλογή: ειδοποίηση όλων των συσκευών", + "A list of Notification Services can be found in Home Assistant under \"Developer Tools > Services\" search for \"notification\" to find your device/phone name.": "Μπορείτε να βρείτε μια λίστα με τις Υπηρεσίες ειδοποιήσεων στον Home assistant στην περιοχή \"Developer Tools > Services\" αναζήτηση για \"notification\" για να βρείτε το όνομα της συσκευής/τηλεφώνου σας.", + "Automations can optionally be triggered in Home Assistant:": "Οι αυτοματισμοί μπορούν προαιρετικά να ενεργοποιηθούν στο Home Assistant:", + "Trigger type:": "Τύπος ενεργοποίησης:", + "Event type:": "Τύπος συμβάντος:", + "Event data:": "Δεδομένα συμβάντος:", + "Then choose an action, for example switch the scene to where an RGB light is red.": "Στη συνέχεια, επιλέξτε μια ενέργεια, για παράδειγμα αλλάξτε τη σκηνή στο σημείο όπου ένα φως RGB είναι κόκκινο.", + "Frontend Version": "Έκδοση Frontend", + "Frontend Version do not match backend version!": "Η Frontend έκδοση δεν ταιριάζει με την έκδοση backend!", + "Base URL": "Βασική διεύθυνση URL", + goAlertInfo: "Το GoAlert είναι μια εφαρμογή ανοιχτού κώδικα για προγραμματισμό κλήσεων, αυτοματοποιημένες κλιμακώσεις και ειδοποιήσεις (όπως SMS ή φωνητικές κλήσεις). Αλληλεπιδράστε αυτόματα με το σωστό άτομο, με τον σωστό τρόπο και τη σωστή στιγμή! {0}", + goAlertIntegrationKeyInfo: "Λάβετε το generic API integration key για την υπηρεσία σε αυτήν τη μορφή \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\" συνήθως την τιμή της παραμέτρου διακριτικού της αντιγραμμένης διεύθυνσης URL.", + goAlert: "GoAlert", + backupOutdatedWarning: "Καταργήθηκε: Επειδή προστέθηκαν πολλές δυνατότητες και αυτή η δυνατότητα δημιουργίας αντιγράφων ασφαλείας δεν διατηρείται πολη, δεν μπορεί να δημιουργήσει ή να επαναφέρει ένα πλήρες αντίγραφο ασφαλείας.", + backupRecommend: "Παρακαλούμε δημιουργήστε αντίγραφα ασφαλείας του τόμου ή του φακέλου δεδομένων (./data/) απευθείας.", +}; From e29527e22fad7f9691b3fed4b29466f9bd1ced9c Mon Sep 17 00:00:00 2001 From: Vasilis The Pikachu Date: Fri, 7 Oct 2022 11:33:29 +0200 Subject: [PATCH 216/803] Update Greek --- src/languages/el-GR.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/languages/el-GR.js b/src/languages/el-GR.js index 29af0417..92a2d775 100644 --- a/src/languages/el-GR.js +++ b/src/languages/el-GR.js @@ -55,7 +55,7 @@ export default { "No important events": "Δεν υπάρχουν σημαντικά γεγονότα", Resume: "Συνέχιση", Edit: "Επεξεργασία", - Delete: "Διαγράφω", + Delete: "Διαγράφη", Current: "Current", Uptime: "Χρόνος λειτουργίας", "Cert Exp.": "Cert Exp.", @@ -448,11 +448,11 @@ export default { "No Proxy": "Οχι Proxy", Authentication: "Authentication", "HTTP Basic Auth": "HTTP Basic Auth", - "New Status Page": "Σελίδα νέας κατάστασης", + "New Status Page": "Νέας Σελίδα κατάστασης", "Page Not Found": "Η σελίδα δεν βρέθηκε", "Reverse Proxy": "Αντίστροφο Proxy", Backup: "Αντιγράφων ασφαλείας", - About: "Σχετικά με", + About: "Σχετικά με το Uptime Kuma", wayToGetCloudflaredURL: "(Λήψη cloudflared από {0})", cloudflareWebsite: "Ιστοσελίδα Cloudflare", "Message:": "Μήνυμα:", From 4c456547807dc3fb4d6de0a4da741ef252cb5c7e Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Fri, 7 Oct 2022 18:38:14 +0800 Subject: [PATCH 217/803] WIP --- extra/exe-builder/Program.cs | 69 +++++++++++++++++++++-------- extra/exe-builder/UptimeKuma.csproj | 5 ++- 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/extra/exe-builder/Program.cs b/extra/exe-builder/Program.cs index 840bc873..84ecda31 100644 --- a/extra/exe-builder/Program.cs +++ b/extra/exe-builder/Program.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Drawing; using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; using System.Threading.Tasks; using System.Windows.Forms; using UptimeKuma.Properties; @@ -14,7 +15,7 @@ namespace UptimeKuma { /// The main entry point for the application. /// [STAThread] - static void Main() { + static void Main(string[] args) { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new UptimeKumaApplicationContext()); @@ -28,37 +29,44 @@ namespace UptimeKuma { public UptimeKumaApplicationContext() { - // Initialize Tray Icon trayIcon = new NotifyIcon(); trayIcon.Icon = Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location); trayIcon.ContextMenu = new ContextMenu(new MenuItem[] { - new MenuItem("Open", Open), - new MenuItem("Check for Update", CheckForUpdate), - new MenuItem("About", About), - new MenuItem("Exit", Exit), + new("Open", Open), + //new("Debug Console", DebugConsole), + new("Check for Update...", CheckForUpdate), + new("Visit GitHub...", VisitGitHub), + new("About", About), + new("Exit", Exit), }); + trayIcon.MouseDoubleClick += new MouseEventHandler(Open); + trayIcon.Visible = true; - var startInfo = new ProcessStartInfo(); - startInfo.FileName = "node/node.exe"; - startInfo.Arguments = "server/server.js"; - startInfo.RedirectStandardOutput = true; - startInfo.RedirectStandardError = true; - startInfo.UseShellExecute = false; - startInfo.CreateNoWindow = true; - startInfo.WorkingDirectory = "core"; + var startInfo = new ProcessStartInfo { + FileName = "node/node.exe", + Arguments = "server/server.js --data-dir=\"../data/\"", + RedirectStandardOutput = false, + RedirectStandardError = false, + UseShellExecute = false, + CreateNoWindow = true, + WorkingDirectory = "core" + }; process = new Process(); process.StartInfo = startInfo; process.EnableRaisingEvents = true; + process.Exited += new EventHandler(ProcessExited); + + try { process.Start(); - Open(null, null); + //Open(null, null); + } catch (Exception e) { MessageBox.Show("Startup failed: " + e.Message, "Uptime Kuma Error"); - throw; } } @@ -66,10 +74,19 @@ namespace UptimeKuma { Process.Start("http://localhost:3001"); } - void CheckForUpdate(object sender, EventArgs e) { + void DebugConsole(object sender, EventArgs e) { } + void CheckForUpdate(object sender, EventArgs e) { + Process.Start("https://github.com/louislam/uptime-kuma/releases"); + } + + void VisitGitHub(object sender, EventArgs e) + { + Process.Start("https://github.com/louislam/uptime-kuma"); + } + void About(object sender, EventArgs e) { MessageBox.Show("Uptime Kuma v1.0.0" + Environment.NewLine + "© 2022 Louis Lam", "Info"); @@ -79,9 +96,25 @@ namespace UptimeKuma { { // Hide tray icon, otherwise it will remain shown until user mouses over it trayIcon.Visible = false; - process.Close(); + process.Kill(); + } + + void ProcessExited(object sender, EventArgs e) { + + if (process.ExitCode != 0) { + var line = ""; + while (!process.StandardOutput.EndOfStream) + { + line += process.StandardOutput.ReadLine(); + } + + MessageBox.Show("Uptime Kuma exited unexpectedly. Exit code: " + process.ExitCode + " " + line); + } + + trayIcon.Visible = false; Application.Exit(); } + } } diff --git a/extra/exe-builder/UptimeKuma.csproj b/extra/exe-builder/UptimeKuma.csproj index d62166e4..c3c6aad2 100644 --- a/extra/exe-builder/UptimeKuma.csproj +++ b/extra/exe-builder/UptimeKuma.csproj @@ -13,7 +13,7 @@ true true ..\..\public\favicon.ico - 10 + 9 AnyCPU @@ -34,6 +34,9 @@ prompt 4 + + COPY "$(SolutionDir)bin\Debug\uptime-kuma.exe" "C:\Users\LouisLam\Desktop\uptime-kuma-win64\" + From 4f6dec41c617267860ec9c686477e03c633e7c4b Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Fri, 7 Oct 2022 20:46:43 +0800 Subject: [PATCH 218/803] Fix ntfy username should not be required --- src/components/notifications/Ntfy.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/notifications/Ntfy.vue b/src/components/notifications/Ntfy.vue index ddcc3917..b0f7888a 100644 --- a/src/components/notifications/Ntfy.vue +++ b/src/components/notifications/Ntfy.vue @@ -18,7 +18,7 @@
- +
From 43c1ec640c41e83b0cb7783b1760f7781fab8f4a Mon Sep 17 00:00:00 2001 From: AnnAngela Date: Sat, 8 Oct 2022 01:45:00 +0800 Subject: [PATCH 219/803] feat: :globe_with_meridians: Update zh-cn and en translation (#2167) Co-authored-by: Louis Lam --- src/components/notifications/HomeAssistant.vue | 2 +- src/components/notifications/SMSManager.vue | 2 +- src/languages/en.js | 6 ++++++ src/languages/zh-CN.js | 14 ++++++++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/components/notifications/HomeAssistant.vue b/src/components/notifications/HomeAssistant.vue index 67e370a1..de368095 100644 --- a/src/components/notifications/HomeAssistant.vue +++ b/src/components/notifications/HomeAssistant.vue @@ -18,7 +18,7 @@
-

{{ $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.") }}

+

{{ $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.') }}

{{ $t("Automations can optionally be triggered in Home Assistant:") }}

{{ $t("Trigger type:") }} Event
diff --git a/src/components/notifications/SMSManager.vue b/src/components/notifications/SMSManager.vue index 25db624f..1be952ae 100644 --- a/src/components/notifications/SMSManager.vue +++ b/src/components/notifications/SMSManager.vue @@ -2,7 +2,7 @@

- {{ $t("SMSManager API Docs ") }} + {{ $t("SMSManager API Docs") }} {{ $t("here") }}
diff --git a/src/languages/en.js b/src/languages/en.js index 4bf92e92..e277958b 100644 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -584,4 +584,10 @@ export default { backupRecommend: "Please backup the volume or the data folder (./data/) directly instead.", "Optional": "Optional", squadcast: "Squadcast", + SendKey: "SendKey", + "SMSManager API Docs": "SMSManager API Docs ", + "Gateway Type": "Gateway Type", + SMSManager: "SMSManager", + "You can divide numbers with": "You can divide numbers with", + "or": "or", }; diff --git a/src/languages/zh-CN.js b/src/languages/zh-CN.js index a37d4ae4..0d21efda 100644 --- a/src/languages/zh-CN.js +++ b/src/languages/zh-CN.js @@ -581,4 +581,18 @@ export default { "Then choose an action, for example switch the scene to where an RGB light is red.": "然后您可以选择关联操作,例如切换到 RGB 灯发出红光的场景", "Frontend Version": "前端版本", "Frontend Version do not match backend version!": "前端版本与后端版本不符!", + "Base URL": "API 基础地址", + goAlertInfo: "GoAlert 是一个用于呼叫调度、自动汇报和通知(如 SMS 或语音呼叫)的开源应用程序。在正确的时间以正确的方式自动让正确的人参与!{0}", + goAlertIntegrationKeyInfo: "使用形如 aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee 的通用 API 集成密钥,通常是复制来的链接中的 token 参数值。", + goAlert: "GoAlert", + backupOutdatedWarning: "已弃用:由于大量新功能的加入,以及备份功能没有时时维护,现在备份功能已经无法生成完整的备份和恢复完整的设置。", + backupRecommend: "请改为直接备份 docker 卷或者数据文件夹(./data/)。", + Optional: "可选的", + squadcast: "Squadcast", + SendKey: "SendKey", + "SMSManager API Docs": "SMSManager API 文档在", + "Gateway Type": "网关类型", + SMSManager: "SMSManager", + "You can divide numbers with": "可用的分隔符:", + "or": "或", }; From 59e7aa74a3a7c542b06d05b963216c4246a65b06 Mon Sep 17 00:00:00 2001 From: Vasilis The Pikachu Date: Fri, 7 Oct 2022 17:51:45 +0000 Subject: [PATCH 220/803] Remove some double dots & fix backuprecommended --- src/languages/el-GR.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/languages/el-GR.js b/src/languages/el-GR.js index 92a2d775..c520a607 100644 --- a/src/languages/el-GR.js +++ b/src/languages/el-GR.js @@ -141,9 +141,9 @@ export default { Events: "Γεγονότα", Heartbeats: "Παλμοι καρδιας", "Auto Get": "Αυτόματη λήψη", - backupDescription: "Μπορείτε να δημιουργήσετε αντίγραφα ασφαλείας γία ολλες της παρακολούθησης και ειδοποιήσης σε ένα αρχείο JSON..", + backupDescription: "Μπορείτε να δημιουργήσετε αντίγραφα ασφαλείας γία ολλες της παρακολούθησης και ειδοποιήσης σε ένα αρχείο JSON.", backupDescription2: "Σημείωση: δεν περιλαμβάνονται δεδομένα ιστορικού και συμβάντων.", - backupDescription3: "Στο αρχείο εξαγωγής περιλαμβάνονται ευαίσθητα δεδομένα, όπως token ειδοποιήσεων. Aποθηκεύστε την εξαγωγή με ασφάλεια..", + backupDescription3: "Στο αρχείο εξαγωγής περιλαμβάνονται ευαίσθητα δεδομένα, όπως token ειδοποιήσεων. Aποθηκεύστε την εξαγωγή με ασφάλεια.", alertNoFile: "Επιλέξτε ένα αρχείο για εισαγωγή.", alertWrongFileType: "Επιλέξτε ένα αρχείο JSON.", "Clear all statistics": "Εκκαθάριση όλων των στατιστικών", @@ -581,5 +581,5 @@ export default { goAlertIntegrationKeyInfo: "Λάβετε το generic API integration key για την υπηρεσία σε αυτήν τη μορφή \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\" συνήθως την τιμή της παραμέτρου διακριτικού της αντιγραμμένης διεύθυνσης URL.", goAlert: "GoAlert", backupOutdatedWarning: "Καταργήθηκε: Επειδή προστέθηκαν πολλές δυνατότητες και αυτή η δυνατότητα δημιουργίας αντιγράφων ασφαλείας δεν διατηρείται πολη, δεν μπορεί να δημιουργήσει ή να επαναφέρει ένα πλήρες αντίγραφο ασφαλείας.", - backupRecommend: "Παρακαλούμε δημιουργήστε αντίγραφα ασφαλείας του τόμου ή του φακέλου δεδομένων (./data/) απευθείας.", + backupRecommend: "Παρακαλούμε δημιουργήστε αντίγραφα ασφαλείας του volume ή του φακέλου δεδομένων (./data/) απευθείας.", }; From df2f5368459cccab1a4713c11aaf3f51273dfc4c Mon Sep 17 00:00:00 2001 From: wellart Date: Sat, 8 Oct 2022 01:15:03 +0700 Subject: [PATCH 221/803] [id-ID.js] Fix some type and word (#2149) Co-authored-by: Matthew Nickson --- src/languages/id-ID.js | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/languages/id-ID.js b/src/languages/id-ID.js index 3d3c3389..f8ee3ab7 100644 --- a/src/languages/id-ID.js +++ b/src/languages/id-ID.js @@ -126,10 +126,10 @@ export default { "Resolver Server": "Resolver Server", "Resource Record Type": "Resource Record Type", "Last Result": "Hasil Terakhir", - "Create your admin account": "Buat admin akun Anda", + "Create your admin account": "Buat akun admin anda", "Repeat Password": "Ulangi Sandi", "Import Backup": "Impor Cadangan", - "Export Backup": "Expor Cadangan", + "Export Backup": "Ekspor Cadangan", Export: "Ekspor", Import: "Impor", respTime: "Tanggapan. Waktu (milidetik)", @@ -217,7 +217,7 @@ export default { smtpBCC: "BCC", discord: "Discord", "Discord Webhook URL": "Discord Webhook URL", - wayToGetDiscordURL: "Anda bisa mendapatkan ini dengan pergi ke Server Settings -> Integrations -> Create Webhook", + wayToGetDiscordURL: "Anda bisa mendapatkan ini dengan pergi ke Server Pengaturan -> Integrasi -> Buat Webhook", "Bot Display Name": "Nama Bot", "Prefix Custom Message": "Awalan Pesan", "Hello @everyone is...": "Halo {'@'}everyone is...", @@ -328,7 +328,7 @@ export default { "Pick a RR-Type...": "Pilih RR-Type...", "Pick Accepted Status Codes...": "Pilih Kode Status yang Diterima...", Default: "Default", - "HTTP Options": "HTTP Options", + "HTTP Options": "Opsi HTTP", "Create Incident": "Buat Incident", Title: "Judul", Content: "Konten", @@ -379,8 +379,8 @@ export default { smtpDkimheaderFieldNames: "Header Keys untuk ditambahkan (Optional)", smtpDkimskipFields: "Header Keys not untuk ditambahkan (Optional)", wayToGetPagerDutyKey: "Anda dapat menambahkan melalui Service -> Service Directory -> (Select a service) -> Integrations -> Add integration. Lalu Anda dapat menjadi dengan kata kunci \"Events API V2\". Informasi tambahan {0}", - "Integration Key": "Integration Key", - "Integration URL": "Integration URL", + "Integration Key": "Kunci Integrasi", + "Integration URL": "URL Integrasi", "Auto resolve or acknowledged": "Penyelesaian otomatis atau diakui", "do nothing": "tidak melakukan apapun", "auto acknowledged": "otomatis diakui", @@ -402,14 +402,14 @@ export default { enableProxyDescription: "Proxy berikut tidak akan berdampak ke monitor hingga diaktifkan. Anda dapat mengontrol menonaktifkan sementara proxy dari semua monitor dengan status aktivasi.", setAsDefaultProxyDescription: "Proxy berikut akan diaktifkan sebagai bawaan untuk monitor baru. Anda masih dapat menonaktifkan proxy secara terpisah untuk setiap monitor.", "Certificate Chain": "Certificate Chain", - Valid: "Sahih", + Valid: "Valid", Invalid: "Tidak Valid", AccessKeyId: "AccessKey ID", SecretAccessKey: "AccessKey Secret", PhoneNumbers: "Nomor Telepon", TemplateCode: "Kode Template", SignName: "Nama Tanda", - "Sms template must contain parameters: ": "Template SMS harus memuat parameter: ", + "Sms template must contain parameters: ": "Template SMS harus berisi parameter: ", "Bark Endpoint": "Bark Endpoint", "Bark Group": "Bark Group", "Bark Sound": "Bark Sound", @@ -432,7 +432,7 @@ export default { User: "Pengguna", Installed: "Terpasang", "Not installed": "Tidak terpasang", - Running: "Berlari", + Running: "Berjalan", "Not running": "Tidak berjalan", "Remove Token": "Hapus Token", Start: "Mulai", @@ -445,7 +445,7 @@ export default { "No consecutive dashes": "Tanda hubung tidak berurutan", Next: "Selanjutnya", "The slug is already taken. Please choose another slug.": "Slug telah digunakan. Silakan pilih slug lain.", - "No Proxy": "TIdak ada Proxy", + "No Proxy": "Tidak ada Proxy", Authentication: "Autentikasi", "HTTP Basic Auth": "HTTP Basic Auth", "New Status Page": "Halaman Status Baru", @@ -457,7 +457,7 @@ export default { cloudflareWebsite: "Situs Cloudflare", "Message:": "Pesan:", "Don't know how to get the token? Please read the guide:": "Tidak tahu cara mendapatkan token? Silakan baca panduannya:", - "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.": "Koneksi saat ini mungkin hilang jika Anda saat ini terhubung melalui CloudflareTunnel. Apakah Anda yakin ingin menghentikannya? Ketik kata sandi Anda saat ini untuk mengonfirmasinya.", + "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.": "Koneksi saat ini mungkin hilang jika Anda saat ini terhubung melalui Cloudflare Tunel. Apakah Anda yakin ingin menghentikannya? Ketik kata sandi Anda saat ini untuk mengonfirmasinya.", "HTTP Headers": "HTTP Headers", "Trust Proxy": "Proxy Terpercaya", "Other Software": "Perangkat Lunak lainnya", @@ -494,7 +494,7 @@ export default { "Certificate Expiry Notification": "Pemberitahuan Kedaluwarsa Sertifikat", "API Username": "Nama Pengguna API", "API Key": "Kunci API", - "Recipient Number": "Nomor Penerima Recipient Number", + "Recipient Number": "Nomor Penerima", "From Name/Number": "Dari Nama/Nomor", "Leave blank to use a shared sender number.": "Biarkan kosong untuk menggunakan nomor pengirim bersama.", "Octopush API Version": "Versi API Octopush", @@ -542,9 +542,9 @@ export default { "Go back to the previous page.": "Kembali ke halaman sebelumnya.", "Coming Soon": "Segera", wayToGetClickSendSMSToken: "Anda bisa mendapatkan Nama Pengguna API dan Kunci API dari {0} .", - "Connection String": "Connection String", + "Connection String": "String Koneksi", Query: "Query", - settingsCertificateExpiry: "Kedaluwarsa Sertifikat TLS", + settingsCertificateExpiry: "Sertifikat TLS Kadaluarsa", certificationExpiryDescription: "Monitor HTTPS memicu pemberitahuan saat sertifikat TLS kedaluwarsa dalam:", "Setup Docker Host": "Siapkan Host Docker", "Connection Type": "Jenis Koneksi", @@ -570,9 +570,9 @@ export default { "default: notify all devices": "bawaan: notifikasi seluruh perangkat", "A listof Notification Services can be found in Home Assistant under \"Developer Tools > Services\" search for \"notification\" to find your device/phone name.": "Daftar Layanan Pemberitahuan dapat ditemukan di Home Assistant pada \"Developer Tools > Services\" cari \"notification\" lalu cari nama perangkat Anda.", "Automations can optionally be triggered in Home Assistant:": "Otomatisasi dapat dipicu secara opsional di Home Assistant:", - "Trigger type:": "Trigger type:", - "Event type:": "Event type:", - "Event data:": "Event data:", + "Trigger type:": "Tipe Trigger/Pemicu:", + "Event type:": "Tipe event:", + "Event data:": "Data event:", "Then choose an action, for example switch the scene to where an RGB light is red.": "Kemudian pilih tindakan, misalnya alihkan ke tempat dimana lampu RGB berwarna merah.", "Frontend Version": "Versi Frontend", "Frontend Version do not match backend version!": "Versi Frontend tidak sama dengan versi backend!", @@ -580,6 +580,6 @@ export default { goAlertInfo: "GoAlert adalah aplikasi open source untuk penjadwalan panggilan, eskalasi otomatis dan pemberitahuan (seperti SMS atau panggilan suara). Secara otomatis melibatkan orang yang tepat, dengan cara yang benar, dan pada waktu yang tepat! {0}", goAlertIntegrationKeyInfo: "Dapatkan kunci integrasi API generik untuk layanan dalam format ini \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\" biasanya nilai parameter token dari URL yang disalin.", goAlert: "GoAlert", - backupOutdatedWarning: "Usang: Karena banyak fitur ditambahkan dan fitur cadangan ini agak tidak terawat, itu tidak dapat menghasilkan atau memulihkan cadangan lengkap.", + backupOutdatedWarning: "Tidak digunakan lagi: Karena banyak fitur ditambahkan dan fitur cadangan ini agak tidak terawat, itu tidak dapat menghasilkan atau memulihkan cadangan lengkap.", backupRecommend: "Harap cadangkan volume atau folder data (./data/) secara langsung.", }; From f67d7cdf3f3edf66415747bde53c944a600df57d Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Sat, 8 Oct 2022 08:01:47 +0100 Subject: [PATCH 222/803] Make update-language-files command more useful (#2198) * [empty commit] pull request for Fix language update script * Avoid mass changes with update-language-files This commit updates the update-language-files script to prevent mass changes as seen on a number of recent PRs where the contributer has ran the script and comitted the results. The script has been updated to now require the --language argument to specify which language file to update. This ensures that only that file is updated instead of all files. If the provided language code does not already exist, a new file with that code is created. This should make it easier to add new languages as you only need to pass the language code to the script. The base lang code is now also passed as an optional argument to negate the need for a seperate script entry in package.json. The script has been restructures into a couple of functions to make it easier to understand. ESlint now only checks the changed file instead of them all in order to improve performance. Signed-off-by: Matthew Nickson * Updated translation docs for new command Signed-off-by: Matthew Nickson * [update-language-files] Add cross-env-shell Signed-off-by: Matthew Nickson Co-authored-by: Louis Lam --- extra/update-language-files/index.js | 77 ++++++++++++++++------------ package.json | 3 +- src/languages/README.md | 13 +++-- 3 files changed, 52 insertions(+), 41 deletions(-) diff --git a/extra/update-language-files/index.js b/extra/update-language-files/index.js index e449fe34..078c4e6f 100644 --- a/extra/update-language-files/index.js +++ b/extra/update-language-files/index.js @@ -1,51 +1,45 @@ // Need to use ES6 to read language files import fs from "fs"; -import path from "path"; import util from "util"; import rmSync from "../fs-rmSync.js"; -// https://stackoverflow.com/questions/13786160/copy-folder-recursively-in-node-js /** - * Look ma, it's cp -R. - * @param {string} src The path to the thing to copy. - * @param {string} dest The path to the new copy. + * Copy across the required language files + * Creates a local directory (./languages) and copies the required files + * into it. + * @param {string} langCode Code of language to update. A file will be + * created with this code if one does not already exist + * @param {string} baseLang The second base language file to copy. This + * will be ignored if set to "en" as en.js is copied by default */ -const copyRecursiveSync = function (src, dest) { - let exists = fs.existsSync(src); - let stats = exists && fs.statSync(src); - let isDirectory = exists && stats.isDirectory(); +function copyFiles(langCode, baseLang) { + if (fs.existsSync("./languages")) { + rmSync("./languages", { recursive: true }); + } + fs.mkdirSync("./languages"); - if (isDirectory) { - fs.mkdirSync(dest); - fs.readdirSync(src).forEach(function (childItemName) { - copyRecursiveSync(path.join(src, childItemName), - path.join(dest, childItemName)); - }); + if (!fs.existsSync(`../../src/languages/${langCode}.js`)) { + fs.closeSync(fs.openSync(`./languages/${langCode}.js`, "a")); } else { - fs.copyFileSync(src, dest); + fs.copyFileSync(`../../src/languages/${langCode}.js`, `./languages/${langCode}.js`); + } + fs.copyFileSync("../../src/languages/en.js", "./languages/en.js"); + if (baseLang !== "en") { + fs.copyFileSync(`../../src/languages/${baseLang}.js`, `./languages/${baseLang}.js`); } -}; - -console.log("Arguments:", process.argv); -const baseLangCode = process.argv[2] || "en"; -console.log("Base Lang: " + baseLangCode); -if (fs.existsSync("./languages")) { - rmSync("./languages", { recursive: true }); } -copyRecursiveSync("../../src/languages", "./languages"); -const en = (await import("./languages/en.js")).default; -const baseLang = (await import(`./languages/${baseLangCode}.js`)).default; -const files = fs.readdirSync("./languages"); -console.log("Files:", files); - -for (const file of files) { - if (! file.endsWith(".js")) { - console.log("Skipping " + file); - continue; - } +/** + * Update the specified language file + * @param {string} langCode Language code to update + * @param {string} baseLang Second language to copy keys from + */ +async function updateLanguage(langCode, baseLangCode) { + const en = (await import("./languages/en.js")).default; + const baseLang = (await import(`./languages/${baseLangCode}.js`)).default; + let file = langCode + ".js"; console.log("Processing " + file); const lang = await import("./languages/" + file); @@ -83,5 +77,20 @@ for (const file of files) { fs.writeFileSync(`../../src/languages/${file}`, code); } +// Get command line arguments +const baseLangCode = process.env.npm_config_baselang || "en"; +const langCode = process.env.npm_config_language; + +// We need the file to edit +if (langCode == null) { + throw new Error("Argument --language= must be provided"); +} + +console.log("Base Lang: " + baseLangCode); +console.log("Updating: " + langCode); + +copyFiles(langCode, baseLangCode); +await updateLanguage(langCode, baseLangCode); rmSync("./languages", { recursive: true }); + console.log("Done. Fixing formatting by ESLint..."); diff --git a/package.json b/package.json index d478a1d2..f5f78f3a 100644 --- a/package.json +++ b/package.json @@ -51,8 +51,7 @@ "test-nodejs16": "docker build --progress plain -f test/ubuntu-nodejs16.dockerfile .", "simple-dns-server": "node extra/simple-dns-server.js", "simple-mqtt-server": "node extra/simple-mqtt-server.js", - "update-language-files-with-base-lang": "cd extra/update-language-files && node index.js %npm_config_base_lang% && eslint ../../src/languages/**.js --fix", - "update-language-files": "cd extra/update-language-files && node index.js && eslint ../../src/languages/**.js --fix", + "update-language-files": "cd extra/update-language-files && node index.js && cross-env-shell eslint ../../src/languages/$npm_config_language.js --fix", "ncu-patch": "npm-check-updates -u -t patch", "release-final": "node extra/update-version.js && npm run build-docker && node ./extra/press-any-key.js && npm run upload-artifacts && node ./extra/update-wiki-version.js", "release-beta": "node extra/beta/update-version.js && npm run build && node ./extra/env2arg.js docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:$VERSION -t louislam/uptime-kuma:beta . --target release --push && node ./extra/press-any-key.js && npm run upload-artifacts", diff --git a/src/languages/README.md b/src/languages/README.md index d505476a..eddd6102 100644 --- a/src/languages/README.md +++ b/src/languages/README.md @@ -1,10 +1,13 @@ # How to translate 1. Fork this repo. -2. Create a language file (e.g. `zh-TW.js`). The filename must be ISO language code: http://www.lingoes.net/en/translator/langcode.htm -3. Run `npm run update-language-files`. You can also use this command to check if there are new strings to translate for your language. -4. Your language file should be filled in. You can translate now. -5. Add it into `languageList` constant. -6. Make a [pull request](https://github.com/louislam/uptime-kuma/pulls) when you have done. +2. Run `npm run update-language-files --language=` where `` + is a valid ISO language code: + http://www.lingoes.net/en/translator/langcode.htm. You can also use + this command to check if there are new strings to + translate for your language. +3. Your language file should be filled in. You can translate now. +4. Add it into `languageList` constant. +5. Make a [pull request](https://github.com/louislam/uptime-kuma/pulls) when you have done. If you do not have programming skills, let me know in [the issues section](https://github.com/louislam/uptime-kuma/issues). I will assist you. 😏 From 3eaccb560ed1a0590a6bb3bf7f73765bbe06db00 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sat, 8 Oct 2022 16:23:11 +0800 Subject: [PATCH 223/803] Implement Download Logic --- extra/exe-builder/DownloadForm.Designer.cs | 84 +++++ extra/exe-builder/DownloadForm.cs | 65 ++++ extra/exe-builder/DownloadForm.resx | 377 +++++++++++++++++++++ extra/exe-builder/Program.cs | 23 +- extra/exe-builder/UptimeKuma.csproj | 9 + 5 files changed, 554 insertions(+), 4 deletions(-) create mode 100644 extra/exe-builder/DownloadForm.Designer.cs create mode 100644 extra/exe-builder/DownloadForm.cs create mode 100644 extra/exe-builder/DownloadForm.resx diff --git a/extra/exe-builder/DownloadForm.Designer.cs b/extra/exe-builder/DownloadForm.Designer.cs new file mode 100644 index 00000000..26a474e9 --- /dev/null +++ b/extra/exe-builder/DownloadForm.Designer.cs @@ -0,0 +1,84 @@ +using System.ComponentModel; + +namespace UptimeKuma { + partial class DownloadForm { + /// + /// Required designer variable. + /// + private IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(DownloadForm)); + this.progressBar = new System.Windows.Forms.ProgressBar(); + this.label = new System.Windows.Forms.Label(); + this.labelData = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // progressBar + // + this.progressBar.Location = new System.Drawing.Point(12, 12); + this.progressBar.Name = "progressBar"; + this.progressBar.Size = new System.Drawing.Size(472, 41); + this.progressBar.TabIndex = 0; + // + // label + // + this.label.Location = new System.Drawing.Point(12, 59); + this.label.Name = "label"; + this.label.Size = new System.Drawing.Size(472, 23); + this.label.TabIndex = 1; + this.label.Text = "Preparing..."; + // + // labelData + // + this.labelData.Location = new System.Drawing.Point(12, 82); + this.labelData.Name = "labelData"; + this.labelData.Size = new System.Drawing.Size(472, 23); + this.labelData.TabIndex = 2; + // + // DownloadForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(496, 117); + this.Controls.Add(this.labelData); + this.Controls.Add(this.label); + this.Controls.Add(this.progressBar); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.MaximizeBox = false; + this.Name = "DownloadForm"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "Uptime Kuma"; + this.Load += new System.EventHandler(this.DownloadForm_Load); + this.ResumeLayout(false); + } + + private System.Windows.Forms.Label labelData; + + private System.Windows.Forms.Label label; + + private System.Windows.Forms.ProgressBar progressBar; + + #endregion + } +} + diff --git a/extra/exe-builder/DownloadForm.cs b/extra/exe-builder/DownloadForm.cs new file mode 100644 index 00000000..9c740e31 --- /dev/null +++ b/extra/exe-builder/DownloadForm.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Net; +using System.Windows.Forms; + +namespace UptimeKuma { + public partial class DownloadForm : Form { + private readonly Queue downloadQueue = new(); + private readonly WebClient webClient = new(); + + public DownloadForm() { + InitializeComponent(); + } + + private void DownloadForm_Load(object sender, EventArgs e) { + webClient.DownloadProgressChanged += DownloadProgressChanged; + webClient.DownloadFileCompleted += DownloadFileCompleted; + + if (!File.Exists("node")) { + downloadQueue.Enqueue(new DownloadItem { + URL = "https://nodejs.org/dist/v16.17.1/node-v16.17.1-win-x64.zip", + Filename = "node.zip" + }); + } + + if (!File.Exists("node")) { + downloadQueue.Enqueue(new DownloadItem { + URL = "https://github.com/louislam/uptime-kuma/archive/refs/tags/1.18.3.zip", + Filename = "core.zip" + }); + } + + DownloadNextFile(); + } + + void DownloadNextFile() { + if (downloadQueue.Count > 0) { + var item = downloadQueue.Dequeue(); + label.Text = item.URL; + webClient.DownloadFileAsync(new Uri(item.URL), item.Filename); + } else { + // TODO: Finished, extract? + } + } + + void DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) { + progressBar.Value = e.ProgressPercentage; + var total = e.TotalBytesToReceive / 1024; + var current = e.BytesReceived / 1024; + labelData.Text = $"{current}KB/{total}KB"; + } + + void DownloadFileCompleted(object sender, AsyncCompletedEventArgs e) { + DownloadNextFile(); + } + } + + public class DownloadItem { + public string URL { get; set; } + public string Filename { get; set; } + } +} + diff --git a/extra/exe-builder/DownloadForm.resx b/extra/exe-builder/DownloadForm.resx new file mode 100644 index 00000000..e87e0c0d --- /dev/null +++ b/extra/exe-builder/DownloadForm.resx @@ -0,0 +1,377 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + AAABAAMAMDAAAAEAIACoJQAANgAAACAgAAABACAAqBAAAN4lAAAQEAAAAQAgAGgEAACGNgAAKAAAADAA + AABgAAAAAQAgAAAAAAAAJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA////BPT09Bfu7u4e8fHxJPPz8yv19fUy9fX1M/Pz8yvx8fEk9vb2HPPz8xXMzMwFAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// + /wHv7+8f7u7uPPPz81Tx8fFs8fHxgPHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGB8fHxcfHx8V3x8fFI9PT0MOvr6w0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADy8vIU8fHxS/Dw8Hbx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fFr9PT0R/Dw8CIAAAABAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA8vLyFPHx8Vnx8fGB8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fFs9fX1Mb+/vwQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAICAgALy8vI88fHxfvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvLy8nby8vI8gICAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAzMzMBfHx8Vrx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8vLyYf///wwAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMzMwF8vLyYPHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8W/z8/MWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADv7+9R8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLw8PB26urqDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPLy8ijx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgu7w7Ifj79ud2u7PtNLrw83P677dzeu85c3r + u+rM67rwzOu68c7rverQ68Dj0uvD3NbuyM3b7c+64u7apujv5ZPx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxXgAAAAEAAAAAAAAAAAAAAAAAAAAA4+PjCfDw + 8Hfx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLd7tSmzeu92MbqsvvG6bH/xumy/8fq + s//H6rP/yOq0/8jqtf/J6rb/yeq2/8rrt//K67j/y+u4/8vruf/M67r/zOu7/83ru//Q7MDx1u7Kz9/t + 163s8OuJ8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgu/v7y8AAAAAAAAAAAAA + AAAAAAAA7u7uPfHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC5PDdl8jqtuTE6a7/xOmv/8Xp + sP/G6bH/xumx/8bpsv/H6rP/x+qz/8jqtP/I6rX/yeq2/8nqtv/K67f/yuu4/8vruP/L67n/zOu6/8zr + u//N67v/zey8/87svf/P67742e3Mx+jv5ZLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvDw + 8HWAgIACAAAAAAAAAACqqqoD8vLyc/Hx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLf7degxOiu+cPo + rf/D6a7/xOmu/8Xpr//F6bD/xumx/8bpsf/G6bL/x+qz/8fqs//I6rT/yOq1/8nqtv/J6rb/yuu3/8rr + uP/L67j/y+u5/8zruv/M67v/zeu7/83svP/O7L3/zuy9/87svfzc7tK28fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fEkAAAAAAAAAADz8/Mq8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgunv + 5o3D6a/0wuis/8Lorf/D6K3/xOmu/8Tprv/F6a//xemw/8bpsf/G6bH/xumy/8fqs//H6rP/yOq0/8jq + tf/J6rb/yeq2/8rrt//K67j/y+u4/8vruf/M67r/zOu7/83ru//N7Lz/zuy9/87svf/O7L3/3e/TtPHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLy8vJNAAAAAAAAAADy8vJM8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgszqutDB6Kv/weir/8LorP/D6K3/w+it/8Tprv/E6a7/xemv/8XpsP/G6bH/xumx/8bp + sv/H6rP/x+qz/8jqtP/I6rX/yeq2/8nqtv/K67f/yuu4/8vruP/L67n/zOu6/8zru//N67v/zey8/87s + vf/O7L3/zuy++u3w6Yzx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLy8vJ1AAAAAAAAAADx8fFr8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC6O/kjsDoqvzA6Kr/weir/8Loq//C6Kz/w+it/8Porf/E6a7/xOmu/8Xp + r//F6bD/xumx/8bpsf/G6bL/x+qz/8fqtP/I6rT/yOq1/8nqtv/J6rb/yuu3/8rruP/L67n/y+u5/8zr + uv/M67v/zeu7/83svP/O7L3/zuy9/93u07Xx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC////Bv// + /wfx8fGB8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC1ezJsr/nqf/A56n/weiq/8Hoq//C6Kv/wuis/8Po + rf/D6K3/xOmu/8Pprv+856T/uOed/7bmmv+05Zf/teWZ/7jnnf+86KP/wOio/8fqs//J6rb/yeq2/8rr + t//K67j/y+u5/8vruf/M67r/zOu7/83ru//N7Lz/zuy9/9buyNLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8vLyE/Ly8hPx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGCy+q6zr/nqP/A56n/wOep/8Ho + qv/B6Kv/wuir/8LorP+u5Y//neF2/5bgav+V4Gr/luBr/5fhbP+Y4W7/meFv/5rhcf+b4nL/nOJ0/53i + dv+j5H//reaM/7nnnf/E6q//y+y4/8vruf/L67n/zOu6/8zru//N67v/zey8/9Lsxd/x8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC7+/vIPb29hzx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGCx+m03L/n + qP+/56j/wOep/8Dnqf/B6Kr/weir/7nmn/+R32T/kt9l/5PfZ/+U4Gj/leBq/5bga/+X4W3/mOFu/5nh + b/+a4XH/m+Jy/5zidP+d4nX/nuN3/5/jeP+f4nn/weqq/8rruP/L67n/y+u5/8zruv/M67v/zeu7/9Ls + w+Lx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8PDwI/Hx8SXx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGCxeix5L/nqP+/56j/v+eo/8Dnqf/A56n/weiq/7Pllv+Q3mP/kd9k/5LfZf+T32f/lOBo/5Xg + av+W4Gv/l+Ft/5jhbv+Z4W//muFx/5vicv+c4nT/neJ1/57jd/+f43j/xOmu/8rrt//K67j/y+u5/8vr + uf/M67r/zOu7/9Tsxtfx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC9PT0GO/v7yDx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGCx+m037/nqP+/56j/v+eo/7/nqP/A56n/wOip/7TmmP+P3mH/kN5j/5Hf + ZP+S32b/k99n/5TgaP+V4Gr/luBr/5fhbf+Y4W7/meFw/5rhcf+b4nL/nOJ0/53idf+h5Hz/yuu2/8nq + t//K67f/yuu4/8vruf/L67n/zOu6/9ftysrx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC7e3tDvT0 + 9Bfx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGCyOq117/nqP+/56j/v+eo/7/nqP+/56j/wOep/7vn + of+O3mD/j95h/5DeY/+R32T/kt9m/5PfZ/+U4Gj/leBq/5bga/+X4W3/mOFu/5nhcP+a4nH/m+Jy/5zi + dP+r5Yr/yOq1/8nqtv/J6rf/yuu3/8rruP/L67n/y+u5/9zu1LHx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLz8/OA////A+7u7g/x8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGCz+q+xb/nqP+/56j/v+eo/7/n + qP+/56j/v+eo/8Dnqf+S4Gb/jt5g/4/eYf+Q3mP/kd9k/5LfZv+T32f/lOBo/5Xgav+W4Gv/l+Ft/5jh + bv+Z4XD/muJx/5vic/+4553/yOq0/8jqtf/J6rb/yeq3/8rrt//K67j/y+u5/+bw4Zfx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fFrAAAAAP///wHz8/N88fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC1+zMrr/n + qP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+f4Xn/jd5f/47eYP+P3mH/kN5j/5HfZP+S32b/k99n/5Tg + af+V4Gr/luBr/5fhbf+Y4W7/meFw/5vic//F6rD/x+q0/8jqtP/I6rX/yeq2/8nqt//K67f/zOu88u/x + 74Px8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLv7+9QAAAAAAAAAADw8PBm8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC5e7gk7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+u5I//jN1d/43eX/+O3mD/j95h/5De + Y/+R32T/kt9m/5PfZ/+U4Gn/leBq/5bga/+X4W3/mOFu/6rliP/G6rL/x+qz/8fqtP/I6rT/yOq1/8nq + tv/J6rf/1OzGy/Hx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YL19fUzAAAAAAAAAADy8vJO8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgsPoru2/56j/v+eo/7/nqP+/56j/v+eo/7/nqP++6Kf/j95i/4zd + Xf+N3l//jt5g/4/eYv+Q3mP/kd9k/5LfZv+T32f/lOBp/5Xgav+W4Gz/l+Ft/7voov/G6bL/xuqy/8fq + s//H6rT/yOq1/8jqtf/J6rb/4e/Zo/Hx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLw8PARAAAAAAAA + AADu7u4u8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgszpvMm/56j/v+eo/7/nqP+/56j/v+eo/7/n + qP+/56j/q+SL/4vdXP+M3V3/jd5f/47eYP+P3mL/kN9j/5HfZP+S32b/k99n/5Tgaf+V4Gr/qOOH/8Xp + sP/G6bH/xumy/8bqsv/H6rP/x+q0/8jqtf/K67jy8PHwhPHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8WoAAAAAAAAAAAAAAADo6OgL8fHxgfHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxguDv2J2/56j/v+eo/7/n + qP+/56j/v+eo/7/nqP+/56j/v+eo/6Xjgv+L3Vz/jN1d/43eX/+O3mD/j95i/5DfY/+R32T/kt9m/5Pf + Z/+k44D/xOmu/8XpsP/F6bD/xumx/8bpsv/G6rL/x+qz/8fqtP/W7cnB8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvPz80AAAAAAAAAAAAAAAAAAAAAA8PDwZ/Hx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLD6K/rv+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+u5I//kt5n/4zdXf+N3l//jt5g/4/e + Yv+Q32P/luFs/67kj//D6K3/xOmu/8Tpr//F6bD/xemw/8bpsf/G6bL/xuqy/8fqtP7o7+WR8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvPz8xYAAAAAAAAAAAAAAAAAAAAA8vLyPPHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLV7ci0v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/wOio/7Xl + mv+u5I7/rOSM/67kj/+35pz/wumr/8Lorf/D6K3/w+it/8Tprv/E6a//xemw/8XpsP/G6bH/xumy/9Ds + wNPx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8vLyZQAAAAAAAAAAAAAAAAAAAAAAAAAA////DPHx + 8YDx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGCx+m03L/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/n + qP+/56j/v+eo/7/nqP+/56j/wOep/8Doqv/B6Kr/weir/8LorP/C6K3/w+it/8Porv/E6a7/xOmv/8Xp + sP/F6bD/yOq18uvw6Yvx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC7+/vMQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAPHx8Vzx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC6O/ij8LorPG/56j/v+eo/7/n + qP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/8Dnqf/A6Kr/weiq/8Hoq//C6Kz/wuit/8Po + rf/D6K7/xOmu/8Tpr//F6bH74u/anvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLw8PB6////BQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPPz8yrx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxguHu + 2pnB56v2v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP/A56n/wOiq/8Ho + q//B6Kv/wuis/8Lorf/D6K3/w+mu/8Tprv3b7dKq8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fFJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHy8vJf8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLi7tyXwumt8L/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/n + qP+/56j/wOep/8Doqv/B6Kv/weir/8LorP/C6K3/xOiv+d7u1aTx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvLy8nb///8KAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADv7+8Q8/Pze/Hx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC6/Dpiszqu82/56j/v+eo/7/nqP+/56j/v+eo/7/n + qP+/56j/v+eo/7/nqP+/56j/v+eo/8Dnqf/A6Kr/weir/8Hoq//H6bTj5e7elfHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvPz8yoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA9fX1MvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLe7tShx+mz3r/n + qP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/v+eo/7/nqP/A56n/xumy5drtz6rv8e+D8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8vLyTgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPHx8Unx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgubv45DU68e2y+q6z8XoseTD6a7uweir9MPpru7F6bHly+q50tLsxLrl796U8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLy8vJh////AwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wHx8fFZ8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8Wzf398IAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8D8/PzVfHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8PDwZujo + 6AsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA////AfHx8Ujx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fFa////BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz8/Mp8vLydvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8/PzfPHx8TcAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////CvLy8lDz8/N/8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvPz84Hx8fFa8PDwEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADw8PAR8vLyTvHx8X3x8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fF/8/PzVvT09BgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wXz8/Mq8/PzU/Hx8XDx8fGB8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLy8vJz8fHxWO/v7y////8IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8G7e3tHfLy + 8ifu7u4u8PDwNPT09C/y8vIo7+/vH+Pj4wkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP///////wAA////////AAD///////8AAP//gAf//wAA//gAAD//AAD/wAAAB/8AAP+A + AAAB/wAA/gAAAAB/AAD8AAAAAD8AAPgAAAAAHwAA8AAAAAAPAADwAAAAAAcAAOAAAAAABwAA4AAAAAAD + AADAAAAAAAMAAMAAAAAAAwAAwAAAAAABAACAAAAAAAEAAIAAAAAAAQAAgAAAAAABAACAAAAAAAEAAIAA + AAAAAQAAgAAAAAABAACAAAAAAAMAAMAAAAAAAwAAwAAAAAADAADAAAAAAAMAAMAAAAAABwAAwAAAAAAH + AADgAAAAAAcAAOAAAAAADwAA4AAAAAAPAADwAAAAAB8AAPAAAAAAHwAA+AAAAAA/AAD8AAAAAD8AAPwA + AAAAfwAA/gAAAAD/AAD/AAAAAf8AAP+AAAAD/wAA/8AAAAf/AAD/8AAAH/8AAP/8AAA//wAA//8AAf// + AAD//+AP//8AAP///////wAA////////AAD///////8AACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAgICAAu/v7xD09PQX7u7uHvDw8CP29vYb8vLyFOrq6gwAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICA + gALy8vIm7+/vT/Pz82fz8/N98fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvDw8Hrw8PBm7+/vUPT0 + 9C3o6OgLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOPj + 4wnz8/NC8vLydPHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YHy8vJj8/PzKoCAgAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADx8fEl8vLydfHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxcfHx8SUAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA9PT0LfHx8YDx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8/PzgPLy8j0AAAABAAAAAAAA + AAAAAAAAAAAAAO3t7Rzx8fGA8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLr8OmM5O7emeTv + 3Z7h79mj5fDem+nv45Tu8u6H8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvLy + 8joAAAAAAAAAAAAAAAD///8E8fHxbvHx8YLx8fGC8fHxgvHx8YLx8fGC7vDshtns0K7N67zayeq288fq + s//I6rT/yOq1/8nqtv/K67f/y+u4/8vruf/P7L7w0+zF29vv0Lrn8OKX8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8/PzfvPz8xUAAAAAAAAAAPX19TLx8fGC8fHxgvHx8YLx8fGC8fHxgt3u1KXF6rHzxOmv/8Xp + sP/G6bH/xumy/8fqs//I6rT/yOq1/8nqtv/K67f/y+u4/8vruf/M67v/zey8/87svf/S7MPj4u7Zp/Hx + 8YLx8fGC8fHxgvHx8YLx8fGC8/PzVQAAAAAAAAAA8fHxavHx8YLx8fGC8fHxgvHx8YLf7defwuis/cPo + rf/E6a7/xOmv/8XpsP/G6bH/xumy/8fqs//I6rT/yOq1/8nqtv/K67f/y+u4/8vruv/M67v/zey8/87s + vf/N67z/3e7SufHx8YLx8fGC8fHxgvHx8YLz8/N8////Bf///w3x8fGC8fHxgvHx8YLx8fGC8fHxgsXp + sOnB6Kv/wuis/8Porf/E6a7/xOmv/8XpsP/G6bH/xumy/8fqs//I6rT/yOq1/8nqtv/K67f/y+u4/8vr + uv/M67v/zey8/87svf/O67z96/Hoj/Hx8YLx8fGC8fHxgvHx8YLy8vIm8/PzK/Hx8YLx8fGC8fHxgvHx + 8YLg79icwOep/8Hoqv/B6Kv/wuis/8Porf/E6a7/wuit/73opP+76KL/u+eh/77opv/D6a3/yeu1/8nq + tv/K67f/y+u5/8zruv/M67v/zey8/87svf/d7tSz8fHxgvHx8YLx8fGC8fHxgvHx8Tby8vI68fHxgvHx + 8YLx8fGC8fHxgtTrxre/56j/wOep/8Hoqv/B6Kv/uOad/53idv+V4Gn/leBq/5fhbP+Y4W//muFx/5vi + c/+e4Xb/puWD/7PmlP/D6a3/y+u5/8zruv/M67v/zey8/9rtzsHx8fGC8fHxgvHx8YLx8fGC8/PzQfPz + 80Lx8fGC8fHxgvHx8YLx8fGC0OvAwr/nqP+/56j/wOep/8Hoqv+o44b/kd9k/5LfZv+U4Gj/leBq/5fh + bf+Y4W//muFx/5vic/+d4nX/n+N3/7fnm//K67j/y+u5/8zruv/M67v/2u3QvPHx8YLx8fGC8fHxgvHx + 8YLy8vI98/PzP/Hx8YLx8fGC8fHxgvHx8YLQ6sK/v+eo/7/nqP+/56j/wOep/6jjhv+P3mL/kd9k/5Lf + Zv+U4Gj/leBr/5fhbf+Y4W//muFx/5zic/+d4nX/v+mm/8nqt//K67j/y+u5/8zruv/f79au8fHxgvHx + 8YLx8fGC8fHxgvX19TLx8fE38fHxgvHx8YLx8fGC8fHxgtTrybO/56j/v+eo/7/nqP+/56j/sOSS/47e + YP+P3mL/kd9k/5LfZv+U4Gj/leBr/5fhbf+Z4W//muJx/5/jd//H6bP/yeq2/8nqt//K67j/y+u5/+nv + 45Tx8fGC8fHxgvHx8YLx8fGC7+/vIPHx8SXx8fGC8fHxgvHx8YLx8fGC4e/Zm7/nqP+/56j/v+eo/7/n + qP+956X/jt5h/47eYP+P3mL/kd9k/5LfZv+U4Gn/luBr/5fhbf+Z4W//q+aK/8fqs//I6rT/yeq2/8nq + t//N7Lvw8fHxgvHx8YLx8fGC8fHxgvPz84D///8G6+vrDfHx8YLx8fGC8fHxgvHx8YLv8e+Dweis87/n + qP+/56j/v+eo/7/nqP+d4XX/jN1e/47eYP+P3mL/kd9k/5PfZ/+U4Gn/luBr/5fhbf+86KP/xuqy/8fq + s//I6rX/yeq2/9Tsx8nx8fGC8fHxgvHx8YLx8fGC8PDwaAAAAAAAAAAA8fHxbPHx8YLx8fGC8fHxgvHx + 8YLM6rrMv+eo/7/nqP+/56j/v+eo/7blmv+N3V//jN1e/47eYP+Q3mL/kd9k/5PfZ/+U4Gn/qeSH/8Xp + sP/G6bH/xuqy/8fqs//I6rX/5fDem/Hx8YLx8fGC8fHxgvHx8YLz8/M/AAAAAAAAAADz8/NB8fHxgvHx + 8YLx8fGC8fHxgt3s06O/56j/v+eo/7/nqP+/56j/v+eo/7Xmmf+U32n/jN1e/47eYP+Q3mL/k99o/6zk + i//D6a7/xemv/8XpsP/G6bH/xuqy/8vqu+jx8fGC8fHxgvHx8YLx8fGC8fHxgvPz8xUAAAAAAAAAAPT0 + 9Bfx8fGC8fHxgvHx8YLx8fGC8fHvg8Tpsee/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+35pz/suWV/7Xm + mf/A6Kj/wuit/8Porf/E6a7/xemv/8XpsP/G6bH/3e3UqvHx8YLx8fGC8fHxgvHx8YLw8PBmAAAAAAAA + AAAAAAAAAAAAAPHx8W7x8fGC8fHxgvHx8YLx8fGC4u7cmMHnqvm/56j/v+eo/7/nqP+/56j/v+eo/7/n + qP+/56j/wOep/8Hoqv/C6Kz/wuit/8Porf/E6a7/xemv/9Hrwszx8fGC8fHxgvHx8YLx8fGC8fHxgvX1 + 9TEAAAAAAAAAAAAAAAAAAAAA7u7uO/Hx8YLx8fGC8fHxgvHx8YLx8fGC3e7SpMHoqfq/56j/v+eo/7/n + qP+/56j/v+eo/7/nqP+/56j/wOip/8Hoq//C6Kz/wuit/8Porf/O67zV8PHwhPHx8YLx8fGC8fHxgvHx + 8YLy8vJ2////BQAAAAAAAAAAAAAAAAAAAACqqqoD8PDwafHx8YLx8fGC8fHxgvHx8YLx8fGC4O/YnMTo + ruy/56j/v+eo/7/nqP+/56j/v+eo/7/nqP+/56j/wOip/8Hoq//C6Kz90uvEwe/x74Px8fGC8fHxgvHx + 8YLx8fGC8fHxgvPz8ykAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz8/MW8fHxfPHx8YLx8fGC8fHxgvHx + 8YLx8fGC8PLuhdXtyLXF6bHlv+eo/7/nqP+/56j/v+eo/7/nqP/B6Kv0zeq8zOXv4JTx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLy8vJNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADy8vIm8fHxgPHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLs8OmJ4e/Zm93u06Pf7def5+/hkvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxXf///wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADy8vIo8/PzffHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8VnMzMwFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD29vYb8fHxbvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvPz83/v7+9BgICAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMzMwF8/PzQPLy8nnx8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgvPz84Hx8fFc9PT0GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////B/X19TLx8fFc8PDwevHx + 8YLx8fGC8fHxgvHx8YLx8fGC8fHxgPHx8Wv09PRE9PT0FwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA7+/vEPb29hvw8PAj7+/vH/T09Be/v78EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////8B///wAA//wAAD/wAAAP4AAAB+AA + AAfAAAADwAAAA4AAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAADwAAAA8AAAAPAAAAH4AAAB+AA + AA/wAAAP+AAAH/gAAD/+AAB//wAB///AA///+B////////////8oAAAAEAAAACAAAAABACAAAAAAAAAE + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////CfDw8BH///8GAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAAu7u7i7x8fFe8PDwevHx8YLx8fGC8fHxgvDw + 8Hvx8fFs7+/vT/Dw8CMAAAABAAAAAAAAAAAAAAAA5ubmCvLy8l/x8fGC8fHxgvHx8YLx8fGC8fHxgvHx + 8YLx8fGC8fHxgvHx8YLx8fGC8/PzZu7u7g8AAAAAAAAAAPHx8V3x8fGC8fHxgunv5o7Z7c200+vFytTs + xc7W7cnH2+7QueLu2qbu8OyH8fHxgvHx8YLx8fFu////BfHx8STx8fGC8fHxgtrtzq3D6a/8xemw/8bp + sv/I6rT/yeq2/8vruP/M67v/z+u++Nzu0bjx8fGC8fHxgu/v7zDx8fFI8fHxguzw6ojC56z3wuis/8Tp + rv/E6q3/weiq/8fqsv/J6rb/y+u5/8zru//N67z/6/HpjfHx8YLy8vJN8fHxXPHx8YLg79icv+eo/8Ho + qv+k4n//lOBo/5fhbf+a4XH/n+J5/7Pmlv/L67n/zOu7/+Xw353x8fGC8fHxXvHx8Vrx8fGC4O3Zm7/n + qP+/56j/nuF3/5HfZP+U4Gj/l+Ft/5ricf+x5pL/yeq3/8vruf/r8emN8fHxgu/v70/x8fFK8fHxguzw + 6ojA6Kn8v+eo/6njiP+O3mD/kd9k/5Tgaf+X4W3/vuim/8jqtP/N67zr8fHxgvHx8YLy8vI68/PzK/Hx + 8YLx8fGCx+m03L/nqP++6Kb/meBw/47eYP+S32X/q+SL/8XpsP/G6rL/1+zLvvHx8YLz8/OB8PDwEdXV + 1Qbx8fF98fHxgt/t1Z/A56j9v+eo/7/nqP+656H/vuim/8Lorf/E6a7/yOq18Ovw6Yvx8fGC8vLyYwAA + AAAAAAAA8fHxR/Hx8YLx8fGC2O3NrMDnqfq/56j/v+eo/7/nqP/B6Kv/xumy7OTu3Zfx8fGC8/PzgfLy + 8icAAAAAAAAAAP///wPz8/Nm8fHxgvHx8YLo7+SO0+zFuczquszM6bzJ1+zMru7w7Ibx8fGC8fHxgvHx + 8UcAAAAAAAAAAAAAAAAAAAAA4+PjCfHx8Vzx8fGC8fHxgvHx8YLx8fGC8fHxgvHx8YLx8fGC8fHxgfPz + 80D///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8/PzK/Ly8mDz8/N+8fHxgvHx8YLy8vJ68vLyUezs + 7BsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAevr6w3j4+MJAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//AAD8fwAA4AcAAMADAACAAQAAgAEAAIABAACAAQAAgAEAAIAB + AADAAwAAwAMAAOAHAADwDwAA/n8AAP//AAA= + + + \ No newline at end of file diff --git a/extra/exe-builder/Program.cs b/extra/exe-builder/Program.cs index 84ecda31..84aa6e45 100644 --- a/extra/exe-builder/Program.cs +++ b/extra/exe-builder/Program.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Drawing; +using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; @@ -42,9 +43,23 @@ namespace UptimeKuma { }); trayIcon.MouseDoubleClick += new MouseEventHandler(Open); - trayIcon.Visible = true; + if (File.Exists("core") && File.Exists("node")) { + // Go go go + StartProcess(); + } else { + DownloadFiles(); + } + } + + void DownloadFiles() { + var form = new DownloadForm(); + form.Closed += Exit; + form.Show(); + } + + void StartProcess() { var startInfo = new ProcessStartInfo { FileName = "node/node.exe", Arguments = "server/server.js --data-dir=\"../data/\"", @@ -58,8 +73,7 @@ namespace UptimeKuma { process = new Process(); process.StartInfo = startInfo; process.EnableRaisingEvents = true; - process.Exited += new EventHandler(ProcessExited); - + process.Exited += ProcessExited; try { process.Start(); @@ -96,7 +110,8 @@ namespace UptimeKuma { { // Hide tray icon, otherwise it will remain shown until user mouses over it trayIcon.Visible = false; - process.Kill(); + process?.Kill(); + Application.Exit(); } void ProcessExited(object sender, EventArgs e) { diff --git a/extra/exe-builder/UptimeKuma.csproj b/extra/exe-builder/UptimeKuma.csproj index c3c6aad2..aa0a8bf8 100644 --- a/extra/exe-builder/UptimeKuma.csproj +++ b/extra/exe-builder/UptimeKuma.csproj @@ -51,8 +51,17 @@ + + Form + + + DownloadForm.cs + + + DownloadForm.cs + ResXFileCodeGenerator Resources.Designer.cs From 230e5110b1db0527c763daa680fa556a06d3bd9b Mon Sep 17 00:00:00 2001 From: Kevin Falentio Date: Sat, 8 Oct 2022 19:59:22 +0700 Subject: [PATCH 224/803] Fix typo in id-ID language file (#2202) --- src/languages/id-ID.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/id-ID.js b/src/languages/id-ID.js index f8ee3ab7..fe5d3594 100644 --- a/src/languages/id-ID.js +++ b/src/languages/id-ID.js @@ -565,7 +565,7 @@ export default { Examples: "Contoh", "Home Assistant URL": "Home Assistant URL", "Long-Lived Access Token": "Token Akses Berumur Panjang", - "Long-Lived Access Token canbe created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "Token Akses Berumur Panjang dapat dibuat dengan mengklik nama profil Anda (kiri bawah) dan menggulir ke bawah lalu klik Buat Token. ", + "Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "Token Akses Berumur Panjang dapat dibuat dengan mengklik nama profil Anda (kiri bawah) dan menggulir ke bawah lalu klik Buat Token. ", "Notification Service": "Layanan Pemberitahuan", "default: notify all devices": "bawaan: notifikasi seluruh perangkat", "A listof Notification Services can be found in Home Assistant under \"Developer Tools > Services\" search for \"notification\" to find your device/phone name.": "Daftar Layanan Pemberitahuan dapat ditemukan di Home Assistant pada \"Developer Tools > Services\" cari \"notification\" lalu cari nama perangkat Anda.", From 655ba015a07c87e7b1a24ce8cafbe171103acad1 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sat, 8 Oct 2022 23:47:27 +0800 Subject: [PATCH 225/803] WIP --- extra/exe-builder/DownloadForm.cs | 127 ++++++++++++++++++++++++++-- extra/exe-builder/Program.cs | 2 +- extra/exe-builder/UptimeKuma.csproj | 3 +- 3 files changed, 121 insertions(+), 11 deletions(-) diff --git a/extra/exe-builder/DownloadForm.cs b/extra/exe-builder/DownloadForm.cs index 9c740e31..5bb88b6d 100644 --- a/extra/exe-builder/DownloadForm.cs +++ b/extra/exe-builder/DownloadForm.cs @@ -1,14 +1,19 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; using System.IO; +using System.IO.Compression; using System.Net; +using System.Threading; +using System.Threading.Tasks; using System.Windows.Forms; namespace UptimeKuma { public partial class DownloadForm : Form { private readonly Queue downloadQueue = new(); private readonly WebClient webClient = new(); + private DownloadItem currentDownloadItem; public DownloadForm() { InitializeComponent(); @@ -18,17 +23,19 @@ namespace UptimeKuma { webClient.DownloadProgressChanged += DownloadProgressChanged; webClient.DownloadFileCompleted += DownloadFileCompleted; - if (!File.Exists("node")) { + if (!Directory.Exists("node")) { downloadQueue.Enqueue(new DownloadItem { URL = "https://nodejs.org/dist/v16.17.1/node-v16.17.1-win-x64.zip", - Filename = "node.zip" + Filename = "node.zip", + TargetFolder = "node" }); } - if (!File.Exists("node")) { + if (!Directory.Exists("node")) { downloadQueue.Enqueue(new DownloadItem { URL = "https://github.com/louislam/uptime-kuma/archive/refs/tags/1.18.3.zip", - Filename = "core.zip" + Filename = "core.zip", + TargetFolder = "core" }); } @@ -38,28 +45,130 @@ namespace UptimeKuma { void DownloadNextFile() { if (downloadQueue.Count > 0) { var item = downloadQueue.Dequeue(); - label.Text = item.URL; - webClient.DownloadFileAsync(new Uri(item.URL), item.Filename); + + currentDownloadItem = item; + + // Download if the zip file is not existing + if (!File.Exists(item.Filename)) { + label.Text = item.URL; + webClient.DownloadFileAsync(new Uri(item.URL), item.Filename); + } else { + progressBar.Value = 100; + label.Text = "Use local " + item.Filename; + DownloadFileCompleted(null, null); + } } else { - // TODO: Finished, extract? + npmSetup(); } } + void npmSetup() { + if (Directory.Exists("core/node_modules")) { + // Application.Restart(); + } + + label.Text = "npm run setup"; + progressBar.Value = 50; + labelData.Text = ""; + + var startInfo = new ProcessStartInfo { + FileName = "cmd.exe", + Arguments = "run setup", + RedirectStandardOutput = false, + RedirectStandardError = false, + RedirectStandardInput = true, + UseShellExecute = false, + CreateNoWindow = false, + WorkingDirectory = "core" + }; + + var process = new Process(); + process.StartInfo = startInfo; + process.EnableRaisingEvents = true; + process.Exited += (object _, EventArgs e) => { + // Application.Restart(); + progressBar.Value = 100; + + if (process.ExitCode == 0) { + label.Text = "Done"; + } else { + label.Text = "Failed, exit code: " + process.ExitCode; + } + + }; + process.Start(); + process.StandardInput.WriteLine("\"../node/npm\" run setup"); + } + void DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) { progressBar.Value = e.ProgressPercentage; var total = e.TotalBytesToReceive / 1024; var current = e.BytesReceived / 1024; - labelData.Text = $"{current}KB/{total}KB"; + + if (total > 0) { + labelData.Text = $"{current}KB/{total}KB"; + } } - void DownloadFileCompleted(object sender, AsyncCompletedEventArgs e) { + async void DownloadFileCompleted(object sender, AsyncCompletedEventArgs e) { + Extract(currentDownloadItem); DownloadNextFile(); } + + void Extract(DownloadItem item) { + if (Directory.Exists(item.TargetFolder)) { + var dir = new DirectoryInfo(item.TargetFolder); + dir.Delete(true); + } + + if (Directory.Exists("temp")) { + var dir = new DirectoryInfo("temp"); + dir.Delete(true); + } + + labelData.Text = $"Extracting {item.Filename}..."; + + ZipFile.ExtractToDirectory(item.Filename, "temp"); + + string[] dirList; + + // Move to the correct level + dirList = Directory.GetDirectories("temp"); + + + + if (dirList.Length > 0) { + var dir = dirList[0]; + + // As sometime ExtractToDirectory is still locking the directory, loop until ok + while (true) { + try { + Directory.Move(dir, item.TargetFolder); + break; + } catch (Exception exception) { + Thread.Sleep(1000); + } + } + + } else { + MessageBox.Show("Unexcepted Error: Cannot move extracted files, folder not found."); + } + + labelData.Text = $"Extracted"; + + if (Directory.Exists("temp")) { + var dir = new DirectoryInfo("temp"); + dir.Delete(true); + } + + File.Delete(item.Filename); + } } public class DownloadItem { public string URL { get; set; } public string Filename { get; set; } + public string TargetFolder { get; set; } } } diff --git a/extra/exe-builder/Program.cs b/extra/exe-builder/Program.cs index 84aa6e45..1b78f038 100644 --- a/extra/exe-builder/Program.cs +++ b/extra/exe-builder/Program.cs @@ -45,7 +45,7 @@ namespace UptimeKuma { trayIcon.MouseDoubleClick += new MouseEventHandler(Open); trayIcon.Visible = true; - if (File.Exists("core") && File.Exists("node")) { + if (Directory.Exists("core") && Directory.Exists("node") && Directory.Exists("core/node_modules")) { // Go go go StartProcess(); } else { diff --git a/extra/exe-builder/UptimeKuma.csproj b/extra/exe-builder/UptimeKuma.csproj index aa0a8bf8..2ad857b2 100644 --- a/extra/exe-builder/UptimeKuma.csproj +++ b/extra/exe-builder/UptimeKuma.csproj @@ -35,11 +35,12 @@ 4 - COPY "$(SolutionDir)bin\Debug\uptime-kuma.exe" "C:\Users\LouisLam\Desktop\uptime-kuma-win64\" + COPY "$(SolutionDir)bin\Debug\uptime-kuma.exe" "%UserProfile%\Desktop\uptime-kuma-win64\" + From ad1a7c255f214d2ce0be9d71d0d232d246fb64f9 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sat, 8 Oct 2022 23:56:58 +0800 Subject: [PATCH 226/803] Drop exports.entryPage fully --- server/server.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/server/server.js b/server/server.js index 620e5bb4..2efad753 100644 --- a/server/server.js +++ b/server/server.js @@ -127,6 +127,7 @@ 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"); +const { Settings } = require("./settings"); app.use(express.json()); @@ -155,9 +156,7 @@ let needSetup = false; Database.init(args); await initDatabase(testMode); - const entryPage = (await getSettings("general"))["entryPage"]; - exports.entryPage = entryPage; - UptimeKumaServer.getInstance().entryPage = entryPage; + server.entryPage = await Settings.get("entryPage"); await StatusPage.loadDomainMappingList(); log.info("server", "Adding route"); @@ -178,7 +177,7 @@ let needSetup = false; log.debug("entry", `Request Domain: ${hostname}`); - const uptimeKumaEntryPage = UptimeKumaServer.getInstance().entryPage; + const uptimeKumaEntryPage = server.entryPage; if (hostname in StatusPage.domainMappingList) { log.debug("entry", "This is a status page domain"); @@ -1087,8 +1086,7 @@ let needSetup = false; } await setSettings("general", data); - exports.entryPage = data.entryPage; - UptimeKumaServer.getInstance().entryPage = data.entryPage; + server.entryPage = data.entryPage; callback({ ok: true, From a487347b3316cc3192b312a195ac44a46160d310 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sun, 9 Oct 2022 03:47:06 +0800 Subject: [PATCH 227/803] [exe] install dependencies and download dist --- extra/exe-builder/DownloadForm.cs | 21 +++++++++++---------- extra/exe-builder/Program.cs | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/extra/exe-builder/DownloadForm.cs b/extra/exe-builder/DownloadForm.cs index 5bb88b6d..f16af422 100644 --- a/extra/exe-builder/DownloadForm.cs +++ b/extra/exe-builder/DownloadForm.cs @@ -63,12 +63,6 @@ namespace UptimeKuma { } void npmSetup() { - if (Directory.Exists("core/node_modules")) { - // Application.Restart(); - } - - label.Text = "npm run setup"; - progressBar.Value = 50; labelData.Text = ""; var startInfo = new ProcessStartInfo { @@ -86,10 +80,12 @@ namespace UptimeKuma { process.StartInfo = startInfo; process.EnableRaisingEvents = true; process.Exited += (object _, EventArgs e) => { - // Application.Restart(); - progressBar.Value = 100; + progressBar.Value = 100; if (process.ExitCode == 0) { + Task.Delay(2000).ContinueWith((task) => { + Application.Restart(); + }); label.Text = "Done"; } else { label.Text = "Failed, exit code: " + process.ExitCode; @@ -97,7 +93,12 @@ namespace UptimeKuma { }; process.Start(); - process.StandardInput.WriteLine("\"../node/npm\" run setup"); + label.Text = "Installing dependencies and download dist files"; + progressBar.Value = 50; + + process.StandardInput.WriteLine("\"../node/npm\" ci --production"); + process.StandardInput.WriteLine("\"../node/npm\" run download-dist"); + process.StandardInput.WriteLine("exit"); } void DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) { @@ -110,7 +111,7 @@ namespace UptimeKuma { } } - async void DownloadFileCompleted(object sender, AsyncCompletedEventArgs e) { + void DownloadFileCompleted(object sender, AsyncCompletedEventArgs e) { Extract(currentDownloadItem); DownloadNextFile(); } diff --git a/extra/exe-builder/Program.cs b/extra/exe-builder/Program.cs index 1b78f038..82c76b05 100644 --- a/extra/exe-builder/Program.cs +++ b/extra/exe-builder/Program.cs @@ -45,7 +45,7 @@ namespace UptimeKuma { trayIcon.MouseDoubleClick += new MouseEventHandler(Open); trayIcon.Visible = true; - if (Directory.Exists("core") && Directory.Exists("node") && Directory.Exists("core/node_modules")) { + if (Directory.Exists("core") && Directory.Exists("node") && Directory.Exists("core/node_modules") && Directory.Exists("core/dist")) { // Go go go StartProcess(); } else { From 1c8631af8d5b3547f1fff5bea1ee3da75b7ee45f Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sun, 9 Oct 2022 16:02:47 +0800 Subject: [PATCH 228/803] Pin dependencies (#2205) --- package-lock.json | 48 +++++++++++++++++++++++------------------------ package.json | 48 +++++++++++++++++++++++------------------------ 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4f222171..6053fa5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,47 +12,47 @@ "@louislam/sqlite3": "~15.0.6", "args-parser": "~1.3.0", "axios": "~0.27.0", - "axios-ntlm": "^1.3.0", - "badge-maker": "^3.3.1", + "axios-ntlm": "~1.3.0", + "badge-maker": "~3.3.1", "bcryptjs": "~2.4.3", "bree": "~7.1.5", "cacheable-lookup": "~6.0.4", - "chardet": "^1.3.0", + "chardet": "~1.4.0", "check-password-strength": "^2.0.5", - "cheerio": "^1.0.0-rc.10", - "chroma-js": "^2.1.2", + "cheerio": "~1.0.0-rc.12", + "chroma-js": "~2.4.2", "command-exists": "~1.2.9", "compare-versions": "~3.6.0", - "compression": "^1.7.4", - "dayjs": "^1.11.0", + "compression": "~1.7.4", + "dayjs": "~1.11.5", "express": "~4.17.3", "express-basic-auth": "~1.2.1", - "express-static-gzip": "^2.1.7", + "express-static-gzip": "~2.1.7", "form-data": "~4.0.0", "http-graceful-shutdown": "~3.1.7", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "iconv-lite": "^0.6.3", - "jsesc": "^3.0.2", + "http-proxy-agent": "~5.0.0", + "https-proxy-agent": "~5.0.1", + "iconv-lite": "~0.6.3", + "jsesc": "~3.0.2", "jsonwebtoken": "~8.5.1", - "jwt-decode": "^3.1.2", - "limiter": "^2.1.0", - "mqtt": "^4.2.8", - "mssql": "^8.1.0", + "jwt-decode": "~3.1.2", + "limiter": "~2.1.0", + "mqtt": "~4.3.7", + "mssql": "~8.1.4", "node-cloudflared-tunnel": "~1.0.9", - "node-radius-client": "^1.0.0", + "node-radius-client": "~1.0.0", "nodemailer": "~6.6.5", "notp": "~2.0.3", "password-hash": "~1.2.2", - "pg": "^8.7.3", - "pg-connection-string": "^2.5.0", + "pg": "~8.8.0", + "pg-connection-string": "~2.5.0", "prom-client": "~13.2.0", "prometheus-api-metrics": "~3.2.1", "redbean-node": "0.1.4", "socket.io": "~4.4.1", "socket.io-client": "~4.4.1", "socks-proxy-agent": "6.1.1", - "tar": "^6.1.11", + "tar": "~6.1.11", "tcp-ping": "~0.1.1", "thirty-two": "~1.0.2" }, @@ -82,18 +82,18 @@ "dns2": "~2.0.1", "eslint": "~8.14.0", "eslint-plugin-vue": "~8.7.1", - "favico.js": "^0.3.10", + "favico.js": "~0.3.10", "jest": "~27.2.5", "postcss-html": "~1.5.0", "postcss-rtlcss": "~3.7.2", "postcss-scss": "~4.0.4", - "prismjs": "^1.27.0", + "prismjs": "~1.29.0", "qrcode": "~1.5.0", "rollup-plugin-visualizer": "^5.6.0", "sass": "~1.42.1", "stylelint": "~14.7.1", "stylelint-config-standard": "~25.0.0", - "terser": "^5.15.0", + "terser": "~5.15.0", "timezones-list": "~3.0.1", "typescript": "~4.4.4", "v-pagination-3": "~0.1.7", @@ -106,7 +106,7 @@ "vue-i18n": "~9.1.9", "vue-image-crop-upload": "~3.0.3", "vue-multiselect": "~3.0.0-alpha.2", - "vue-prism-editor": "^2.0.0-alpha.2", + "vue-prism-editor": "~2.0.0-alpha.2", "vue-qrcode": "~1.0.0", "vue-router": "~4.0.14", "vue-toastification": "~2.0.0-rc.5", diff --git a/package.json b/package.json index f5f78f3a..0c72722d 100644 --- a/package.json +++ b/package.json @@ -66,47 +66,47 @@ "@louislam/sqlite3": "~15.0.6", "args-parser": "~1.3.0", "axios": "~0.27.0", - "axios-ntlm": "^1.3.0", - "badge-maker": "^3.3.1", + "axios-ntlm": "~1.3.0", + "badge-maker": "~3.3.1", "bcryptjs": "~2.4.3", "bree": "~7.1.5", "cacheable-lookup": "~6.0.4", - "chardet": "^1.3.0", + "chardet": "~1.4.0", "check-password-strength": "^2.0.5", - "cheerio": "^1.0.0-rc.10", - "chroma-js": "^2.1.2", + "cheerio": "~1.0.0-rc.12", + "chroma-js": "~2.4.2", "command-exists": "~1.2.9", "compare-versions": "~3.6.0", - "compression": "^1.7.4", - "dayjs": "^1.11.0", + "compression": "~1.7.4", + "dayjs": "~1.11.5", "express": "~4.17.3", "express-basic-auth": "~1.2.1", - "express-static-gzip": "^2.1.7", + "express-static-gzip": "~2.1.7", "form-data": "~4.0.0", "http-graceful-shutdown": "~3.1.7", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "iconv-lite": "^0.6.3", - "jsesc": "^3.0.2", + "http-proxy-agent": "~5.0.0", + "https-proxy-agent": "~5.0.1", + "iconv-lite": "~0.6.3", + "jsesc": "~3.0.2", "jsonwebtoken": "~8.5.1", - "jwt-decode": "^3.1.2", - "limiter": "^2.1.0", - "mqtt": "^4.2.8", - "mssql": "^8.1.0", + "jwt-decode": "~3.1.2", + "limiter": "~2.1.0", + "mqtt": "~4.3.7", + "mssql": "~8.1.4", "node-cloudflared-tunnel": "~1.0.9", - "node-radius-client": "^1.0.0", + "node-radius-client": "~1.0.0", "nodemailer": "~6.6.5", "notp": "~2.0.3", "password-hash": "~1.2.2", - "pg": "^8.7.3", - "pg-connection-string": "^2.5.0", + "pg": "~8.8.0", + "pg-connection-string": "~2.5.0", "prom-client": "~13.2.0", "prometheus-api-metrics": "~3.2.1", "redbean-node": "0.1.4", "socket.io": "~4.4.1", "socket.io-client": "~4.4.1", "socks-proxy-agent": "6.1.1", - "tar": "^6.1.11", + "tar": "~6.1.11", "tcp-ping": "~0.1.1", "thirty-two": "~1.0.2" }, @@ -136,18 +136,18 @@ "dns2": "~2.0.1", "eslint": "~8.14.0", "eslint-plugin-vue": "~8.7.1", - "favico.js": "^0.3.10", + "favico.js": "~0.3.10", "jest": "~27.2.5", "postcss-html": "~1.5.0", "postcss-rtlcss": "~3.7.2", "postcss-scss": "~4.0.4", - "prismjs": "^1.27.0", + "prismjs": "~1.29.0", "qrcode": "~1.5.0", "rollup-plugin-visualizer": "^5.6.0", "sass": "~1.42.1", "stylelint": "~14.7.1", "stylelint-config-standard": "~25.0.0", - "terser": "^5.15.0", + "terser": "~5.15.0", "timezones-list": "~3.0.1", "typescript": "~4.4.4", "v-pagination-3": "~0.1.7", @@ -160,7 +160,7 @@ "vue-i18n": "~9.1.9", "vue-image-crop-upload": "~3.0.3", "vue-multiselect": "~3.0.0-alpha.2", - "vue-prism-editor": "^2.0.0-alpha.2", + "vue-prism-editor": "~2.0.0-alpha.2", "vue-qrcode": "~1.0.0", "vue-router": "~4.0.14", "vue-toastification": "~2.0.0-rc.5", From 07f9aafd7bce1b6d5226744654070a2fc7d54745 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sun, 9 Oct 2022 16:50:47 +0800 Subject: [PATCH 229/803] Update to 1.18.4 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 0c72722d..180bea28 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uptime-kuma", - "version": "1.18.3", + "version": "1.18.4", "license": "MIT", "repository": { "type": "git", @@ -38,7 +38,7 @@ "build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain", "build-docker-pr-test": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:pr-test --target pr-test . --push", "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.18.3 && npm ci --production && npm run download-dist", + "setup": "git checkout 1.18.4 && npm ci --production && npm run download-dist", "download-dist": "node extra/download-dist.js", "mark-as-nightly": "node extra/mark-as-nightly.js", "reset-password": "node extra/reset-password.js", From a36f24d827f2dd37c247cee6a124495bbd0e3d19 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sun, 9 Oct 2022 20:59:58 +0800 Subject: [PATCH 230/803] Add configurable server timezone --- package-lock.json | 4 ++-- package.json | 2 +- server/server.js | 24 ++++++++++++++++++------ server/uptime-kuma-server.js | 25 +++++++++++++++++++++++++ src/components/settings/General.vue | 20 ++++++++++++++++++-- src/util.js | 2 +- src/util.ts | 2 +- 7 files changed, 66 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2fdcb272..fb50f275 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "uptime-kuma", - "version": "1.18.3", + "version": "1.18.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "uptime-kuma", - "version": "1.18.3", + "version": "1.18.4", "license": "MIT", "dependencies": { "@louislam/sqlite3": "~15.0.6", diff --git a/package.json b/package.json index a77ea3d7..c3f7a886 100644 --- a/package.json +++ b/package.json @@ -123,7 +123,7 @@ "@vitejs/plugin-legacy": "~2.1.0", "@vitejs/plugin-vue": "~3.1.0", "@vue/compiler-sfc": "~3.2.36", - "@vuepic/vue-datepicker": "^3.4.8", + "@vuepic/vue-datepicker": "~3.4.8", "aedes": "^0.46.3", "babel-plugin-rewire": "~1.2.0", "bootstrap": "5.1.3", diff --git a/server/server.js b/server/server.js index 1ad99899..f80d5d57 100644 --- a/server/server.js +++ b/server/server.js @@ -5,6 +5,11 @@ */ console.log("Welcome to Uptime Kuma"); +// As the log function need to use dayjs, it should be very top +const dayjs = require("dayjs"); +dayjs.extend(require("dayjs/plugin/utc")); +dayjs.extend(require("dayjs/plugin/timezone")); + // Check Node.js Version const nodeVersion = parseInt(process.versions.node.split(".")[0]); const requiredVersion = 14; @@ -34,10 +39,6 @@ const fs = require("fs"); log.info("server", "Importing 3rd-party libraries"); -const dayjs = require("dayjs"); -dayjs.extend(require("dayjs/plugin/utc")); -dayjs.extend(require("dayjs/plugin/timezone")); - log.debug("server", "Importing express"); const express = require("express"); const expressStaticGzip = require("express-static-gzip"); @@ -160,6 +161,7 @@ let needSetup = false; (async () => { Database.init(args); await initDatabase(testMode); + await server.initAfterDatabaseReady(); exports.entryPage = await setting("entryPage"); await StatusPage.loadDomainMappingList(); @@ -1061,10 +1063,15 @@ let needSetup = false; socket.on("getSettings", async (callback) => { try { checkLogin(socket); + const data = await getSettings("general"); + + if (!data.serverTimezone) { + data.serverTimezone = await server.getTimezone(); + } callback({ ok: true, - data: await getSettings("general"), + data: data, }); } catch (e) { @@ -1092,9 +1099,14 @@ let needSetup = false; await setSettings("general", data); exports.entryPage = data.entryPage; + // Also need to apply timezone globally + if (data.serverTimezone) { + await server.setTimezone(data.serverTimezone); + } + callback({ ok: true, - msg: "Saved" + msg: "Saved " + dayjs() }); sendInfo(socket); diff --git a/server/uptime-kuma-server.js b/server/uptime-kuma-server.js index 7de53fe6..15583159 100644 --- a/server/uptime-kuma-server.js +++ b/server/uptime-kuma-server.js @@ -9,6 +9,7 @@ const Database = require("./database"); const util = require("util"); const { CacheableDnsHttpAgent } = require("./cacheable-dns-http-agent"); const { Settings } = require("./settings"); +const dayjs = require("dayjs"); /** * `module.exports` (alias: `server`) should be inside this class, in order to avoid circular dependency issue. @@ -84,6 +85,13 @@ class UptimeKumaServer { this.io = new Server(this.httpServer); } + async initAfterDatabaseReady() { + process.env.TZ = await this.getTimezone(); + dayjs.tz.setDefault(process.env.TZ); + log.debug("DEBUG", "Timezone: " + process.env.TZ); + log.debug("DEBUG", "Current Time: " + dayjs.tz().format()); + } + async sendMonitorList(socket) { let list = await this.getMonitorJSONList(socket.userID); this.io.to(socket.userID).emit("monitorList", list); @@ -184,6 +192,23 @@ class UptimeKumaServer { return clientIP.replace(/^.*:/, ""); } } + + async getTimezone() { + let timezone = await Settings.get("serverTimezone"); + if (timezone) { + return timezone; + } else if (process.env.TZ) { + return process.env.TZ; + } else { + return dayjs.tz.guess(); + } + } + + async setTimezone(timezone) { + await Settings.set("serverTimezone", timezone, "general"); + process.env.TZ = timezone; + dayjs.tz.setDefault(timezone); + } } module.exports = { diff --git a/src/components/settings/General.vue b/src/components/settings/General.vue index 242ad853..57c8e0ca 100644 --- a/src/components/settings/General.vue +++ b/src/components/settings/General.vue @@ -1,10 +1,10 @@ @@ -186,7 +187,6 @@ :dark="$root.isDark" timePicker disableTimeRangeValidation range - textInput />
@@ -196,7 +196,7 @@ Date: Mon, 10 Oct 2022 02:47:24 +0800 Subject: [PATCH 232/803] Fix #2207 --- server/model/status_page.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/model/status_page.js b/server/model/status_page.js index 7682272c..68c7f8b0 100644 --- a/server/model/status_page.js +++ b/server/model/status_page.js @@ -58,7 +58,7 @@ class StatusPage extends BeanModel { // Preload data // Add jsesc, fix https://github.com/louislam/uptime-kuma/issues/2186 - const escapedJSONObject = jsesc(JSON.stringify(await StatusPage.getStatusPageData(statusPage)), { + const escapedJSONObject = jsesc(await StatusPage.getStatusPageData(statusPage), { "isScriptContext": true }); From bd42450e55dc29bdd1daddb1080aaa6d81b32912 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Mon, 10 Oct 2022 16:23:32 +0800 Subject: [PATCH 233/803] Update vue-i18n from 9.1.9 to 9.2.2, force to use production version of vue-i18n in order to improve the performance --- package-lock.json | 186 +++++++++++++++++----------------------------- package.json | 2 +- src/i18n.js | 2 +- 3 files changed, 71 insertions(+), 119 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6053fa5f..44af1469 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "uptime-kuma", - "version": "1.18.3", + "version": "1.18.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "uptime-kuma", - "version": "1.18.3", + "version": "1.18.4", "license": "MIT", "dependencies": { "@louislam/sqlite3": "~15.0.6", @@ -103,7 +103,7 @@ "vue-chart-3": "3.0.9", "vue-confirm-dialog": "~1.0.2", "vue-contenteditable": "~3.0.4", - "vue-i18n": "~9.1.9", + "vue-i18n": "~9.2.2", "vue-image-crop-upload": "~3.0.3", "vue-multiselect": "~3.0.0-alpha.2", "vue-prism-editor": "~2.0.0-alpha.2", @@ -2338,92 +2338,65 @@ "dev": true }, "node_modules/@intlify/core-base": { - "version": "9.1.10", - "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.1.10.tgz", - "integrity": "sha512-So9CNUavB/IsZ+zBmk2Cv6McQp6vc2wbGi1S0XQmJ8Vz+UFcNn9MFXAe9gY67PreIHrbLsLxDD0cwo1qsxM1Nw==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.2.2.tgz", + "integrity": "sha512-JjUpQtNfn+joMbrXvpR4hTF8iJQ2sEFzzK3KIESOx+f+uwIjgw20igOyaIdhfsVVBCds8ZM64MoeNSx+PHQMkA==", "dev": true, "dependencies": { - "@intlify/devtools-if": "9.1.10", - "@intlify/message-compiler": "9.1.10", - "@intlify/message-resolver": "9.1.10", - "@intlify/runtime": "9.1.10", - "@intlify/shared": "9.1.10", - "@intlify/vue-devtools": "9.1.10" + "@intlify/devtools-if": "9.2.2", + "@intlify/message-compiler": "9.2.2", + "@intlify/shared": "9.2.2", + "@intlify/vue-devtools": "9.2.2" }, "engines": { - "node": ">= 10" + "node": ">= 14" } }, "node_modules/@intlify/devtools-if": { - "version": "9.1.10", - "resolved": "https://registry.npmjs.org/@intlify/devtools-if/-/devtools-if-9.1.10.tgz", - "integrity": "sha512-SHaKoYu6sog3+Q8js1y3oXLywuogbH1sKuc7NSYkN3GElvXSBaMoCzW+we0ZSFqj/6c7vTNLg9nQ6rxhKqYwnQ==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@intlify/devtools-if/-/devtools-if-9.2.2.tgz", + "integrity": "sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg==", "dev": true, "dependencies": { - "@intlify/shared": "9.1.10" + "@intlify/shared": "9.2.2" }, "engines": { - "node": ">= 10" + "node": ">= 14" } }, "node_modules/@intlify/message-compiler": { - "version": "9.1.10", - "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.1.10.tgz", - "integrity": "sha512-+JiJpXff/XTb0EadYwdxOyRTB0hXNd4n1HaJ/a4yuV960uRmPXaklJsedW0LNdcptd/hYUZtCkI7Lc9J5C1gxg==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.2.2.tgz", + "integrity": "sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA==", "dev": true, "dependencies": { - "@intlify/message-resolver": "9.1.10", - "@intlify/shared": "9.1.10", + "@intlify/shared": "9.2.2", "source-map": "0.6.1" }, "engines": { - "node": ">= 10" - } - }, - "node_modules/@intlify/message-resolver": { - "version": "9.1.10", - "resolved": "https://registry.npmjs.org/@intlify/message-resolver/-/message-resolver-9.1.10.tgz", - "integrity": "sha512-5YixMG/M05m0cn9+gOzd4EZQTFRUu8RGhzxJbR1DWN21x/Z3bJ8QpDYj6hC4FwBj5uKsRfKpJQ3Xqg98KWoA+w==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@intlify/runtime": { - "version": "9.1.10", - "resolved": "https://registry.npmjs.org/@intlify/runtime/-/runtime-9.1.10.tgz", - "integrity": "sha512-7QsuByNzpe3Gfmhwq6hzgXcMPpxz8Zxb/XFI6s9lQdPLPe5Lgw4U1ovRPZTOs6Y2hwitR3j/HD8BJNGWpJnOFA==", - "dev": true, - "dependencies": { - "@intlify/message-compiler": "9.1.10", - "@intlify/message-resolver": "9.1.10", - "@intlify/shared": "9.1.10" - }, - "engines": { - "node": ">= 10" + "node": ">= 14" } }, "node_modules/@intlify/shared": { - "version": "9.1.10", - "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.1.10.tgz", - "integrity": "sha512-Om54xJeo1Vw+K1+wHYyXngE8cAbrxZHpWjYzMR9wCkqbhGtRV5VLhVc214Ze2YatPrWlS2WSMOWXR8JktX/IgA==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.2.2.tgz", + "integrity": "sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==", "dev": true, "engines": { - "node": ">= 10" + "node": ">= 14" } }, "node_modules/@intlify/vue-devtools": { - "version": "9.1.10", - "resolved": "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.1.10.tgz", - "integrity": "sha512-5l3qYARVbkWAkagLu1XbDUWRJSL8br1Dj60wgMaKB0+HswVsrR6LloYZTg7ozyvM621V6+zsmwzbQxbVQyrytQ==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.2.2.tgz", + "integrity": "sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg==", "dev": true, "dependencies": { - "@intlify/message-resolver": "9.1.10", - "@intlify/runtime": "9.1.10", - "@intlify/shared": "9.1.10" + "@intlify/core-base": "9.2.2", + "@intlify/shared": "9.2.2" }, "engines": { - "node": ">= 10" + "node": ">= 14" } }, "node_modules/@istanbuljs/load-nyc-config": { @@ -16107,18 +16080,18 @@ } }, "node_modules/vue-i18n": { - "version": "9.1.10", - "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.1.10.tgz", - "integrity": "sha512-jpr7gV5KPk4n+sSPdpZT8Qx3XzTcNDWffRlHV/cT2NUyEf+sEgTTmLvnBAibjOFJ0zsUyZlVTAWH5DDnYep+1g==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.2.2.tgz", + "integrity": "sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ==", "dev": true, "dependencies": { - "@intlify/core-base": "9.1.10", - "@intlify/shared": "9.1.10", - "@intlify/vue-devtools": "9.1.10", - "@vue/devtools-api": "^6.0.0-beta.7" + "@intlify/core-base": "9.2.2", + "@intlify/shared": "9.2.2", + "@intlify/vue-devtools": "9.2.2", + "@vue/devtools-api": "^6.2.1" }, "engines": { - "node": ">= 10" + "node": ">= 14" }, "peerDependencies": { "vue": "^3.0.0" @@ -18279,71 +18252,50 @@ "dev": true }, "@intlify/core-base": { - "version": "9.1.10", - "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.1.10.tgz", - "integrity": "sha512-So9CNUavB/IsZ+zBmk2Cv6McQp6vc2wbGi1S0XQmJ8Vz+UFcNn9MFXAe9gY67PreIHrbLsLxDD0cwo1qsxM1Nw==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.2.2.tgz", + "integrity": "sha512-JjUpQtNfn+joMbrXvpR4hTF8iJQ2sEFzzK3KIESOx+f+uwIjgw20igOyaIdhfsVVBCds8ZM64MoeNSx+PHQMkA==", "dev": true, "requires": { - "@intlify/devtools-if": "9.1.10", - "@intlify/message-compiler": "9.1.10", - "@intlify/message-resolver": "9.1.10", - "@intlify/runtime": "9.1.10", - "@intlify/shared": "9.1.10", - "@intlify/vue-devtools": "9.1.10" + "@intlify/devtools-if": "9.2.2", + "@intlify/message-compiler": "9.2.2", + "@intlify/shared": "9.2.2", + "@intlify/vue-devtools": "9.2.2" } }, "@intlify/devtools-if": { - "version": "9.1.10", - "resolved": "https://registry.npmjs.org/@intlify/devtools-if/-/devtools-if-9.1.10.tgz", - "integrity": "sha512-SHaKoYu6sog3+Q8js1y3oXLywuogbH1sKuc7NSYkN3GElvXSBaMoCzW+we0ZSFqj/6c7vTNLg9nQ6rxhKqYwnQ==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@intlify/devtools-if/-/devtools-if-9.2.2.tgz", + "integrity": "sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg==", "dev": true, "requires": { - "@intlify/shared": "9.1.10" + "@intlify/shared": "9.2.2" } }, "@intlify/message-compiler": { - "version": "9.1.10", - "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.1.10.tgz", - "integrity": "sha512-+JiJpXff/XTb0EadYwdxOyRTB0hXNd4n1HaJ/a4yuV960uRmPXaklJsedW0LNdcptd/hYUZtCkI7Lc9J5C1gxg==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.2.2.tgz", + "integrity": "sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA==", "dev": true, "requires": { - "@intlify/message-resolver": "9.1.10", - "@intlify/shared": "9.1.10", + "@intlify/shared": "9.2.2", "source-map": "0.6.1" } }, - "@intlify/message-resolver": { - "version": "9.1.10", - "resolved": "https://registry.npmjs.org/@intlify/message-resolver/-/message-resolver-9.1.10.tgz", - "integrity": "sha512-5YixMG/M05m0cn9+gOzd4EZQTFRUu8RGhzxJbR1DWN21x/Z3bJ8QpDYj6hC4FwBj5uKsRfKpJQ3Xqg98KWoA+w==", - "dev": true - }, - "@intlify/runtime": { - "version": "9.1.10", - "resolved": "https://registry.npmjs.org/@intlify/runtime/-/runtime-9.1.10.tgz", - "integrity": "sha512-7QsuByNzpe3Gfmhwq6hzgXcMPpxz8Zxb/XFI6s9lQdPLPe5Lgw4U1ovRPZTOs6Y2hwitR3j/HD8BJNGWpJnOFA==", - "dev": true, - "requires": { - "@intlify/message-compiler": "9.1.10", - "@intlify/message-resolver": "9.1.10", - "@intlify/shared": "9.1.10" - } - }, "@intlify/shared": { - "version": "9.1.10", - "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.1.10.tgz", - "integrity": "sha512-Om54xJeo1Vw+K1+wHYyXngE8cAbrxZHpWjYzMR9wCkqbhGtRV5VLhVc214Ze2YatPrWlS2WSMOWXR8JktX/IgA==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.2.2.tgz", + "integrity": "sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==", "dev": true }, "@intlify/vue-devtools": { - "version": "9.1.10", - "resolved": "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.1.10.tgz", - "integrity": "sha512-5l3qYARVbkWAkagLu1XbDUWRJSL8br1Dj60wgMaKB0+HswVsrR6LloYZTg7ozyvM621V6+zsmwzbQxbVQyrytQ==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.2.2.tgz", + "integrity": "sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg==", "dev": true, "requires": { - "@intlify/message-resolver": "9.1.10", - "@intlify/runtime": "9.1.10", - "@intlify/shared": "9.1.10" + "@intlify/core-base": "9.2.2", + "@intlify/shared": "9.2.2" } }, "@istanbuljs/load-nyc-config": { @@ -28795,15 +28747,15 @@ } }, "vue-i18n": { - "version": "9.1.10", - "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.1.10.tgz", - "integrity": "sha512-jpr7gV5KPk4n+sSPdpZT8Qx3XzTcNDWffRlHV/cT2NUyEf+sEgTTmLvnBAibjOFJ0zsUyZlVTAWH5DDnYep+1g==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.2.2.tgz", + "integrity": "sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ==", "dev": true, "requires": { - "@intlify/core-base": "9.1.10", - "@intlify/shared": "9.1.10", - "@intlify/vue-devtools": "9.1.10", - "@vue/devtools-api": "^6.0.0-beta.7" + "@intlify/core-base": "9.2.2", + "@intlify/shared": "9.2.2", + "@intlify/vue-devtools": "9.2.2", + "@vue/devtools-api": "^6.2.1" } }, "vue-image-crop-upload": { diff --git a/package.json b/package.json index 180bea28..6f48fedd 100644 --- a/package.json +++ b/package.json @@ -157,7 +157,7 @@ "vue-chart-3": "3.0.9", "vue-confirm-dialog": "~1.0.2", "vue-contenteditable": "~3.0.4", - "vue-i18n": "~9.1.9", + "vue-i18n": "~9.2.2", "vue-image-crop-upload": "~3.0.3", "vue-multiselect": "~3.0.0-alpha.2", "vue-prism-editor": "~2.0.0-alpha.2", diff --git a/src/i18n.js b/src/i18n.js index 4c19eb00..902177cf 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -1,4 +1,4 @@ -import { createI18n } from "vue-i18n/index"; +import { createI18n } from "vue-i18n/dist/vue-i18n.esm-browser.prod.js"; import en from "./languages/en"; const languageList = { From c1ccaa7a9feb3dfc9390039f86f183f077c78283 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Mon, 10 Oct 2022 20:48:11 +0800 Subject: [PATCH 234/803] WIP --- package-lock.json | 2 +- server/model/maintenance.js | 32 +++++--------- server/model/maintenance_timeslot.js | 6 +++ .../maintenance-socket-handler.js | 13 +++--- server/util-server.js | 26 +++++++++-- src/languages/en.js | 1 + src/pages/EditMaintenance.vue | 15 +++---- src/pages/ManageMaintenance.vue | 43 ++++++++++++++++++- 8 files changed, 93 insertions(+), 45 deletions(-) diff --git a/package-lock.json b/package-lock.json index f422d6a8..161208af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -69,7 +69,7 @@ "@vitejs/plugin-legacy": "~2.1.0", "@vitejs/plugin-vue": "~3.1.0", "@vue/compiler-sfc": "~3.2.36", - "@vuepic/vue-datepicker": "^3.4.8", + "@vuepic/vue-datepicker": "~3.4.8", "aedes": "^0.46.3", "babel-plugin-rewire": "~1.2.0", "bootstrap": "5.1.3", diff --git a/server/model/maintenance.js b/server/model/maintenance.js index 840267ef..d46b9d4b 100644 --- a/server/model/maintenance.js +++ b/server/model/maintenance.js @@ -1,5 +1,5 @@ const { BeanModel } = require("redbean-node/dist/bean-model"); -const { parseTimeObject, parseTimeFromTimeObject, isoToUTCDateTime, utcToISODateTime, SQL_DATETIME_FORMAT, utcToLocal, localToUTC } = require("../../src/util"); +const { parseTimeObject, parseTimeFromTimeObject, utcToLocal, localToUTC } = require("../../src/util"); const { isArray } = require("chart.js/helpers"); const { timeObjectToUTC, timeObjectToLocal } = require("../util-server"); @@ -11,7 +11,7 @@ class Maintenance extends BeanModel { * @param {string} timezone If not specified, the timeRange will be in UTC * @returns {Object} */ - async toPublicJSON(timezone = null) { + async toPublicJSON() { let dateRange = []; if (this.start_date) { @@ -22,21 +22,11 @@ class Maintenance extends BeanModel { } let timeRange = []; - let startTime = parseTimeObject(this.start_time); + let startTime = timeObjectToLocal(parseTimeObject(this.start_time)); timeRange.push(startTime); - let endTime = parseTimeObject(this.end_time); + let endTime = timeObjectToLocal(parseTimeObject(this.end_time)); timeRange.push(endTime); - // Apply timezone offset - if (timezone) { - if (this.start_time) { - timeObjectToLocal(startTime, timezone); - } - if (this.end_time) { - timeObjectToLocal(endTime, timezone); - } - } - let obj = { id: this.id, title: this.title, @@ -70,18 +60,16 @@ class Maintenance extends BeanModel { return this.toPublicJSON(timezone); } - static jsonToBean(bean, obj, timezone) { + static jsonToBean(bean, obj) { if (obj.id) { bean.id = obj.id; } // Apply timezone offset to timeRange, as it cannot apply automatically. - if (timezone) { - if (obj.timeRange[0]) { - timeObjectToUTC(obj.timeRange[0], timezone); - if (obj.timeRange[1]) { - timeObjectToUTC(obj.timeRange[1], timezone); - } + if (obj.timeRange[0]) { + timeObjectToUTC(obj.timeRange[0]); + if (obj.timeRange[1]) { + timeObjectToUTC(obj.timeRange[1]); } } @@ -118,7 +106,7 @@ class Maintenance extends BeanModel { (maintenance_timeslot.start_date <= DATETIME('now') AND maintenance_timeslot.end_date >= DATETIME('now') AND maintenance.active = 1) - AND + OR (maintenance.strategy = 'manual' AND active = 1) `; diff --git a/server/model/maintenance_timeslot.js b/server/model/maintenance_timeslot.js index 0ac5158d..4db3a1db 100644 --- a/server/model/maintenance_timeslot.js +++ b/server/model/maintenance_timeslot.js @@ -40,6 +40,12 @@ class MaintenanceTimeslot extends BeanModel { bean.end_date = maintenance.end_date; bean.generated_next = true; await R.store(bean); + } else if (maintenance.strategy === "recurring-interval") { + // TODO + } else if (maintenance.strategy === "recurring-weekday") { + // TODO + } else if (maintenance.strategy === "recurring-day-of-month") { + // TODO } else { throw new Error("Unknown maintenance strategy"); } diff --git a/server/socket-handlers/maintenance-socket-handler.js b/server/socket-handlers/maintenance-socket-handler.js index 9ae36b5c..49527f23 100644 --- a/server/socket-handlers/maintenance-socket-handler.js +++ b/server/socket-handlers/maintenance-socket-handler.js @@ -5,7 +5,6 @@ const apicache = require("../modules/apicache"); const { UptimeKumaServer } = require("../uptime-kuma-server"); const Maintenance = require("../model/maintenance"); const server = UptimeKumaServer.getInstance(); -const dayjs = require("dayjs"); const MaintenanceTimeslot = require("../model/maintenance_timeslot"); /** @@ -14,13 +13,13 @@ const MaintenanceTimeslot = require("../model/maintenance_timeslot"); */ module.exports.maintenanceSocketHandler = (socket) => { // Add a new maintenance - socket.on("addMaintenance", async (maintenance, timezone, callback) => { + socket.on("addMaintenance", async (maintenance, callback) => { try { checkLogin(socket); log.debug("maintenance", maintenance); - let bean = Maintenance.jsonToBean(R.dispense("maintenance"), maintenance, timezone); + let bean = Maintenance.jsonToBean(R.dispense("maintenance"), maintenance); bean.user_id = socket.userID; let maintenanceID = await R.store(bean); await MaintenanceTimeslot.generateTimeslot(bean); @@ -42,7 +41,7 @@ module.exports.maintenanceSocketHandler = (socket) => { }); // Edit a maintenance - socket.on("editMaintenance", async (maintenance, timezone, callback) => { + socket.on("editMaintenance", async (maintenance, callback) => { try { checkLogin(socket); @@ -52,7 +51,7 @@ module.exports.maintenanceSocketHandler = (socket) => { throw new Error("Permission denied."); } - Maintenance.jsonToBean(bean, maintenance, timezone); + Maintenance.jsonToBean(bean, maintenance); await R.store(bean); await MaintenanceTimeslot.generateTimeslot(bean, null, true); @@ -142,7 +141,7 @@ module.exports.maintenanceSocketHandler = (socket) => { } }); - socket.on("getMaintenance", async (maintenanceID, timezone, callback) => { + socket.on("getMaintenance", async (maintenanceID, callback) => { try { checkLogin(socket); @@ -155,7 +154,7 @@ module.exports.maintenanceSocketHandler = (socket) => { callback({ ok: true, - maintenance: await bean.toJSON(timezone), + maintenance: await bean.toJSON(), }); } catch (e) { diff --git a/server/util-server.js b/server/util-server.js index ddb9dab5..7c81cde7 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -648,8 +648,14 @@ module.exports.send403 = (res, msg = "") => { }; function timeObjectConvertTimezone(obj, timezone, timeObjectToUTC = true) { - // e.g. +08:00 - let offsetString = dayjs().tz(timezone).format("Z"); + let offsetString; + + if (timezone) { + offsetString = dayjs().tz(timezone).format("Z"); + } else { + offsetString = dayjs().format("Z"); + } + let hours = parseInt(offsetString.substring(1, 3)); let minutes = parseInt(offsetString.substring(4, 6)); @@ -680,10 +686,22 @@ function timeObjectConvertTimezone(obj, timezone, timeObjectToUTC = true) { return obj; } -module.exports.timeObjectToUTC = (obj, timezone) => { +/** + * + * @param {object} obj + * @param {string} timezone + * @returns {object} + */ +module.exports.timeObjectToUTC = (obj, timezone = undefined) => { return timeObjectConvertTimezone(obj, timezone, true); }; -module.exports.timeObjectToLocal = (obj, timezone) => { +/** + * + * @param {object} obj + * @param {string} timezone + * @returns {object} + */ +module.exports.timeObjectToLocal = (obj, timezone = undefined) => { return timeObjectConvertTimezone(obj, timezone, false); }; diff --git a/src/languages/en.js b/src/languages/en.js index d6a65b04..835fa248 100644 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -630,4 +630,5 @@ export default { lastDay2: "2nd Last Day of Month", lastDay3: "3rd Last Day of Month", lastDay4: "4th Last Day of Month", + "No Maintenance": "No Maintenance", }; diff --git a/src/pages/EditMaintenance.vue b/src/pages/EditMaintenance.vue index 946059cb..790588d8 100644 --- a/src/pages/EditMaintenance.vue +++ b/src/pages/EditMaintenance.vue @@ -200,7 +200,8 @@ :monthChangeOnScroll="false" :minDate="minDate" :enableTimePicker="false" - :utc="true" + format="yyyy-MM-dd" + modelType="yyyy-MM-dd HH:mm:ss" />
@@ -356,9 +357,6 @@ export default { }, methods: { init() { - // Use browser's timezone! - let timezone = dayjs.tz.guess(); - this.affectedMonitors = []; this.selectedStatusPages = []; @@ -381,7 +379,7 @@ export default { daysOfMonth: [], }; } else if (this.isEdit) { - this.$root.getSocket().emit("getMaintenance", this.$route.params.id, timezone, (res) => { + this.$root.getSocket().emit("getMaintenance", this.$route.params.id, (res) => { if (res.ok) { this.maintenance = res.maintenance; @@ -440,11 +438,8 @@ export default { this.maintenance.end_date = this.$root.toUTC(this.maintenance.end_date); */ - // Use browser's timezone! - let timezone = dayjs.tz.guess(); - if (this.isAdd) { - this.$root.addMaintenance(this.maintenance, timezone, async (res) => { + this.$root.addMaintenance(this.maintenance, async (res) => { if (res.ok) { await this.addMonitorMaintenance(res.maintenanceID, async () => { await this.addMaintenanceStatusPage(res.maintenanceID, () => { @@ -461,7 +456,7 @@ export default { }); } else { - this.$root.getSocket().emit("editMaintenance", this.maintenance, timezone, async (res) => { + this.$root.getSocket().emit("editMaintenance", this.maintenance, async (res) => { if (res.ok) { await this.addMonitorMaintenance(res.maintenanceID, async () => { await this.addMaintenanceStatusPage(res.maintenanceID, () => { diff --git a/src/pages/ManageMaintenance.vue b/src/pages/ManageMaintenance.vue index 4bfa9059..51e3ee28 100644 --- a/src/pages/ManageMaintenance.vue +++ b/src/pages/ManageMaintenance.vue @@ -13,7 +13,7 @@
- {{ $t("No maintenance") }} + {{ $t("No Maintenance") }}
{{ $t("Details") }} + + + + + {{ $t("Edit") }} + @@ -48,6 +58,10 @@ Learn More
+ + {{ $t("pauseMaintenanceMsg") }} + + {{ $t("deleteMaintenanceMsg") }} @@ -148,6 +162,33 @@ export default { } }); }, + + /** + * Show dialog to confirm pause + */ + pauseDialog() { + this.$refs.confirmPause.show(); + }, + + /** + * Pause maintenance + */ + pauseMonitor() { + return; + this.$root.getSocket().emit("pauseMaintenance", selectedMaintenanceID, (res) => { + this.$root.toastRes(res); + }); + }, + + /** + * Resume maintenance + */ + resumeMaintenance() { + return; + this.$root.getSocket().emit("resumeMaintenance", selectedMaintenanceID, (res) => { + this.$root.toastRes(res); + }); + }, }, }; From c84de4d2598c363b4a45ab0ddef09ca8c50b11c4 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Tue, 11 Oct 2022 01:45:30 +0800 Subject: [PATCH 235/803] WIP: Add maintenance status --- server/model/maintenance.js | 53 ++++++++++++++++++++++- server/model/monitor.js | 6 +-- src/assets/app.scss | 14 ++++++ src/layouts/Layout.vue | 2 +- src/mixins/datetime.js | 4 -- src/pages/ManageMaintenance.vue | 77 ++++++++++++++++----------------- 6 files changed, 108 insertions(+), 48 deletions(-) diff --git a/server/model/maintenance.js b/server/model/maintenance.js index d46b9d4b..2ab2a5bb 100644 --- a/server/model/maintenance.js +++ b/server/model/maintenance.js @@ -2,13 +2,14 @@ const { BeanModel } = require("redbean-node/dist/bean-model"); const { parseTimeObject, parseTimeFromTimeObject, utcToLocal, localToUTC } = require("../../src/util"); const { isArray } = require("chart.js/helpers"); const { timeObjectToUTC, timeObjectToLocal } = require("../util-server"); +const { R } = require("redbean-node"); +const dayjs = require("dayjs"); class Maintenance extends BeanModel { /** * Return an object that ready to parse to JSON for public * Only show necessary data to public - * @param {string} timezone If not specified, the timeRange will be in UTC * @returns {Object} */ async toPublicJSON() { @@ -38,6 +39,7 @@ class Maintenance extends BeanModel { timeRange: timeRange, weekdays: (this.weekdays) ? JSON.parse(this.weekdays) : [], daysOfMonth: (this.days_of_month) ? JSON.parse(this.days_of_month) : [], + timeslotList: await this.getTimeslotList(), }; if (!isArray(obj.weekdays)) { @@ -48,9 +50,45 @@ class Maintenance extends BeanModel { obj.daysOfMonth = []; } + // Maintenance Status + if (!obj.active) { + obj.status = "inactive"; + } else if (obj.strategy === "manual" || obj.timeslotList.length > 0) { + for (let timeslot of obj.timeslotList) { + if (dayjs.utc(timeslot.start_date) <= dayjs.utc() && dayjs.utc(timeslot.end_date) >= dayjs.utc()) { + obj.status = "under-maintenance"; + break; + } + } + + if (!obj.status) { + obj.status = "scheduled"; + } + } else if (obj.timeslotList.length === 0) { + obj.status = "ended"; + } else { + obj.status = "unknown"; + } + return obj; } + /** + * Only get future or current timeslots only + * @returns {Promise<[]>} + */ + async getTimeslotList() { + return await R.getAll(` + SELECT maintenance_timeslot.* + FROM maintenance_timeslot, maintenance + WHERE maintenance_timeslot.maintenance_id = maintenance.id + AND maintenance.id = ? + AND ${Maintenance.getActiveAndFutureMaintenanceSQLCondition()} + `, [ + this.id + ]); + } + /** * Return an object that ready to parse to JSON * @param {string} timezone If not specified, the timeRange will be in UTC @@ -111,6 +149,19 @@ class Maintenance extends BeanModel { `; } + + /** + * SQL conditions for active and future maintenance + * @returns {string} + */ + static getActiveAndFutureMaintenanceSQLCondition() { + return ` + (maintenance_timeslot.end_date >= DATETIME('now') + AND maintenance.active = 1) + OR + (maintenance.strategy = 'manual' AND active = 1) + `; + } } module.exports = Maintenance; diff --git a/server/model/monitor.js b/server/model/monitor.js index 4c51d220..d77c5529 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -1109,10 +1109,10 @@ class Monitor extends BeanModel { FROM monitor_maintenance mm JOIN maintenance ON mm.maintenance_id = maintenance.id - JOIN maintenance_timeslot + AND mm.monitor_id = ? + LEFT JOIN maintenance_timeslot ON maintenance_timeslot.maintenance_id = maintenance.id - WHERE mm.monitor_id = ? - AND ${activeCondition} + WHERE ${activeCondition} LIMIT 1`, [ monitorID ]); return maintenance.count !== 0; } diff --git a/src/assets/app.scss b/src/assets/app.scss index 81cf7724..be324afd 100644 --- a/src/assets/app.scss +++ b/src/assets/app.scss @@ -269,6 +269,20 @@ optgroup { color: white; } + .btn-normal { + $bg-color: $dark-header-bg; + + color: $dark-font-color; + background-color: $bg-color; + border-color: $bg-color; + + &:hover { + $hover-color: darken($bg-color, 3%); + background-color: $hover-color; + border-color: $hover-color; + } + } + .btn-warning { color: $dark-font-color2; diff --git a/src/layouts/Layout.vue b/src/layouts/Layout.vue index 7ece4982..acd9446c 100644 --- a/src/layouts/Layout.vue +++ b/src/layouts/Layout.vue @@ -58,7 +58,7 @@
  • - + {{ $t("Settings") }}
  • diff --git a/src/mixins/datetime.js b/src/mixins/datetime.js index 3bbe1130..4fa2fa83 100644 --- a/src/mixins/datetime.js +++ b/src/mixins/datetime.js @@ -12,10 +12,6 @@ export default { }, methods: { - isActiveMaintenance(endDate) { - return (dayjs.utc(endDate).unix() >= dayjs.utc().unix()); - }, - toUTC(value) { return dayjs.tz(value, this.timezone).utc().format(); }, diff --git a/src/pages/ManageMaintenance.vue b/src/pages/ManageMaintenance.vue index 51e3ee28..28bea9b8 100644 --- a/src/pages/ManageMaintenance.vue +++ b/src/pages/ManageMaintenance.vue @@ -20,7 +20,7 @@ v-for="(item, index) in sortedMaintenanceList" :key="index" class="item" - :class="{ 'ended': !$root.isActiveMaintenance(item.end_date) }" + :class="item.status" >
    {{ $t("Details") }} - @@ -43,7 +43,7 @@ {{ $t("Resume") }} - + {{ $t("Edit") }} @@ -90,36 +90,6 @@ export default { let result = Object.values(this.$root.maintenanceList); result.sort((m1, m2) => { - - if (this.$root.isActiveMaintenance(m1.end_date) !== this.$root.isActiveMaintenance(m2.end_date)) { - if (!this.$root.isActiveMaintenance(m2.end_date)) { - return -1; - } - if (!this.$root.isActiveMaintenance(m1.end_date)) { - return 1; - } - } - - if (this.$root.isActiveMaintenance(m1.end_date) && this.$root.isActiveMaintenance(m2.end_date)) { - if (Date.parse(m1.end_date) < Date.parse(m2.end_date)) { - return -1; - } - - if (Date.parse(m2.end_date) < Date.parse(m1.end_date)) { - return 1; - } - } - - if (!this.$root.isActiveMaintenance(m1.end_date) && !this.$root.isActiveMaintenance(m2.end_date)) { - if (Date.parse(m1.end_date) < Date.parse(m2.end_date)) { - return 1; - } - - if (Date.parse(m2.end_date) < Date.parse(m1.end_date)) { - return -1; - } - } - return m1.title.localeCompare(m2.title); }); @@ -173,7 +143,7 @@ export default { /** * Pause maintenance */ - pauseMonitor() { + pauseMaintenance() { return; this.$root.getSocket().emit("pauseMaintenance", selectedMaintenanceID, (res) => { this.$root.toastRes(res); @@ -211,13 +181,43 @@ export default { background-color: $highlight-white; } + &.under-maintenance { + background-color: rgba(23, 71, 245, 0.16); + + &:hover { + background-color: rgba(23, 71, 245, 0.3) !important; + } + + .circle { + background-color: $maintenance; + } + } + + &.scheduled { + .circle { + background-color: $primary; + } + } + + &.inactive { + .circle { + background-color: $danger; + } + } + &.ended { .left-part { - opacity: 0.5; + opacity: 0.3; + } - .circle { - background-color: $dark-font-color; - } + .circle { + background-color: $dark-font-color; + } + } + + &.unknown { + .circle { + background-color: $dark-font-color; } } @@ -230,7 +230,6 @@ export default { width: 25px; height: 25px; border-radius: 50rem; - background-color: $maintenance; } .info { From d5c02fc627e87a301eb4d7e9ff6466f05424773d Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Tue, 11 Oct 2022 01:59:47 +0800 Subject: [PATCH 236/803] Update Maintenance list order by status --- src/pages/ManageMaintenance.vue | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/pages/ManageMaintenance.vue b/src/pages/ManageMaintenance.vue index 28bea9b8..5820aada 100644 --- a/src/pages/ManageMaintenance.vue +++ b/src/pages/ManageMaintenance.vue @@ -83,6 +83,13 @@ export default { data() { return { selectedMaintenanceID: undefined, + statusOrderList: { + "under-maintenance": 1000, + "scheduled": 900, + "inactive": 800, + "ended": 700, + "unknown": 0, + } }; }, computed: { @@ -90,7 +97,11 @@ export default { let result = Object.values(this.$root.maintenanceList); result.sort((m1, m2) => { - return m1.title.localeCompare(m2.title); + if (this.statusOrderList[m1.status] === this.statusOrderList[m2.status]) { + return m1.title.localeCompare(m2.title); + } else { + return this.statusOrderList[m1.status] < this.statusOrderList[m2.status]; + } }); return result; From 2ee8378814d5cf92dceabcf2d9e4e79505001f16 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Tue, 11 Oct 2022 02:32:57 +0800 Subject: [PATCH 237/803] Update to 1.18.5 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 6f48fedd..b1a76d36 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uptime-kuma", - "version": "1.18.4", + "version": "1.18.5", "license": "MIT", "repository": { "type": "git", @@ -38,7 +38,7 @@ "build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain", "build-docker-pr-test": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64 -t louislam/uptime-kuma:pr-test --target pr-test . --push", "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.18.4 && npm ci --production && npm run download-dist", + "setup": "git checkout 1.18.5 && npm ci --production && npm run download-dist", "download-dist": "node extra/download-dist.js", "mark-as-nightly": "node extra/mark-as-nightly.js", "reset-password": "node extra/reset-password.js", From 2271ac4a5a181bfcb0c8836a3a6f812215e03685 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Tue, 11 Oct 2022 14:52:47 +0800 Subject: [PATCH 238/803] Add info.serverTimezoneOffset and improve some styles --- package-lock.json | 4 ++-- server/client.js | 2 ++ src/assets/app.scss | 5 +++++ src/pages/EditMaintenance.vue | 4 ++-- src/pages/ManageMaintenance.vue | 26 ++++++++++++++------------ src/util.js | 1 - src/util.ts | 1 - 7 files changed, 25 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index 161208af..0b9454ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "uptime-kuma", - "version": "1.18.4", + "version": "1.18.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "uptime-kuma", - "version": "1.18.4", + "version": "1.18.5", "license": "MIT", "dependencies": { "@louislam/sqlite3": "~15.0.6", diff --git a/server/client.js b/server/client.js index 795b3ad4..dcc778df 100644 --- a/server/client.js +++ b/server/client.js @@ -8,6 +8,7 @@ const server = UptimeKumaServer.getInstance(); const io = server.io; const { setting } = require("./util-server"); const checkVersion = require("./check-version"); +const dayjs = require("dayjs"); /** * Send list of notification providers to client @@ -124,6 +125,7 @@ async function sendInfo(socket) { latestVersion: checkVersion.latestVersion, primaryBaseURL: await setting("primaryBaseURL"), serverTimezone: await server.getTimezone(), + serverTimezoneOffset: dayjs().format("Z"), }); } diff --git a/src/assets/app.scss b/src/assets/app.scss index be324afd..7eb95931 100644 --- a/src/assets/app.scss +++ b/src/assets/app.scss @@ -101,6 +101,11 @@ optgroup { } } +// Override Bootstrap +.btn-group > .btn:hover { + z-index: initial; +} + .btn { padding-left: 20px; padding-right: 20px; diff --git a/src/pages/EditMaintenance.vue b/src/pages/EditMaintenance.vue index 790588d8..24229293 100644 --- a/src/pages/EditMaintenance.vue +++ b/src/pages/EditMaintenance.vue @@ -5,7 +5,7 @@
    -
    +
    @@ -82,7 +82,7 @@

    {{ $t("Date and Time") }}

    -
    ⚠️ {{ $t("warningTimezone") }}: {{ $root.info.serverTimezone }}
    +
    ⚠️ {{ $t("warningTimezone") }}: {{ $root.info.serverTimezone }} ({{ $root.info.serverTimezoneOffset }})
    diff --git a/src/pages/ManageMaintenance.vue b/src/pages/ManageMaintenance.vue index 5820aada..725e94b0 100644 --- a/src/pages/ManageMaintenance.vue +++ b/src/pages/ManageMaintenance.vue @@ -35,21 +35,23 @@
    {{ $t("Details") }} - +
    + - + - - {{ $t("Edit") }} - + + {{ $t("Edit") }} + - + +
    diff --git a/src/util.js b/src/util.js index ec2d2322..2213d4d0 100644 --- a/src/util.js +++ b/src/util.js @@ -363,7 +363,6 @@ function utcToISODateTime(input) { return dayjs.utc(input).toISOString(); } exports.utcToISODateTime = utcToISODateTime; -/** /** * For SQL_DATETIME_FORMAT */ diff --git a/src/util.ts b/src/util.ts index 34e0905d..966383f8 100644 --- a/src/util.ts +++ b/src/util.ts @@ -409,7 +409,6 @@ export function utcToISODateTime(input : string) { return dayjs.utc(input).toISOString(); } -/** /** * For SQL_DATETIME_FORMAT */ From 180d881ac122fb8f85805153f2342725a4843851 Mon Sep 17 00:00:00 2001 From: Alexander Borzov <59200516+Borzoff@users.noreply.github.com> Date: Tue, 11 Oct 2022 10:35:08 +0300 Subject: [PATCH 239/803] Update ru-RU.js (#2217) --- src/languages/ru-RU.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/languages/ru-RU.js b/src/languages/ru-RU.js index 0aaf0968..6922f51b 100644 --- a/src/languages/ru-RU.js +++ b/src/languages/ru-RU.js @@ -393,6 +393,12 @@ export default { alertaAlertState: "Состояние алерта", alertaRecoverState: "Состояние восстановления", Proxies: "Прокси", + "Setup Proxy": "Настройка Прокси", + "Proxy Protocol": "Протокол Прокси", + "Proxy Server": "Прокси", + "Proxy server has authentication": "Прокси имеет аутентификацию", + "Reverse Proxy": "Обратный прокси", + "No Proxy": "Без прокси", default: "По умолчанию", enabled: "Включено", setAsDefault: "Установлено по умолчанию", From 1e8a16504b7454f0e53692f948548280445b1449 Mon Sep 17 00:00:00 2001 From: Christian Meis Date: Tue, 11 Oct 2022 11:15:33 +0200 Subject: [PATCH 240/803] Make icon optional for ntfy notificaation provider. Add Icon header to ntfy request only, if icon is actually defined. --- server/notification-providers/ntfy.js | 11 +++++++---- src/components/notifications/Ntfy.vue | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/server/notification-providers/ntfy.js b/server/notification-providers/ntfy.js index 3288d8fb..64064f57 100644 --- a/server/notification-providers/ntfy.js +++ b/server/notification-providers/ntfy.js @@ -7,15 +7,18 @@ class Ntfy extends NotificationProvider { async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { let okMsg = "Sent Successfully."; - try { - await axios.post(`${notification.ntfyserverurl}`, { + var ntfyparams = { "topic": notification.ntfytopic, "message": msg, "priority": notification.ntfyPriority || 4, "title": "Uptime-Kuma", - "icon": notification.ntfyIcon || "", - }); + }; + if (notification.ntfyIcon) { + ntfyparams.icon = notification.ntfyIcon; + } + try { + await axios.post(`${notification.ntfyserverurl}`, ntfyparams); return okMsg; } catch (error) { diff --git a/src/components/notifications/Ntfy.vue b/src/components/notifications/Ntfy.vue index 4728f891..5764429f 100644 --- a/src/components/notifications/Ntfy.vue +++ b/src/components/notifications/Ntfy.vue @@ -19,7 +19,7 @@
    - +
    From e07aa982c34c5b72f08b9408aab08b39a8d49088 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Tue, 11 Oct 2022 18:23:17 +0800 Subject: [PATCH 241/803] WIP --- server/client.js | 2 +- server/model/maintenance.js | 20 +++++--- server/model/maintenance_timeslot.js | 16 +++++- .../maintenance-socket-handler.js | 50 +++++++++++++++++++ server/uptime-kuma-server.js | 4 ++ src/assets/app.scss | 18 +++++-- src/languages/en.js | 6 +++ src/pages/ManageMaintenance.vue | 49 ++++++++++++++---- src/util.js | 11 ++-- src/util.ts | 9 ++-- 10 files changed, 152 insertions(+), 33 deletions(-) diff --git a/server/client.js b/server/client.js index dcc778df..bed2ba03 100644 --- a/server/client.js +++ b/server/client.js @@ -125,7 +125,7 @@ async function sendInfo(socket) { latestVersion: checkVersion.latestVersion, primaryBaseURL: await setting("primaryBaseURL"), serverTimezone: await server.getTimezone(), - serverTimezoneOffset: dayjs().format("Z"), + serverTimezoneOffset: server.getTimezoneOffset(), }); } diff --git a/server/model/maintenance.js b/server/model/maintenance.js index 2ab2a5bb..4910d2a0 100644 --- a/server/model/maintenance.js +++ b/server/model/maintenance.js @@ -39,9 +39,15 @@ class Maintenance extends BeanModel { timeRange: timeRange, weekdays: (this.weekdays) ? JSON.parse(this.weekdays) : [], daysOfMonth: (this.days_of_month) ? JSON.parse(this.days_of_month) : [], - timeslotList: await this.getTimeslotList(), + timeslotList: [], }; + const timeslotList = await this.getTimeslotList(); + + for (let timeslot of timeslotList) { + obj.timeslotList.push(await timeslot.toPublicJSON()); + } + if (!isArray(obj.weekdays)) { obj.weekdays = []; } @@ -53,7 +59,9 @@ class Maintenance extends BeanModel { // Maintenance Status if (!obj.active) { obj.status = "inactive"; - } else if (obj.strategy === "manual" || obj.timeslotList.length > 0) { + } else if (obj.strategy === "manual") { + obj.status = "under-maintenance"; + } else if (obj.timeslotList.length > 0) { for (let timeslot of obj.timeslotList) { if (dayjs.utc(timeslot.start_date) <= dayjs.utc() && dayjs.utc(timeslot.end_date) >= dayjs.utc()) { obj.status = "under-maintenance"; @@ -78,7 +86,7 @@ class Maintenance extends BeanModel { * @returns {Promise<[]>} */ async getTimeslotList() { - return await R.getAll(` + return R.convertToBeans("maintenance_timeslot", await R.getAll(` SELECT maintenance_timeslot.* FROM maintenance_timeslot, maintenance WHERE maintenance_timeslot.maintenance_id = maintenance.id @@ -86,7 +94,7 @@ class Maintenance extends BeanModel { AND ${Maintenance.getActiveAndFutureMaintenanceSQLCondition()} `, [ this.id - ]); + ])); } /** @@ -156,10 +164,10 @@ class Maintenance extends BeanModel { */ static getActiveAndFutureMaintenanceSQLCondition() { return ` - (maintenance_timeslot.end_date >= DATETIME('now') + ((maintenance_timeslot.end_date >= DATETIME('now') AND maintenance.active = 1) OR - (maintenance.strategy = 'manual' AND active = 1) + (maintenance.strategy = 'manual' AND active = 1)) `; } } diff --git a/server/model/maintenance_timeslot.js b/server/model/maintenance_timeslot.js index 4db3a1db..f06806ac 100644 --- a/server/model/maintenance_timeslot.js +++ b/server/model/maintenance_timeslot.js @@ -1,16 +1,28 @@ const { BeanModel } = require("redbean-node/dist/bean-model"); const { R } = require("redbean-node"); const dayjs = require("dayjs"); -const { log } = require("../../src/util"); +const { log, utcToLocal, SQL_DATETIME_FORMAT_WITHOUT_SECOND } = require("../../src/util"); +const { UptimeKumaServer } = require("../uptime-kuma-server"); class MaintenanceTimeslot extends BeanModel { async toPublicJSON() { + const serverTimezoneOffset = await UptimeKumaServer.getInstance().getTimezoneOffset(); + const obj = { + id: this.id, + startDate: this.start_date, + endDate: this.end_date, + startDateServerTimezone: utcToLocal(this.start_date, SQL_DATETIME_FORMAT_WITHOUT_SECOND), + endDateServerTimezone: utcToLocal(this.end_date, SQL_DATETIME_FORMAT_WITHOUT_SECOND), + serverTimezoneOffset, + }; + + return obj; } async toJSON() { - + return await this.toPublicJSON(); } /** diff --git a/server/socket-handlers/maintenance-socket-handler.js b/server/socket-handlers/maintenance-socket-handler.js index 49527f23..5294050c 100644 --- a/server/socket-handlers/maintenance-socket-handler.js +++ b/server/socket-handlers/maintenance-socket-handler.js @@ -258,4 +258,54 @@ module.exports.maintenanceSocketHandler = (socket) => { }); } }); + + socket.on("pauseMaintenance", async (maintenanceID, callback) => { + try { + checkLogin(socket); + + log.debug("maintenance", `Pause Maintenance: ${maintenanceID} User ID: ${socket.userID}`); + + await R.exec("UPDATE maintenance SET active = 0 WHERE id = ? ", [ + maintenanceID, + ]); + + callback({ + ok: true, + msg: "Paused Successfully.", + }); + + await server.sendMaintenanceList(socket); + + } catch (e) { + callback({ + ok: false, + msg: e.message, + }); + } + }); + + socket.on("resumeMaintenance", async (maintenanceID, callback) => { + try { + checkLogin(socket); + + log.debug("maintenance", `Resume Maintenance: ${maintenanceID} User ID: ${socket.userID}`); + + await R.exec("UPDATE maintenance SET active = 1 WHERE id = ? ", [ + maintenanceID, + ]); + + callback({ + ok: true, + msg: "Resume Successfully", + }); + + await server.sendMaintenanceList(socket); + + } catch (e) { + callback({ + ok: false, + msg: e.message, + }); + } + }); }; diff --git a/server/uptime-kuma-server.js b/server/uptime-kuma-server.js index 15583159..667f6b6a 100644 --- a/server/uptime-kuma-server.js +++ b/server/uptime-kuma-server.js @@ -204,6 +204,10 @@ class UptimeKumaServer { } } + async getTimezoneOffset() { + return dayjs().format("Z"); + } + async setTimezone(timezone) { await Settings.set("serverTimezone", timezone, "general"); process.env.TZ = timezone; diff --git a/src/assets/app.scss b/src/assets/app.scss index 7eb95931..7da76fff 100644 --- a/src/assets/app.scss +++ b/src/assets/app.scss @@ -101,11 +101,6 @@ optgroup { } } -// Override Bootstrap -.btn-group > .btn:hover { - z-index: initial; -} - .btn { padding-left: 20px; padding-right: 20px; @@ -125,6 +120,19 @@ optgroup { } } +.btn-normal { + $bg-color: #F5F5F5; + + background-color: $bg-color; + border-color: $bg-color; + + &:hover { + $hover-color: darken($bg-color, 3%); + background-color: $hover-color; + border-color: $hover-color; + } +} + .btn-warning { color: white; diff --git a/src/languages/en.js b/src/languages/en.js index 835fa248..a1c9b560 100644 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -631,4 +631,10 @@ export default { lastDay3: "3rd Last Day of Month", lastDay4: "4th Last Day of Month", "No Maintenance": "No Maintenance", + pauseMaintenanceMsg: "Are you sure want to pause?", + "maintenanceStatus-under-maintenance": "Under Maintenance", + "maintenanceStatus-inactive": "Inactive", + "maintenanceStatus-scheduled": "Scheduled", + "maintenanceStatus-ended": "Ended", + "maintenanceStatus-unknown": "Unknown", }; diff --git a/src/pages/ManageMaintenance.vue b/src/pages/ManageMaintenance.vue index 725e94b0..af9c0ba5 100644 --- a/src/pages/ManageMaintenance.vue +++ b/src/pages/ManageMaintenance.vue @@ -28,7 +28,18 @@ >
    {{ item.title }}
    -
    {{ item.description }}
    +
    {{ item.description }}
    +
    + {{ $t("maintenanceStatus-" + item.status) }} +
    + +
    + {{ $t("Manual") }} +
    +
    + {{ item.timeslotList[0].startDateServerTimezone }} - {{ item.timeslotList[0].endDateServerTimezone }} + (UTC{{ item.timeslotList[0].serverTimezoneOffset }}) +
    @@ -36,11 +47,11 @@ {{ $t("Details") }}
    - - @@ -149,7 +160,8 @@ export default { /** * Show dialog to confirm pause */ - pauseDialog() { + pauseDialog(maintenanceID) { + this.selectedMaintenanceID = maintenanceID; this.$refs.confirmPause.show(); }, @@ -157,8 +169,7 @@ export default { * Pause maintenance */ pauseMaintenance() { - return; - this.$root.getSocket().emit("pauseMaintenance", selectedMaintenanceID, (res) => { + this.$root.getSocket().emit("pauseMaintenance", this.selectedMaintenanceID, (res) => { this.$root.toastRes(res); }); }, @@ -166,9 +177,8 @@ export default { /** * Resume maintenance */ - resumeMaintenance() { - return; - this.$root.getSocket().emit("resumeMaintenance", selectedMaintenanceID, (res) => { + resumeMaintenance(id) { + this.$root.getSocket().emit("resumeMaintenance", id, (res) => { this.$root.toastRes(res); }); }, @@ -189,6 +199,7 @@ export default { justify-content: space-between; padding: 10px; min-height: 90px; + margin-bottom: 5px; &:hover { background-color: $highlight-white; @@ -251,9 +262,27 @@ export default { font-size: 20px; } - .slug { + .status { font-size: 14px; } + + .timeslot { + margin-top: 5px; + display: inline-block; + font-size: 14px; + background-color: rgba(255, 255, 255, 0.5); + border-radius: 20px; + padding: 0 10px; + + .to { + margin: 0 6px; + } + + .dark & { + color: white; + background-color: rgba(255, 255, 255, 0.1); + } + } } } diff --git a/src/util.js b/src/util.js index 2213d4d0..9cdecc17 100644 --- a/src/util.js +++ b/src/util.js @@ -7,7 +7,7 @@ // Backend uses the compiled file util.js // Frontend uses util.ts Object.defineProperty(exports, "__esModule", { value: true }); -exports.localToUTC = exports.utcToLocal = exports.utcToISODateTime = exports.isoToUTCDateTime = exports.parseTimeFromTimeObject = exports.parseTimeObject = exports.getMaintenanceRelativeURL = exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.log = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.SQL_DATETIME_FORMAT = exports.SQL_DATE_FORMAT = exports.STATUS_PAGE_MAINTENANCE = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.MAINTENANCE = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0; +exports.localToUTC = exports.utcToLocal = exports.utcToISODateTime = exports.isoToUTCDateTime = exports.parseTimeFromTimeObject = exports.parseTimeObject = exports.getMaintenanceRelativeURL = exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.log = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.SQL_DATETIME_FORMAT_WITHOUT_SECOND = exports.SQL_DATETIME_FORMAT = exports.SQL_DATE_FORMAT = exports.STATUS_PAGE_MAINTENANCE = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.MAINTENANCE = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0; const dayjs = require("dayjs"); exports.isDev = process.env.NODE_ENV === "development"; exports.appName = "Uptime Kuma"; @@ -21,6 +21,7 @@ exports.STATUS_PAGE_PARTIAL_DOWN = 2; exports.STATUS_PAGE_MAINTENANCE = 3; exports.SQL_DATE_FORMAT = "YYYY-MM-DD"; exports.SQL_DATETIME_FORMAT = "YYYY-MM-DD HH:mm:ss"; +exports.SQL_DATETIME_FORMAT_WITHOUT_SECOND = "YYYY-MM-DD HH:mm"; /** Flip the status of s */ function flipStatus(s) { if (s === exports.UP) { @@ -366,11 +367,11 @@ exports.utcToISODateTime = utcToISODateTime; /** * For SQL_DATETIME_FORMAT */ -function utcToLocal(input) { - return dayjs.utc(input).local().format(exports.SQL_DATETIME_FORMAT); +function utcToLocal(input, format = exports.SQL_DATETIME_FORMAT) { + return dayjs.utc(input).local().format(format); } exports.utcToLocal = utcToLocal; -function localToUTC(input) { - return dayjs(input).utc().format(exports.SQL_DATETIME_FORMAT); +function localToUTC(input, format = exports.SQL_DATETIME_FORMAT) { + return dayjs(input).utc().format(format); } exports.localToUTC = localToUTC; diff --git a/src/util.ts b/src/util.ts index 966383f8..5d8acfcd 100644 --- a/src/util.ts +++ b/src/util.ts @@ -25,6 +25,7 @@ export const STATUS_PAGE_MAINTENANCE = 3; export const SQL_DATE_FORMAT = "YYYY-MM-DD"; export const SQL_DATETIME_FORMAT = "YYYY-MM-DD HH:mm:ss"; +export const SQL_DATETIME_FORMAT_WITHOUT_SECOND = "YYYY-MM-DD HH:mm"; /** Flip the status of s */ export function flipStatus(s: number) { @@ -412,10 +413,10 @@ export function utcToISODateTime(input : string) { /** * For SQL_DATETIME_FORMAT */ -export function utcToLocal(input : string) { - return dayjs.utc(input).local().format(SQL_DATETIME_FORMAT); +export function utcToLocal(input : string, format = SQL_DATETIME_FORMAT) { + return dayjs.utc(input).local().format(format); } -export function localToUTC(input : string) { - return dayjs(input).utc().format(SQL_DATETIME_FORMAT); +export function localToUTC(input : string, format = SQL_DATETIME_FORMAT) { + return dayjs(input).utc().format(format); } From dfb75c8afb83aee87051966b87d4bb6312433e80 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Tue, 11 Oct 2022 20:56:48 +0800 Subject: [PATCH 242/803] Update status page's maintenance message --- server/model/status_page.js | 14 ++++---- src/components/MaintenanceTime.vue | 44 +++++++++++++++++++++++++ src/languages/en.js | 6 ++-- src/pages/EditMaintenance.vue | 16 --------- src/pages/ManageMaintenance.vue | 28 ++-------------- src/pages/StatusPage.vue | 53 +++++++++++++----------------- 6 files changed, 80 insertions(+), 81 deletions(-) create mode 100644 src/components/MaintenanceTime.vue diff --git a/server/model/status_page.js b/server/model/status_page.js index a5a3a4b7..4e7b38cf 100644 --- a/server/model/status_page.js +++ b/server/model/status_page.js @@ -91,7 +91,7 @@ class StatusPage extends BeanModel { incident = incident.toPublicJSON(); } - let maintenance = await StatusPage.getMaintenanceList(statusPage.id); + let maintenanceList = await StatusPage.getMaintenanceList(statusPage.id); // Public Group List const publicGroupList = []; @@ -111,7 +111,7 @@ class StatusPage extends BeanModel { config: await statusPage.toPublicJSON(), incident, publicGroupList, - maintenance, + maintenanceList, }; } @@ -281,13 +281,13 @@ class StatusPage extends BeanModel { let activeCondition = Maintenance.getActiveMaintenanceSQLCondition(); let maintenanceBeanList = R.convertToBeans("maintenance", await R.getAll(` - SELECT m.* - FROM maintenance m, maintenance_status_page msp, maintenance_timeslot - WHERE msp.maintenance_id = m.id - AND maintenance_timeslot.maintenance.id = m.id + SELECT maintenance.* + FROM maintenance, maintenance_status_page msp, maintenance_timeslot + WHERE msp.maintenance_id = maintenance.id + AND maintenance_timeslot.maintenance_id = maintenance.id AND msp.status_page_id = ? AND ${activeCondition} - ORDER BY m.end_date + ORDER BY maintenance.end_date `, [ statusPageId ])); for (const bean of maintenanceBeanList) { diff --git a/src/components/MaintenanceTime.vue b/src/components/MaintenanceTime.vue new file mode 100644 index 00000000..66ee4abf --- /dev/null +++ b/src/components/MaintenanceTime.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/src/languages/en.js b/src/languages/en.js index a1c9b560..a4c937e6 100644 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -20,13 +20,10 @@ export default { "Selected status pages": "Selected status pages", "Select status pages...": "Select status pages...", recurringIntervalMessage: "Run once every day | Run once every {0} days", - "End": "End", affectedMonitorsDescription: "Select monitors that are affected by current maintenance", affectedStatusPages: "Show this maintenance message on selected status pages", atLeastOneMonitor: "Select at least one affected monitor", - maintenanceInvalidDate: "Invalid maintenance end date entered", selectedStatusPagesDescription: "Select status pages to display maintenance info on", - atLeastOneStatusPage: "Select at least one status page", maintenanceTitleExample: "Network infrastructure maintenance", maintenanceDescriptionExample: "Example: Network infrastructure maintenance is underway which will affect some of our services.", passwordNotMatchMsg: "The repeat password does not match.", @@ -637,4 +634,7 @@ export default { "maintenanceStatus-scheduled": "Scheduled", "maintenanceStatus-ended": "Ended", "maintenanceStatus-unknown": "Unknown", + "Display Timezone": "Display Timezone", + "Server Timezone": "Server Timezone", + statusPageMaintenanceEndDate: "End", }; diff --git a/src/pages/EditMaintenance.vue b/src/pages/EditMaintenance.vue index 24229293..71df19b1 100644 --- a/src/pages/EditMaintenance.vue +++ b/src/pages/EditMaintenance.vue @@ -422,22 +422,6 @@ export default { return this.processing = false; } - /* - TODO: Temporary disable - if (this.maintenance.start_date >= this.maintenance.end_date) { - toast.error(this.$t("maintenanceInvalidDate")); - return this.processing = false; - } - - if (!this.showOnAllPages && this.selectedStatusPages.length === 0) { - toast.error(this.$t("atLeastOneStatusPage")); - return this.processing = false; - } - - this.maintenance.start_date = this.$root.toUTC(this.maintenance.start_date); - this.maintenance.end_date = this.$root.toUTC(this.maintenance.end_date); - */ - if (this.isAdd) { this.$root.addMaintenance(this.maintenance, async (res) => { if (res.ok) { diff --git a/src/pages/ManageMaintenance.vue b/src/pages/ManageMaintenance.vue index af9c0ba5..9ded0459 100644 --- a/src/pages/ManageMaintenance.vue +++ b/src/pages/ManageMaintenance.vue @@ -33,13 +33,7 @@ {{ $t("maintenanceStatus-" + item.status) }}
    -
    - {{ $t("Manual") }} -
    -
    - {{ item.timeslotList[0].startDateServerTimezone }} - {{ item.timeslotList[0].endDateServerTimezone }} - (UTC{{ item.timeslotList[0].serverTimezoneOffset }}) -
    +
    @@ -86,11 +80,13 @@ import { getResBaseURL } from "../util-frontend"; import { getMaintenanceRelativeURL } from "../util.ts"; import Confirm from "../components/Confirm.vue"; +import MaintenanceTime from "../components/MaintenanceTime.vue"; import { useToast } from "vue-toastification"; const toast = useToast(); export default { components: { + MaintenanceTime, Confirm, }, data() { @@ -265,24 +261,6 @@ export default { .status { font-size: 14px; } - - .timeslot { - margin-top: 5px; - display: inline-block; - font-size: 14px; - background-color: rgba(255, 255, 255, 0.5); - border-radius: 20px; - padding: 0 10px; - - .to { - margin: 0 6px; - } - - .dark & { - color: white; - background-color: rgba(255, 255, 255, 0.1); - } - } } } diff --git a/src/pages/StatusPage.vue b/src/pages/StatusPage.vue index 699c236d..6cecf668 100644 --- a/src/pages/StatusPage.vue +++ b/src/pages/StatusPage.vue @@ -195,33 +195,6 @@
    - - -
    @@ -247,7 +220,7 @@
    - {{ $t("Maintenance") }} + {{ $t("maintenanceStatus-under-maintenance") }}
    @@ -256,6 +229,18 @@
    + + + {{ $t("Description") }}: @@ -327,6 +312,7 @@ import "vue-prism-editor/dist/prismeditor.min.css"; // import the styles somewhe import { useToast } from "vue-toastification"; import Confirm from "../components/Confirm.vue"; import PublicGroupList from "../components/PublicGroupList.vue"; +import MaintenanceTime from "../components/MaintenanceTime.vue"; import { getResBaseURL } from "../util-frontend"; import { STATUS_PAGE_ALL_DOWN, STATUS_PAGE_ALL_UP, STATUS_PAGE_MAINTENANCE, STATUS_PAGE_PARTIAL_DOWN, UP, MAINTENANCE } from "../util.ts"; @@ -348,6 +334,7 @@ export default { ImageCropUpload, Confirm, PrismEditor, + MaintenanceTime, }, // Leave Page for vue route change @@ -388,7 +375,7 @@ export default { loadedData: false, baseURL: "", clickedEditButton: false, - maintenance: [], + maintenanceList: [], }; }, computed: { @@ -594,7 +581,7 @@ export default { } this.incident = res.data.incident; - this.maintenance = res.data.maintenance; + this.maintenanceList = res.data.maintenanceList; this.$root.publicGroupList = res.data.publicGroupList; }).catch( function (error) { if (error.response.status === 404) { @@ -1069,4 +1056,10 @@ footer { } } +.bg-maintenance { + .alert-heading { + font-weight: bold; + } +} + From 39b67251631176dbaf628ef5731133abd9c313a6 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Tue, 11 Oct 2022 21:48:43 +0800 Subject: [PATCH 243/803] Update maintenance tables --- db/patch-maintenance-table.sql | 34 ----------------- db/patch-maintenance-table2.sql | 56 ++++++++++++++++++++++++++++ server/database.js | 2 +- server/model/maintenance_timeslot.js | 2 +- server/uptime-kuma-server.js | 2 +- 5 files changed, 59 insertions(+), 37 deletions(-) delete mode 100644 db/patch-maintenance-table.sql create mode 100644 db/patch-maintenance-table2.sql diff --git a/db/patch-maintenance-table.sql b/db/patch-maintenance-table.sql deleted file mode 100644 index ce14f766..00000000 --- a/db/patch-maintenance-table.sql +++ /dev/null @@ -1,34 +0,0 @@ --- You should not modify if this have pushed to Github, unless it does serious wrong with the db. -BEGIN TRANSACTION; - -CREATE TABLE maintenance -( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - title VARCHAR(150), - description TEXT, - user_id INTEGER REFERENCES user ON UPDATE CASCADE ON DELETE SET NULL, - start_date DATETIME, - end_date DATETIME -); - -CREATE TABLE monitor_maintenance -( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - monitor_id INTEGER NOT NULL, - maintenance_id INTEGER NOT NULL, - CONSTRAINT FK_maintenance FOREIGN KEY (maintenance_id) REFERENCES maintenance (id) ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT FK_monitor FOREIGN KEY (monitor_id) REFERENCES monitor (id) ON DELETE CASCADE ON UPDATE CASCADE -); - -CREATE TABLE maintenance_status_page -( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - status_page_id INTEGER NOT NULL, - maintenance_id INTEGER NOT NULL, - CONSTRAINT FK_maintenance FOREIGN KEY (maintenance_id) REFERENCES maintenance (id) ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT FK_status_page FOREIGN KEY (status_page_id) REFERENCES status_page (id) ON DELETE CASCADE ON UPDATE CASCADE -); - -create index maintenance_user_id on maintenance (user_id); - -COMMIT; diff --git a/db/patch-maintenance-table2.sql b/db/patch-maintenance-table2.sql new file mode 100644 index 00000000..76644596 --- /dev/null +++ b/db/patch-maintenance-table2.sql @@ -0,0 +1,56 @@ +-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. +BEGIN TRANSACTION; + +-- Just for someone who tested maintenance before (patch-maintenance-table.sql) +DROP TABLE IF EXISTS maintenance_status_page; +DROP TABLE IF EXISTS monitor_maintenance; +DROP TABLE IF EXISTS maintenance; +DROP TABLE IF EXISTS maintenance_timeslot; + +-- maintenance +CREATE TABLE [maintenance] ( + [id] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + [title] VARCHAR(150) NOT NULL, + [description] TEXT NOT NULL, + [user_id] INTEGER REFERENCES [user]([id]) ON DELETE SET NULL ON UPDATE CASCADE, + [active] BOOLEAN NOT NULL DEFAULT 1, + [strategy] VARCHAR(50) NOT NULL DEFAULT 'single', + [start_date] DATETIME, + [end_date] DATETIME, + [start_time] TIME, + [end_time] TIME, + [weekdays] VARCHAR2(250) DEFAULT '[]', + [days_of_month] TEXT DEFAULT '[]', + [interval_day] INTEGER +); + +CREATE INDEX [maintenance_user_id] ON [maintenance]([user_id]); + +-- maintenance_status_page +CREATE TABLE maintenance_status_page ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + status_page_id INTEGER NOT NULL, + maintenance_id INTEGER NOT NULL, + CONSTRAINT FK_maintenance FOREIGN KEY (maintenance_id) REFERENCES maintenance (id) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT FK_status_page FOREIGN KEY (status_page_id) REFERENCES status_page (id) ON DELETE CASCADE ON UPDATE CASCADE +); + +-- maintenance_timeslot +CREATE TABLE [maintenance_timeslot] ( + [id] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + [maintenance_id] INTEGER NOT NULL CONSTRAINT [FK_maintenance] REFERENCES [maintenance]([id]) ON DELETE CASCADE ON UPDATE CASCADE, + [start_date] DATETIME NOT NULL, + [end_date] DATETIME, + [generated_next] BOOLEAN DEFAULT 0 +); + +-- monitor_maintenance +CREATE TABLE monitor_maintenance ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + monitor_id INTEGER NOT NULL, + maintenance_id INTEGER NOT NULL, + CONSTRAINT FK_maintenance FOREIGN KEY (maintenance_id) REFERENCES maintenance (id) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT FK_monitor FOREIGN KEY (monitor_id) REFERENCES monitor (id) ON DELETE CASCADE ON UPDATE CASCADE +); + +COMMIT; diff --git a/server/database.js b/server/database.js index 5666d9a6..16f12445 100644 --- a/server/database.js +++ b/server/database.js @@ -64,7 +64,7 @@ class Database { "patch-add-other-auth.sql": { parents: [ "patch-monitor-basic-auth.sql" ] }, "patch-add-radius-monitor.sql": true, "patch-monitor-add-resend-interval.sql": true, - "patch-maintenance-table.sql": true, + "patch-maintenance-table2.sql": true, }; /** diff --git a/server/model/maintenance_timeslot.js b/server/model/maintenance_timeslot.js index f06806ac..3ac64b6b 100644 --- a/server/model/maintenance_timeslot.js +++ b/server/model/maintenance_timeslot.js @@ -7,7 +7,7 @@ const { UptimeKumaServer } = require("../uptime-kuma-server"); class MaintenanceTimeslot extends BeanModel { async toPublicJSON() { - const serverTimezoneOffset = await UptimeKumaServer.getInstance().getTimezoneOffset(); + const serverTimezoneOffset = UptimeKumaServer.getInstance().getTimezoneOffset(); const obj = { id: this.id, diff --git a/server/uptime-kuma-server.js b/server/uptime-kuma-server.js index 667f6b6a..ac832f8e 100644 --- a/server/uptime-kuma-server.js +++ b/server/uptime-kuma-server.js @@ -204,7 +204,7 @@ class UptimeKumaServer { } } - async getTimezoneOffset() { + getTimezoneOffset() { return dayjs().format("Z"); } From d95e7226586fb504e08d59666e58e5ac6eb534fe Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Tue, 11 Oct 2022 22:09:18 +0800 Subject: [PATCH 244/803] Init dayjs for backend.spec.js --- test/backend.spec.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/backend.spec.js b/test/backend.spec.js index 5b9fa92c..644a0fd0 100644 --- a/test/backend.spec.js +++ b/test/backend.spec.js @@ -6,6 +6,9 @@ const { UptimeKumaServer } = require("../server/uptime-kuma-server"); const Database = require("../server/database"); const {Settings} = require("../server/settings"); const fs = require("fs"); +const dayjs = require("dayjs"); +dayjs.extend(require("dayjs/plugin/utc")); +dayjs.extend(require("dayjs/plugin/timezone")); jest.mock("axios"); From 7b9766091e79282cb039e2ace956f264a7029d1c Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Tue, 11 Oct 2022 22:18:09 +0800 Subject: [PATCH 245/803] Revert testing --- server/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/server.js b/server/server.js index f80d5d57..41cf7c37 100644 --- a/server/server.js +++ b/server/server.js @@ -1106,7 +1106,7 @@ let needSetup = false; callback({ ok: true, - msg: "Saved " + dayjs() + msg: "Saved" }); sendInfo(socket); From 8cc3e4b7c1a0e69d419f943862d338e9f480f293 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Tue, 11 Oct 2022 22:28:00 +0800 Subject: [PATCH 246/803] Revert --- src/languages/en.js | 1 - src/layouts/Layout.vue | 20 +------------------- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/src/languages/en.js b/src/languages/en.js index a4c937e6..7db8147f 100644 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -59,7 +59,6 @@ export default { "Check Update On GitHub": "Check Update On GitHub", List: "List", Add: "Add", - "Add Monitor": "Add Monitor", "Add New Monitor": "Add New Monitor", "Quick Stats": "Quick Stats", Up: "Up", diff --git a/src/layouts/Layout.vue b/src/layouts/Layout.vue index acd9446c..d13e8a8d 100644 --- a/src/layouts/Layout.vue +++ b/src/layouts/Layout.vue @@ -103,7 +103,7 @@
    - {{ $t("Add Monitor") }} + {{ $t("Add") }}
    @@ -317,22 +317,4 @@ main { background-color: $dark-bg; } } - -.scroll { - display: flex; - flex-wrap: nowrap; - overflow-x: auto; - -webkit-overflow-scrolling: touch; - -ms-overflow-style: -ms-autohiding-scrollbar; -} - -.scroll::-webkit-scrollbar { - display: none; -} - -.scroll a { - flex: 0 0 auto; - min-width: fit-content; -} - From 2faf866e9ed5014452a61c96e3df436677ec39cf Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Wed, 12 Oct 2022 17:02:16 +0800 Subject: [PATCH 247/803] Implement generateTimeslot() for recurring interval type --- server/model/maintenance.js | 11 +++- server/model/maintenance_timeslot.js | 75 ++++++++++++++++++++++++---- server/server.js | 2 + server/util-server.js | 14 +++--- src/components/MaintenanceTime.vue | 6 +-- src/pages/EditMaintenance.vue | 3 +- 6 files changed, 87 insertions(+), 24 deletions(-) diff --git a/server/model/maintenance.js b/server/model/maintenance.js index 4910d2a0..d1197009 100644 --- a/server/model/maintenance.js +++ b/server/model/maintenance.js @@ -1,5 +1,5 @@ const { BeanModel } = require("redbean-node/dist/bean-model"); -const { parseTimeObject, parseTimeFromTimeObject, utcToLocal, localToUTC } = require("../../src/util"); +const { parseTimeObject, parseTimeFromTimeObject, utcToLocal, localToUTC, log } = require("../../src/util"); const { isArray } = require("chart.js/helpers"); const { timeObjectToUTC, timeObjectToLocal } = require("../util-server"); const { R } = require("redbean-node"); @@ -62,8 +62,15 @@ class Maintenance extends BeanModel { } else if (obj.strategy === "manual") { obj.status = "under-maintenance"; } else if (obj.timeslotList.length > 0) { + let currentTimestamp = dayjs().unix(); + for (let timeslot of obj.timeslotList) { - if (dayjs.utc(timeslot.start_date) <= dayjs.utc() && dayjs.utc(timeslot.end_date) >= dayjs.utc()) { + if (dayjs.utc(timeslot.startDate).unix() <= currentTimestamp && dayjs.utc(timeslot.endDate).unix() >= currentTimestamp) { + log.debug("timeslot", "Timeslot ID: " + timeslot.id); + log.debug("timeslot", "currentTimestamp:" + currentTimestamp); + log.debug("timeslot", "timeslot.start_date:" + dayjs.utc(timeslot.startDate).unix()); + log.debug("timeslot", "timeslot.end_date:" + dayjs.utc(timeslot.endDate).unix()); + obj.status = "under-maintenance"; break; } diff --git a/server/model/maintenance_timeslot.js b/server/model/maintenance_timeslot.js index 3ac64b6b..4c13632d 100644 --- a/server/model/maintenance_timeslot.js +++ b/server/model/maintenance_timeslot.js @@ -1,7 +1,7 @@ const { BeanModel } = require("redbean-node/dist/bean-model"); const { R } = require("redbean-node"); const dayjs = require("dayjs"); -const { log, utcToLocal, SQL_DATETIME_FORMAT_WITHOUT_SECOND } = require("../../src/util"); +const { log, utcToLocal, SQL_DATETIME_FORMAT_WITHOUT_SECOND, localToUTC } = require("../../src/util"); const { UptimeKumaServer } = require("../uptime-kuma-server"); class MaintenanceTimeslot extends BeanModel { @@ -26,17 +26,12 @@ class MaintenanceTimeslot extends BeanModel { } /** - * * @param {Maintenance} maintenance - * @param {dayjs} startFrom (For recurring type only) Generate Timeslot from this date, if it is smaller than the current date, it will use the current date instead. As generating a passed timeslot is meaningless. + * @param {dayjs} minDate (For recurring type only) Generate a next timeslot from this date. * @param {boolean} removeExist Remove existing timeslot before create - * @returns {Promise} + * @returns {Promise} */ - static async generateTimeslot(maintenance, startFrom = null, removeExist = false) { - if (!startFrom) { - startFrom = dayjs(); - } - + static async generateTimeslot(maintenance, minDate = null, removeExist = false) { if (removeExist) { await R.exec("DELETE FROM maintenance_timeslot WHERE maintenance_id = ? ", [ maintenance.id @@ -51,9 +46,67 @@ class MaintenanceTimeslot extends BeanModel { bean.start_date = maintenance.start_date; bean.end_date = maintenance.end_date; bean.generated_next = true; - await R.store(bean); + return await R.store(bean); } else if (maintenance.strategy === "recurring-interval") { - // TODO + let bean = R.dispense("maintenance_timeslot"); + + // Prevent dead loop, in case interval_day is not set + if (!maintenance.interval_day || maintenance.interval_day <= 0) { + maintenance.interval_day = 1; + } + + let startOfTheDay = dayjs.utc(maintenance.start_date).format("HH:mm"); + log.debug("timeslot", "startOfTheDay: " + startOfTheDay); + + // Start Time + let startTimeSecond = dayjs.utc(maintenance.start_time, "HH:mm").diff(dayjs.utc(startOfTheDay, "HH:mm"), "second"); + log.debug("timeslot", "startTime: " + startTimeSecond); + + // Duration + let duration = dayjs.utc(maintenance.end_time, "HH:mm").diff(dayjs.utc(maintenance.start_time, "HH:mm"), "second"); + // Add 24hours if it is across day + if (duration < 0) { + duration += 24 * 3600; + } + + // Bake StartDate + StartTime = Start DateTime + let startDateTime = dayjs.utc(maintenance.start_date).add(startTimeSecond, "second"); + let endDateTime; + + // Keep generating from the first possible date, until it is ok + while (true) { + log.debug("timeslot", "startDateTime: " + startDateTime.format()); + + // Handling out of effective date range + if (startDateTime.diff(dayjs.utc(maintenance.end_date)) > 0) { + log.debug("timeslot", "Out of effective date range"); + return null; + } + + endDateTime = startDateTime.add(duration, "second"); + + // If endDateTime is out of effective date range, use the end datetime from effective date range + if (endDateTime.diff(dayjs.utc(maintenance.end_date)) > 0) { + endDateTime = dayjs.utc(maintenance.end_date); + } + + // If minDate is set, the endDateTime must be bigger than it. + // And the endDateTime must be bigger current time + if ( + (!minDate || endDateTime.diff(minDate) > 0) && + endDateTime.diff(dayjs()) > 0 + ) { + break; + } + + startDateTime = startDateTime.add(maintenance.interval_day, "day"); + } + + bean.maintenance_id = maintenance.id; + bean.start_date = localToUTC(startDateTime); + bean.end_date = localToUTC(endDateTime); + bean.generated_next = false; + return await R.store(bean); } else if (maintenance.strategy === "recurring-weekday") { // TODO } else if (maintenance.strategy === "recurring-day-of-month") { diff --git a/server/server.js b/server/server.js index 41cf7c37..03b8b6cd 100644 --- a/server/server.js +++ b/server/server.js @@ -9,6 +9,7 @@ console.log("Welcome to Uptime Kuma"); const dayjs = require("dayjs"); dayjs.extend(require("dayjs/plugin/utc")); dayjs.extend(require("dayjs/plugin/timezone")); +dayjs.extend(require("dayjs/plugin/customParseFormat")); // Check Node.js Version const nodeVersion = parseInt(process.versions.node.split(".")[0]); @@ -1110,6 +1111,7 @@ let needSetup = false; }); sendInfo(socket); + server.sendMaintenanceList(socket); } catch (e) { callback({ diff --git a/server/util-server.js b/server/util-server.js index 7c81cde7..ac8bc488 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -671,18 +671,20 @@ function timeObjectConvertTimezone(obj, timezone, timeObjectToUTC = true) { obj.minutes += minutes; // Handle out of bound + if (obj.minutes < 0) { + obj.minutes += 60; + obj.hours--; + } else if (obj.minutes > 60) { + obj.minutes -= 60; + obj.hours++; + } + if (obj.hours < 0) { obj.hours += 24; } else if (obj.hours > 24) { obj.hours -= 24; } - if (obj.minutes < 0) { - obj.minutes += 24; - } else if (obj.minutes > 24) { - obj.minutes -= 24; - } - return obj; } diff --git a/src/components/MaintenanceTime.vue b/src/components/MaintenanceTime.vue index 66ee4abf..07d65740 100644 --- a/src/components/MaintenanceTime.vue +++ b/src/components/MaintenanceTime.vue @@ -1,9 +1,9 @@