From b9a6088f50e1680fbab3c7fa3ef4a03cfaf3caca Mon Sep 17 00:00:00 2001 From: Nelson Chan Date: Fri, 30 Jul 2021 14:54:40 +0800 Subject: [PATCH 001/189] WIP: Add login page, nav bar buttons --- src/icon.js | 4 +-- src/layouts/Layout.vue | 10 ++++++ src/main.js | 7 ++-- src/pages/Login.vue | 78 ++++++++++++++++++++++++++++++++++++++++++ src/pages/Settings.vue | 6 ---- 5 files changed, 95 insertions(+), 10 deletions(-) create mode 100644 src/pages/Login.vue diff --git a/src/icon.js b/src/icon.js index d8ea36d..7911e9c 100644 --- a/src/icon.js +++ b/src/icon.js @@ -1,10 +1,10 @@ import { library } from "@fortawesome/fontawesome-svg-core" -import { faCog, faEdit, faList, faPause, faPlay, faPlus, faTachometerAlt, faTrash } from "@fortawesome/free-solid-svg-icons" +import { faCog, faEdit, faList, faPause, faPlay, faPlus, faSignInAlt, faSignOutAlt, faTachometerAlt, faTrash } from "@fortawesome/free-solid-svg-icons" //import { fa } from '@fortawesome/free-regular-svg-icons' import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome" // Add Free Font Awesome Icons here // https://fontawesome.com/v5.15/icons?d=gallery&p=2&s=solid&m=free -library.add(faCog, faTachometerAlt, faEdit, faPlus, faPause, faPlay, faTrash, faList) +library.add(faCog, faTachometerAlt, faEdit, faPlus, faPause, faPlay, faTrash, faList, faSignInAlt, faSignOutAlt) export { FontAwesomeIcon } diff --git a/src/layouts/Layout.vue b/src/layouts/Layout.vue index 8e72550..2f5b1cb 100644 --- a/src/layouts/Layout.vue +++ b/src/layouts/Layout.vue @@ -23,6 +23,16 @@ Settings + + diff --git a/src/main.js b/src/main.js index 6eb296d..8621b89 100644 --- a/src/main.js +++ b/src/main.js @@ -13,6 +13,7 @@ import Dashboard from "./pages/Dashboard.vue"; import DashboardHome from "./pages/DashboardHome.vue"; import Details from "./pages/Details.vue"; import EditMonitor from "./pages/EditMonitor.vue"; +import Login from "./pages/Login.vue"; import Settings from "./pages/Settings.vue"; import Setup from "./pages/Setup.vue"; @@ -57,9 +58,11 @@ const routes = [ }, ], }, - + { + path: "/login", + component: Login, + }, ], - }, { path: "/setup", diff --git a/src/pages/Login.vue b/src/pages/Login.vue new file mode 100644 index 0000000..4b08de0 --- /dev/null +++ b/src/pages/Login.vue @@ -0,0 +1,78 @@ + + + + + diff --git a/src/pages/Settings.vue b/src/pages/Settings.vue index d1d3599..d2695ed 100644 --- a/src/pages/Settings.vue +++ b/src/pages/Settings.vue @@ -53,12 +53,6 @@ - -
- -
From 403202d4d424c2fb6c8d3441a11478e82ae0b65e Mon Sep 17 00:00:00 2001 From: Ponkhy Date: Thu, 9 Sep 2021 21:10:31 +0200 Subject: [PATCH 002/189] Added simple TOTP Two Factor Authentication --- db/patch12.sql | 10 ++ package.json | 4 + server/server.js | 168 +++++++++++++++++++++++++++++-- server/util-server.js | 10 ++ src/components/Login.vue | 25 ++++- src/components/TwoFADialog.vue | 178 +++++++++++++++++++++++++++++++++ src/languages/de-DE.js | 13 +++ src/languages/en.js | 15 ++- src/mixins/socket.js | 26 ++++- src/pages/Settings.vue | 11 ++ src/pages/Setup.vue | 2 +- 11 files changed, 447 insertions(+), 15 deletions(-) create mode 100644 db/patch12.sql create mode 100644 src/components/TwoFADialog.vue diff --git a/db/patch12.sql b/db/patch12.sql new file mode 100644 index 0000000..754ffdf --- /dev/null +++ b/db/patch12.sql @@ -0,0 +1,10 @@ +-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. +BEGIN TRANSACTION; + +ALTER TABLE user + ADD twofa_secret VARCHAR(64); + +ALTER TABLE user + ADD twofa_status BOOLEAN default 0; + +COMMIT; diff --git a/package.json b/package.json index c04be03..e43e009 100644 --- a/package.json +++ b/package.json @@ -56,20 +56,24 @@ "http-graceful-shutdown": "^3.1.4", "jsonwebtoken": "^8.5.1", "nodemailer": "^6.6.3", + "notp": "^2.0.3", "password-hash": "^1.2.2", "prom-client": "^13.2.0", "prometheus-api-metrics": "^3.2.0", + "qrcode": "^1.4.4", "redbean-node": "0.1.2", "socket.io": "^4.2.0", "socket.io-client": "^4.2.0", "sqlite3": "github:mapbox/node-sqlite3#593c9d", "tcp-ping": "^0.1.1", + "thirty-two": "^1.0.2", "v-pagination-3": "^0.1.6", "vue": "^3.2.8", "vue-chart-3": "^0.5.7", "vue-confirm-dialog": "^1.0.2", "vue-i18n": "^9.1.7", "vue-multiselect": "^3.0.0-alpha.2", + "vue-qrcode": "^1.0.0", "vue-router": "^4.0.11", "vue-toastification": "^2.0.0-rc.1" }, diff --git a/server/server.js b/server/server.js index 2949c4b..9319fa9 100644 --- a/server/server.js +++ b/server/server.js @@ -22,11 +22,15 @@ const gracefulShutdown = require("http-graceful-shutdown"); debug("Importing prometheus-api-metrics"); const prometheusAPIMetrics = require("prometheus-api-metrics"); +debug("2FA Modules"); +const notp = require("notp"); +const base32 = require("thirty-two"); + console.log("Importing this project modules"); debug("Importing Monitor"); const Monitor = require("./model/monitor"); debug("Importing Settings"); -const { getSettings, setSettings, setting, initJWTSecret } = require("./util-server"); +const { getSettings, setSettings, setting, initJWTSecret, genSecret } = require("./util-server"); debug("Importing Notification"); const { Notification } = require("./notification"); @@ -219,12 +223,38 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString(); if (user) { afterLogin(socket, user) - callback({ - ok: true, - token: jwt.sign({ - username: data.username, - }, jwtSecret), - }) + if (user.twofaStatus == 0) { + callback({ + ok: true, + token: jwt.sign({ + username: data.username, + }, jwtSecret), + }) + } + + if (user.twofaStatus == 1 && !data.token) { + callback({ + tokenRequired: true, + }) + } + + if (data.token) { + let verify = notp.totp.verify(data.token, user.twofa_secret); + + if (verify && verify.delta == 0) { + callback({ + ok: true, + token: jwt.sign({ + username: data.username, + }, jwtSecret), + }) + } else { + callback({ + ok: false, + msg: "Token Invalid!", + }) + } + } } else { callback({ ok: false, @@ -240,6 +270,130 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString(); callback(); }); + socket.on("prepare2FA", async (callback) => { + try { + checkLogin(socket) + + let user = await R.findOne("user", " id = ? AND active = 1 ", [ + socket.userID, + ]) + + if (user.twofa_status == 0) { + let newSecret = await genSecret() + let encodedSecret = base32.encode(newSecret); + let uri = `otpauth://totp/UptimeKuma:${user.username}?secret=${encodedSecret}`; + + await R.exec("UPDATE `user` SET twofa_secret = ? WHERE id = ? ", [ + newSecret, + socket.userID, + ]); + + callback({ + ok: true, + uri: uri, + }) + } else { + callback({ + ok: false, + msg: "2FA is already enabled.", + }) + } + } catch (error) { + callback({ + ok: false, + msg: "Error while trying to prepare 2FA.", + }) + } + }); + + socket.on("save2FA", async (callback) => { + try { + checkLogin(socket) + + await R.exec("UPDATE `user` SET twofa_status = 1 WHERE id = ? ", [ + socket.userID, + ]); + + callback({ + ok: true, + msg: "2FA Enabled.", + }) + } catch (error) { + callback({ + ok: false, + msg: "Error while trying to change 2FA.", + }) + } + }); + + socket.on("disable2FA", async (callback) => { + try { + checkLogin(socket) + + await R.exec("UPDATE `user` SET twofa_status = 0 WHERE id = ? ", [ + socket.userID, + ]); + + callback({ + ok: true, + msg: "2FA Disabled.", + }) + } catch (error) { + callback({ + ok: false, + msg: "Error while trying to change 2FA.", + }) + } + }); + + socket.on("verifyToken", async (token, callback) => { + let user = await R.findOne("user", " id = ? AND active = 1 ", [ + socket.userID, + ]) + + let verify = notp.totp.verify(token, user.twofa_secret); + + if (verify && verify.delta == 0) { + callback({ + ok: true, + valid: true, + }) + } else { + callback({ + ok: false, + msg: "Token Invalid.", + valid: false, + }) + } + }); + + socket.on("twoFAStatus", async (callback) => { + checkLogin(socket) + + try { + let user = await R.findOne("user", " id = ? AND active = 1 ", [ + socket.userID, + ]) + + if (user.twofa_status == 1) { + callback({ + ok: true, + status: true, + }) + } else { + callback({ + ok: true, + status: false, + }) + } + } catch (error) { + callback({ + ok: false, + msg: "Error while trying to get 2FA status.", + }) + } + }); + socket.on("needSetup", async (callback) => { callback(needSetup); }); diff --git a/server/util-server.js b/server/util-server.js index a2fef06..079bd82 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -271,3 +271,13 @@ exports.getTotalClientInRoom = (io, roomName) => { return 0; } } + +exports.genSecret = () => { + let secret = ""; + let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + let charsLength = chars.length; + for ( let i = 0; i < 64; i++ ) { + secret += chars.charAt(Math.floor(Math.random() * charsLength)); + } + return secret; +} diff --git a/src/components/Login.vue b/src/components/Login.vue index bd51759..ca36fdb 100644 --- a/src/components/Login.vue +++ b/src/components/Login.vue @@ -4,16 +4,23 @@

-
+
-
+
+
+
+ + +
+
+
@@ -42,16 +49,24 @@ export default { processing: false, username: "", password: "", - + token: "", res: null, + tokenRequired: false, } }, methods: { submit() { this.processing = true; - this.$root.login(this.username, this.password, (res) => { + + this.$root.login(this.username, this.password, this.token, (res) => { this.processing = false; - this.res = res; + console.log(res) + + if (res.tokenRequired) { + this.tokenRequired = true; + } else { + this.res = res; + } }) }, }, diff --git a/src/components/TwoFADialog.vue b/src/components/TwoFADialog.vue new file mode 100644 index 0000000..371462b --- /dev/null +++ b/src/components/TwoFADialog.vue @@ -0,0 +1,178 @@ + + + + + diff --git a/src/languages/de-DE.js b/src/languages/de-DE.js index cfadf17..b6bc676 100644 --- a/src/languages/de-DE.js +++ b/src/languages/de-DE.js @@ -128,4 +128,17 @@ export default { backupDescription3: "Sensible Daten wie Benachrichtigungstoken sind in der Exportdatei enthalten, bitte bewahre sie sorgfältig auf.", alertNoFile: "Bitte wähle eine Datei zum importieren aus.", alertWrongFileType: "Bitte wähle eine JSON Datei aus.", + twoFAVerifyLabel: "Bitte trage deinen Token ein um zu verifizieren das 2FA funktioniert", + "Verify Token": "Token verifizieren", + "Setup 2FA": "2FA Einrichten", + "Enable 2FA": "2FA Aktivieren", + "Disable 2FA": "2FA deaktivieren", + "2FA Settings": "2FA Einstellungen", + confirmEnableTwoFAMsg: "Bist du sicher das du 2FA aktivieren möchtest?", + confirmDisableTwoFAMsg: "Bist du sicher das du 2FA deaktivieren möchtest?", + tokenValidSettingsMsg: "Token gültig! Du kannst jetzt die 2FA Einstellungen speichern.", + "Two Factor Authentication": "Zwei Faktor Authentifizierung", + Active: "Aktiv", + Inactive: "Inaktiv", + Token: "Token", } diff --git a/src/languages/en.js b/src/languages/en.js index 1272bf3..c7facb2 100644 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -20,6 +20,10 @@ export default { clearEventsMsg: "Are you sure want to delete all events for this monitor?", clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?", confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", + twoFAVerifyLabel: "Please type in your token to verify that 2FA is working", + tokenValidSettingsMsg: "Token valid! You can now save the 2FA settings.", + confirmEnableTwoFAMsg: "Are you sure you want to enable 2FA?", + confirmDisableTwoFAMsg: "Are you sure you want to disable 2FA?", Settings: "Settings", Dashboard: "Dashboard", "New Update": "New Update", @@ -127,5 +131,14 @@ export default { backupDescription2: "PS: History and event data is not included.", backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.", alertNoFile: "Please select a file to import.", - alertWrongFileType: "Please select a JSON file." + alertWrongFileType: "Please select a JSON file.", + "Verify Token": "Verify Token", + "Setup 2FA": "Setup 2FA", + "Enable 2FA": "Enable 2FA", + "Disable 2FA": "Disable 2FA", + "2FA Settings": "2FA Settings", + "Two Factor Authentication": "Two Factor Authentication", + Active: "Active", + Inactive: "Inactive", + Token: "Token", } diff --git a/src/mixins/socket.js b/src/mixins/socket.js index 22cc25b..0cffbdc 100644 --- a/src/mixins/socket.js +++ b/src/mixins/socket.js @@ -201,11 +201,15 @@ export default { } }, - login(username, password, callback) { + login(username, password, token, callback) { socket.emit("login", { username, password, + token, }, (res) => { + if (res.tokenRequired) { + callback(res) + } if (res.ok) { this.storage().token = res.token; @@ -240,6 +244,26 @@ export default { this.clearData() }, + prepare2FA(callback) { + socket.emit("prepare2FA", callback) + }, + + save2FA(secret, callback) { + socket.emit("save2FA", callback) + }, + + disable2FA(callback) { + socket.emit("disable2FA", callback) + }, + + verifyToken(token, callback) { + socket.emit("verifyToken", token, callback) + }, + + twoFAStatus(callback) { + socket.emit("twoFAStatus", callback) + }, + add(monitor, callback) { socket.emit("add", monitor, callback) }, diff --git a/src/pages/Settings.vue b/src/pages/Settings.vue index 17c2630..33992a4 100644 --- a/src/pages/Settings.vue +++ b/src/pages/Settings.vue @@ -120,6 +120,14 @@ +

+ {{ $t("Two Factor Authentication") }} +

+ +
+ +
+

{{ $t("Import/Export Backup") }}

@@ -186,6 +194,7 @@ +

From c3c576bd13b6aa7a6aa2e41e38b54ba2790944c1 Mon Sep 17 00:00:00 2001 From: Ponkhy Date: Fri, 10 Sep 2021 01:33:26 +0200 Subject: [PATCH 007/189] Added "Show URI" translation --- src/components/TwoFADialog.vue | 2 +- src/languages/de-DE.js | 1 + src/languages/en.js | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/TwoFADialog.vue b/src/components/TwoFADialog.vue index 158d1f0..b7b9668 100644 --- a/src/components/TwoFADialog.vue +++ b/src/components/TwoFADialog.vue @@ -15,7 +15,7 @@
- +

{{ uri }}

diff --git a/src/languages/de-DE.js b/src/languages/de-DE.js index b6bc676..7f46e77 100644 --- a/src/languages/de-DE.js +++ b/src/languages/de-DE.js @@ -141,4 +141,5 @@ export default { Active: "Aktiv", Inactive: "Inaktiv", Token: "Token", + "Show URI": "URI Anzeigen" } diff --git a/src/languages/en.js b/src/languages/en.js index c7facb2..520d475 100644 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -141,4 +141,5 @@ export default { Active: "Active", Inactive: "Inactive", Token: "Token", + "Show URI": "Show URI" } From 08de0090dc147f98434ce4790a15566274bc985d Mon Sep 17 00:00:00 2001 From: LouisLam Date: Fri, 10 Sep 2021 17:23:45 +0800 Subject: [PATCH 008/189] add a better approach for patching db, change setting.value from varchar to TEXT, restore Database.close() to 1.2.0 --- ...ch11.sql => patch-improve-performance.sql} | 0 db/patch-setting-value-type.sql | 22 ++ server/database.js | 221 ++++++++++++++++-- 3 files changed, 221 insertions(+), 22 deletions(-) rename db/{patch11.sql => patch-improve-performance.sql} (100%) create mode 100644 db/patch-setting-value-type.sql diff --git a/db/patch11.sql b/db/patch-improve-performance.sql similarity index 100% rename from db/patch11.sql rename to db/patch-improve-performance.sql diff --git a/db/patch-setting-value-type.sql b/db/patch-setting-value-type.sql new file mode 100644 index 0000000..18d6390 --- /dev/null +++ b/db/patch-setting-value-type.sql @@ -0,0 +1,22 @@ +-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. +BEGIN TRANSACTION; + +-- Generated by Intellij IDEA +create table setting_dg_tmp +( + id INTEGER + primary key autoincrement, + key VARCHAR(200) not null + unique, + value TEXT, + type VARCHAR(20) +); + +insert into setting_dg_tmp(id, key, value, type) select id, key, value, type from setting; + +drop table setting; + +alter table setting_dg_tmp rename to setting; + + +COMMIT; diff --git a/server/database.js b/server/database.js index b76b387..e0bb0c9 100644 --- a/server/database.js +++ b/server/database.js @@ -1,15 +1,44 @@ const fs = require("fs"); const { R } = require("redbean-node"); const { setSetting, setting } = require("./util-server"); +const { debug, sleep } = require("../src/util"); +const dayjs = require("dayjs"); class Database { - static templatePath = "./db/kuma.db" + static templatePath = "./db/kuma.db"; static dataDir; static path; + + /** + * @type {boolean} + */ + static patched = false; + + /** + * For Backup only + */ + static backupPath = null; + + /** + * Add patch filename in key + * Values: + * true: Add it regardless of order + * false: Do nothing + * { parents: []}: Need parents before add it + */ + static patchList = { + "patch-setting-value-type.sql": true, + "patch-improve-performance.sql": true, + } + + /** + * The finally version should be 10 after merged tag feature + * @deprecated Use patchList for any new feature + */ static latestVersion = 9; + static noReject = true; - static sqliteInstance = null; static async connect() { const acquireConnectionTimeout = 120 * 1000; @@ -60,19 +89,7 @@ class Database { } else { console.info("Database patch is needed") - console.info("Backup the db") - const backupPath = this.dataDir + "kuma.db.bak" + version; - fs.copyFileSync(Database.path, backupPath); - - const shmPath = Database.path + "-shm"; - if (fs.existsSync(shmPath)) { - fs.copyFileSync(shmPath, shmPath + ".bak" + version); - } - - const walPath = Database.path + "-wal"; - if (fs.existsSync(walPath)) { - fs.copyFileSync(walPath, walPath + ".bak" + version); - } + this.backup(version); // Try catch anything here, if gone wrong, restore the backup try { @@ -83,18 +100,92 @@ class Database { console.info(`Patched ${sqlFile}`); await setSetting("database_version", i); } - console.log("Database Patched Successfully"); } catch (ex) { await Database.close(); - console.error("Patch db failed!!! Restoring the backup") - fs.copyFileSync(backupPath, Database.path); - console.error(ex) + this.restore(); + console.error(ex) console.error("Start Uptime-Kuma failed due to patch db failed") console.error("Please submit the bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues") process.exit(1); } } + + await this.patch2(); + } + + /** + * Call it from patch() only + * @returns {Promise} + */ + static async patch2() { + console.log("Database Patch 2.0 Process"); + let databasePatchedFiles = await setting("databasePatchedFiles"); + + if (! databasePatchedFiles) { + databasePatchedFiles = {}; + } + + debug("Patched files:"); + debug(databasePatchedFiles); + + try { + for (let sqlFilename in this.patchList) { + await this.patch2Recursion(sqlFilename, databasePatchedFiles) + } + + if (this.patched) { + console.log("Database Patched Successfully"); + } + + } catch (ex) { + await Database.close(); + this.restore(); + + console.error(ex) + console.error("Start Uptime-Kuma failed due to patch db failed"); + console.error("Please submit the bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues"); + process.exit(1); + } + + await setSetting("databasePatchedFiles", databasePatchedFiles); + } + + /** + * Used it patch2() only + * @param sqlFilename + * @param databasePatchedFiles + */ + static async patch2Recursion(sqlFilename, databasePatchedFiles) { + let value = this.patchList[sqlFilename]; + + if (! value) { + console.log(sqlFilename + " skip"); + return; + } + + // Check if patched + if (! databasePatchedFiles[sqlFilename]) { + console.log(sqlFilename + " is not patched"); + + if (value.parents) { + console.log(sqlFilename + " need parents"); + for (let parentSQLFilename of value.parents) { + await this.patch2Recursion(parentSQLFilename, databasePatchedFiles); + } + } + + this.backup(dayjs().format("YYYYMMDDHHmmss")); + + console.log(sqlFilename + " is patching"); + this.patched = true; + await this.importSQLFile("./db/" + sqlFilename); + databasePatchedFiles[sqlFilename] = true; + console.log(sqlFilename + " is patched successfully"); + + } else { + console.log(sqlFilename + " is already patched, skip"); + } } /** @@ -140,10 +231,96 @@ class Database { * @returns {Promise} */ static async close() { - if (this.sqliteInstance) { - this.sqliteInstance.close(); + const listener = (reason, p) => { + Database.noReject = false; + }; + process.addListener("unhandledRejection", listener); + + console.log("Closing DB"); + + while (true) { + Database.noReject = true; + await R.close(); + await sleep(2000); + + if (Database.noReject) { + break; + } else { + console.log("Waiting to close the db"); + } + } + console.log("SQLite closed"); + + process.removeListener("unhandledRejection", listener); + } + + /** + * One backup one time in this process. + * Reset this.backupPath if you want to backup again + * @param version + */ + static backup(version) { + if (! this.backupPath) { + console.info("Backup the db") + this.backupPath = this.dataDir + "kuma.db.bak" + version; + fs.copyFileSync(Database.path, this.backupPath); + + const shmPath = Database.path + "-shm"; + if (fs.existsSync(shmPath)) { + this.backupShmPath = shmPath + ".bak" + version; + fs.copyFileSync(shmPath, this.backupShmPath); + } + + const walPath = Database.path + "-wal"; + if (fs.existsSync(walPath)) { + this.backupWalPath = walPath + ".bak" + version; + fs.copyFileSync(walPath, this.backupWalPath); + } + } + } + + /** + * + */ + static restore() { + if (this.backupPath) { + console.error("Patch db failed!!! Restoring the backup"); + + const shmPath = Database.path + "-shm"; + const walPath = Database.path + "-wal"; + + // Delete patch failed db + try { + if (fs.existsSync(Database.path)) { + fs.unlinkSync(Database.path); + } + + if (fs.existsSync(shmPath)) { + fs.unlinkSync(shmPath); + } + + if (fs.existsSync(walPath)) { + fs.unlinkSync(walPath); + } + } catch (e) { + console.log("Restore failed, you may need to restore the backup manually"); + process.exit(1); + } + + // Restore backup + fs.copyFileSync(this.backupPath, Database.path); + + if (this.backupShmPath) { + fs.copyFileSync(this.backupShmPath, shmPath); + } + + if (this.backupWalPath) { + fs.copyFileSync(this.backupWalPath, walPath); + } + + } else { + console.log("Nothing to restore"); } - console.log("Stopped database"); } } From a49df29a8730c930ceccf0986466bc31f6d03b63 Mon Sep 17 00:00:00 2001 From: LouisLam Date: Fri, 10 Sep 2021 17:56:33 +0800 Subject: [PATCH 009/189] update to 1.6.0 --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index c04be03..95180dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uptime-kuma", - "version": "1.5.3", + "version": "1.6.0", "license": "MIT", "repository": { "type": "git", @@ -19,11 +19,11 @@ "build": "vite build", "vite-preview-dist": "vite preview --host", "build-docker": "npm run build-docker-alpine && npm run build-docker-debian", - "build-docker-alpine": "docker buildx build -f dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:1.5.3-alpine --target release . --push", - "build-docker-debian": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.5.3 -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:1.5.3-debian --target release . --push", + "build-docker-alpine": "docker buildx build -f dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:1.6.0-alpine --target release . --push", + "build-docker-debian": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.6.0 -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:1.6.0-debian --target release . --push", "build-docker-nightly": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push", "build-docker-nightly-amd64": "docker buildx build --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain", - "setup": "git checkout 1.5.3 && npm install --legacy-peer-deps && node node_modules/esbuild/install.js && npm run build && npm prune", + "setup": "git checkout 1.6.0 && npm install --legacy-peer-deps && node node_modules/esbuild/install.js && npm run build && npm prune", "update-version": "node extra/update-version.js", "mark-as-nightly": "node extra/mark-as-nightly.js", "reset-password": "node extra/reset-password.js", From 951ece8a65f4c5559c3db823ea3bc329fc86e4fd Mon Sep 17 00:00:00 2001 From: Paride Barison Date: Fri, 10 Sep 2021 12:07:54 +0200 Subject: [PATCH 010/189] updated --- src/languages/it-IT.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/languages/it-IT.js b/src/languages/it-IT.js index 1d33781..ad2804f 100644 --- a/src/languages/it-IT.js +++ b/src/languages/it-IT.js @@ -118,14 +118,14 @@ export default { Heartbeats: "Controlli", "Auto Get": "Auto Get", enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.", - "Import/Export Backup": "Import/Export Backup", - Export: "Export", - Import: "Import", - "Default enabled": "Default enabled", - "Also apply to existing monitors": "Also apply to existing monitors", - backupDescription: "You can backup all monitors and all notifications into a JSON file.", - backupDescription2: "PS: History and event data is not included.", - backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.", - alertNoFile: "Please select a file to import.", - alertWrongFileType: "Please select a JSON file." + "Import/Export Backup": "Importa/Esporta Backup", + Export: "Esporta", + Import: "Importa", + "Default enabled": "Abilitato di default", + "Also apply to existing monitors": "Applica anche ai monitoraggi esistenti", + backupDescription: "È possibile fare il backup di tutti i monitoraggi e di tutte le notifiche in un file JSON.", + backupDescription2: "P.S.: lo storico e i dati relativi agli eventi non saranno inclusi.", + backupDescription3: "Dati sensibili come i token di autenticazione saranno inclusi nel backup, tenere quindi in un luogo sicuro.", + alertNoFile: "Selezionare il file da importare.", + alertWrongFileType: "Selezionare un file JSON." } From 31b4a5c33e872395b22c121bdb2fcab4189bccaa Mon Sep 17 00:00:00 2001 From: LouisLam Date: Fri, 10 Sep 2021 18:08:23 +0800 Subject: [PATCH 011/189] build-docker, debian first --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 95180dd..e1c405c 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "start-server": "node server/server.js", "build": "vite build", "vite-preview-dist": "vite preview --host", - "build-docker": "npm run build-docker-alpine && npm run build-docker-debian", + "build-docker": "npm run build-docker-debian && npm run build-docker-alpine", "build-docker-alpine": "docker buildx build -f dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:1.6.0-alpine --target release . --push", "build-docker-debian": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.6.0 -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:1.6.0-debian --target release . --push", "build-docker-nightly": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push", From 201bba63d76033cfc04fe6643076e1b3465428ba Mon Sep 17 00:00:00 2001 From: LouisLam Date: Fri, 10 Sep 2021 21:17:20 +0800 Subject: [PATCH 012/189] add a healthcheck comment --- extra/healthcheck.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/extra/healthcheck.js b/extra/healthcheck.js index ba3569d..99f748f 100644 --- a/extra/healthcheck.js +++ b/extra/healthcheck.js @@ -1,3 +1,6 @@ +/* + * This script should be run after a period of time (180s), because the server may need some time to prepare. + */ process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; let client; From 22a7acbaf6d9cf7b70fbd101082a9612575d7950 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Fri, 10 Sep 2021 22:03:28 +0800 Subject: [PATCH 013/189] Update README.md --- kubernetes/README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kubernetes/README.md b/kubernetes/README.md index 26ab2f6..3057a53 100644 --- a/kubernetes/README.md +++ b/kubernetes/README.md @@ -1,4 +1,7 @@ # Uptime-Kuma K8s Deployment + +⚠ Warning: K8s deployment is provided by contributors. I have no experience with K8s and I can't fix error in the future. I only test Docker and Node.js. Use at your own risk. + ## How does it work? Kustomize is a tool which builds a complete deployment file for all config elements. @@ -25,4 +28,4 @@ This ingressroute.yml is for the [nginx-ingress-controller](https://kubernetes.g - run ```kustomize build > apply.yml``` - run ```kubectl apply -f apply.yml``` -Now you should see some k8s magic and Uptime-Kuma should be available at the specified address. \ No newline at end of file +Now you should see some k8s magic and Uptime-Kuma should be available at the specified address. From 83fc844e8eaf59011eaabc8f617123c67416cc3b Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Fri, 10 Sep 2021 22:32:11 +0800 Subject: [PATCH 014/189] Update deployment.yml --- kubernetes/uptime-kuma/deployment.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kubernetes/uptime-kuma/deployment.yml b/kubernetes/uptime-kuma/deployment.yml index a122509..b97ece2 100644 --- a/kubernetes/uptime-kuma/deployment.yml +++ b/kubernetes/uptime-kuma/deployment.yml @@ -30,6 +30,9 @@ spec: command: - node - extra/healthcheck.js + initialDelaySeconds: 180 + periodSeconds: 60 + timeoutSeconds: 30 readinessProbe: httpGet: path: / From ef1604675b33ff570fa5b407fb9f03433fbc323e Mon Sep 17 00:00:00 2001 From: LouisLam Date: Sat, 11 Sep 2021 02:50:29 +0800 Subject: [PATCH 015/189] change to node:14-bullseye-slim, and reduce the docker image size --- dockerfile | 38 ++++++++++++++++++++++---------------- dockerfile-alpine | 17 ++++++++++++----- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/dockerfile b/dockerfile index ddb5f4e..6622def 100644 --- a/dockerfile +++ b/dockerfile @@ -1,26 +1,32 @@ -FROM node:14-bullseye-slim AS release +# DON'T UPDATE TO node:14-bullseye-slim, see #372. +FROM node:14-buster-slim AS build WORKDIR /app -# install dependencies -RUN apt update && apt --yes install python3 python3-pip python3-dev git g++ make iputils-ping -RUN ln -s /usr/bin/python3 /usr/bin/python - # split the sqlite install here, so that it can caches the arm prebuilt -RUN npm install mapbox/node-sqlite3#593c9d +# do not modify it, since we don't want to re-compile the arm prebuilt again +RUN apt update && \ + apt --yes install python3 python3-pip python3-dev git g++ make && \ + ln -s /usr/bin/python3 /usr/bin/python && \ + npm install mapbox/node-sqlite3#593c9d --build-from-source -# Install apprise -RUN apt --yes install python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib -RUN pip3 --no-cache-dir install apprise && \ - rm -rf /root/.cache +COPY . . +RUN npm install --legacy-peer-deps && npm run build && npm prune --production -# additional package should be added here, since we don't want to re-compile the arm prebuilt again +FROM node:14-bullseye-slim AS release +WORKDIR /app +# Install Apprise, # add sqlite3 cli for debugging in the future -RUN apt --yes install sqlite3 - - -COPY . . -RUN npm install --legacy-peer-deps && npm run build && npm prune +# iputils-ping for ping +RUN apt update && \ + apt --yes install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \ + sqlite3 \ + iputils-ping && \ + pip3 --no-cache-dir install apprise && \ + rm -rf /var/lib/apt/lists/* + +# Copy app files from build layer +COPY --from=build /app /app EXPOSE 3001 VOLUME ["/app/data"] diff --git a/dockerfile-alpine b/dockerfile-alpine index c8bead8..9982046 100644 --- a/dockerfile-alpine +++ b/dockerfile-alpine @@ -1,5 +1,5 @@ # DON'T UPDATE TO alpine3.13, 1.14, see #41. -FROM node:14-alpine3.12 AS release +FROM node:14-alpine3.12 AS build WORKDIR /app # split the sqlite install here, so that it can caches the arm prebuilt @@ -9,13 +9,20 @@ RUN apk add --no-cache --virtual .build-deps make g++ python3 python3-dev git && apk del .build-deps && \ rm -f /usr/bin/python +COPY . . +RUN npm install --legacy-peer-deps && npm run build && npm prune --production + + +FROM node:14-alpine3.12 AS release +WORKDIR /app + # Install apprise -RUN apk add --no-cache python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib -RUN pip3 --no-cache-dir install apprise && \ +RUN apk add --no-cache python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib && \ + pip3 --no-cache-dir install apprise && \ rm -rf /root/.cache -COPY . . -RUN npm install --legacy-peer-deps && npm run build && npm prune +# Copy app files from build layer +COPY --from=build /app /app EXPOSE 3001 VOLUME ["/app/data"] From d953ba7c60ab6da17358308d56b8ada93a8b7004 Mon Sep 17 00:00:00 2001 From: LouisLam Date: Sat, 11 Sep 2021 03:12:25 +0800 Subject: [PATCH 016/189] update to 1.6.1 --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index e1c405c..342a73c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uptime-kuma", - "version": "1.6.0", + "version": "1.6.1", "license": "MIT", "repository": { "type": "git", @@ -19,11 +19,11 @@ "build": "vite build", "vite-preview-dist": "vite preview --host", "build-docker": "npm run build-docker-debian && npm run build-docker-alpine", - "build-docker-alpine": "docker buildx build -f dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:1.6.0-alpine --target release . --push", - "build-docker-debian": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.6.0 -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:1.6.0-debian --target release . --push", + "build-docker-alpine": "docker buildx build -f dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:1.6.1-alpine --target release . --push", + "build-docker-debian": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.6.1 -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:1.6.1-debian --target release . --push", "build-docker-nightly": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push", "build-docker-nightly-amd64": "docker buildx build --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain", - "setup": "git checkout 1.6.0 && npm install --legacy-peer-deps && node node_modules/esbuild/install.js && npm run build && npm prune", + "setup": "git checkout 1.6.1 && npm install --legacy-peer-deps && node node_modules/esbuild/install.js && npm run build && npm prune", "update-version": "node extra/update-version.js", "mark-as-nightly": "node extra/mark-as-nightly.js", "reset-password": "node extra/reset-password.js", From 14b7688b70445392aa4a341705d13fc1e1a4a4f5 Mon Sep 17 00:00:00 2001 From: Ponkhy Date: Sat, 11 Sep 2021 00:13:05 +0200 Subject: [PATCH 017/189] Added NOT NULL to twofa_status --- db/patch12.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/patch12.sql b/db/patch12.sql index 754ffdf..35069d8 100644 --- a/db/patch12.sql +++ b/db/patch12.sql @@ -5,6 +5,6 @@ ALTER TABLE user ADD twofa_secret VARCHAR(64); ALTER TABLE user - ADD twofa_status BOOLEAN default 0; + ADD twofa_status BOOLEAN default 0 NOT NULL; COMMIT; From e21a18a593436c74f95eb81e4fb4efecf2e3ccfe Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sat, 11 Sep 2021 13:41:22 +0800 Subject: [PATCH 018/189] Update ask-for-help.md --- .github/ISSUE_TEMPLATE/ask-for-help.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/ask-for-help.md b/.github/ISSUE_TEMPLATE/ask-for-help.md index a89d942..f1ea1ac 100644 --- a/.github/ISSUE_TEMPLATE/ask-for-help.md +++ b/.github/ISSUE_TEMPLATE/ask-for-help.md @@ -12,6 +12,8 @@ Please search in Issues without filters: https://github.com/louislam/uptime-kuma **Info** Uptime Kuma Version: Using Docker?: Yes/No +Docker Version: +Node.js Version (Without Docker only): OS: Browser: From f8322de5dae3b666cd94d3e0acb080982115e567 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sat, 11 Sep 2021 13:44:06 +0800 Subject: [PATCH 019/189] Update bug_report.md --- .github/ISSUE_TEMPLATE/bug_report.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index da89d15..158b3e7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -25,10 +25,13 @@ A clear and concise description of what you expected to happen. **Info** -- Uptime Kuma Version: -- Using Docker?: Yes/No -- OS: -- Browser: +Uptime Kuma Version: +Using Docker?: Yes/No +Docker Version: +Node.js Version (Without Docker only): +OS: +Browser: + **Screenshots** If applicable, add screenshots to help explain your problem. @@ -36,3 +39,6 @@ If applicable, add screenshots to help explain your problem. **Error Log** It is easier for us to find out the problem. +Docker: "docker logs " +PM2: "~/.pm2/logs/" (e.g. /home/ubuntu/.pm2/logs) + From 2da77d84489e133b895a2a4a6b494013346e8d25 Mon Sep 17 00:00:00 2001 From: LouisLam Date: Sat, 11 Sep 2021 14:37:05 +0800 Subject: [PATCH 020/189] fix ipv6 connection problem --- src/mixins/socket.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/mixins/socket.js b/src/mixins/socket.js index 22cc25b..9771db0 100644 --- a/src/mixins/socket.js +++ b/src/mixins/socket.js @@ -32,12 +32,14 @@ export default { created() { window.addEventListener("resize", this.onResize); + let protocol = (location.protocol === "https:") ? "wss://" : "ws://"; + let wsHost; const env = process.env.NODE_ENV || "production"; if (env === "development" || localStorage.dev === "dev") { - wsHost = ":3001" + wsHost = protocol + location.hostname + ":3001"; } else { - wsHost = "" + wsHost = protocol + location.host; } socket = io(wsHost, { From 0969be5981b3672d35f4fb9d230d4bc1a3f0fd74 Mon Sep 17 00:00:00 2001 From: LouisLam Date: Sat, 11 Sep 2021 15:08:56 +0800 Subject: [PATCH 021/189] Revert "WIP: Add login page, nav bar buttons" This reverts commit b9a6088f50e1680fbab3c7fa3ef4a03cfaf3caca. --- src/icon.js | 4 +-- src/layouts/Layout.vue | 10 ------ src/main.js | 7 ++-- src/pages/Login.vue | 78 ------------------------------------------ src/pages/Settings.vue | 6 ++++ 5 files changed, 10 insertions(+), 95 deletions(-) delete mode 100644 src/pages/Login.vue diff --git a/src/icon.js b/src/icon.js index 7911e9c..d8ea36d 100644 --- a/src/icon.js +++ b/src/icon.js @@ -1,10 +1,10 @@ import { library } from "@fortawesome/fontawesome-svg-core" -import { faCog, faEdit, faList, faPause, faPlay, faPlus, faSignInAlt, faSignOutAlt, faTachometerAlt, faTrash } from "@fortawesome/free-solid-svg-icons" +import { faCog, faEdit, faList, faPause, faPlay, faPlus, faTachometerAlt, faTrash } from "@fortawesome/free-solid-svg-icons" //import { fa } from '@fortawesome/free-regular-svg-icons' import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome" // Add Free Font Awesome Icons here // https://fontawesome.com/v5.15/icons?d=gallery&p=2&s=solid&m=free -library.add(faCog, faTachometerAlt, faEdit, faPlus, faPause, faPlay, faTrash, faList, faSignInAlt, faSignOutAlt) +library.add(faCog, faTachometerAlt, faEdit, faPlus, faPause, faPlay, faTrash, faList) export { FontAwesomeIcon } diff --git a/src/layouts/Layout.vue b/src/layouts/Layout.vue index 2f5b1cb..8e72550 100644 --- a/src/layouts/Layout.vue +++ b/src/layouts/Layout.vue @@ -23,16 +23,6 @@ Settings - - diff --git a/src/main.js b/src/main.js index 8621b89..6eb296d 100644 --- a/src/main.js +++ b/src/main.js @@ -13,7 +13,6 @@ import Dashboard from "./pages/Dashboard.vue"; import DashboardHome from "./pages/DashboardHome.vue"; import Details from "./pages/Details.vue"; import EditMonitor from "./pages/EditMonitor.vue"; -import Login from "./pages/Login.vue"; import Settings from "./pages/Settings.vue"; import Setup from "./pages/Setup.vue"; @@ -58,11 +57,9 @@ const routes = [ }, ], }, - { - path: "/login", - component: Login, - }, + ], + }, { path: "/setup", diff --git a/src/pages/Login.vue b/src/pages/Login.vue deleted file mode 100644 index 4b08de0..0000000 --- a/src/pages/Login.vue +++ /dev/null @@ -1,78 +0,0 @@ - - - - - diff --git a/src/pages/Settings.vue b/src/pages/Settings.vue index d2695ed..d1d3599 100644 --- a/src/pages/Settings.vue +++ b/src/pages/Settings.vue @@ -53,6 +53,12 @@
+ +
+ +
From 4b0a8087a21188eb2973ea0f0ef5d98ef9608632 Mon Sep 17 00:00:00 2001 From: LouisLam Date: Sat, 11 Sep 2021 16:22:30 +0800 Subject: [PATCH 022/189] do not connect to socket io for status page --- src/main.js | 5 + src/mixins/socket.js | 287 +++++++++++++++++++++------------------ src/pages/StatusPage.vue | 36 +++++ 3 files changed, 199 insertions(+), 129 deletions(-) create mode 100644 src/pages/StatusPage.vue diff --git a/src/main.js b/src/main.js index 21793fa..a4129db 100644 --- a/src/main.js +++ b/src/main.js @@ -20,6 +20,7 @@ import EditMonitor from "./pages/EditMonitor.vue"; import Settings from "./pages/Settings.vue"; import Setup from "./pages/Setup.vue"; import List from "./pages/List.vue"; +import StatusPage from "./pages/StatusPage.vue"; import { appName } from "./util.ts"; @@ -94,6 +95,10 @@ const routes = [ path: "/setup", component: Setup, }, + { + path: "/status-page", + component: StatusPage, + }, ] const router = createRouter({ diff --git a/src/mixins/socket.js b/src/mixins/socket.js index 9771db0..5ffdf75 100644 --- a/src/mixins/socket.js +++ b/src/mixins/socket.js @@ -4,6 +4,10 @@ const toast = useToast() let socket; +const noSocketIOPage = [ + "/status-page", +]; + export default { data() { @@ -14,6 +18,8 @@ export default { firstConnect: true, connected: false, connectCount: 0, + initedSocketIO: false, + }, remember: (localStorage.remember !== "0"), allowLoginDialog: false, // Allowed to show login dialog, but "loggedIn" have to be true too. This exists because prevent the login dialog show 0.1s in first before the socket server auth-ed. @@ -31,161 +37,176 @@ export default { created() { window.addEventListener("resize", this.onResize); + this.initSocketIO(); + }, - let protocol = (location.protocol === "https:") ? "wss://" : "ws://"; + methods: { - let wsHost; - const env = process.env.NODE_ENV || "production"; - if (env === "development" || localStorage.dev === "dev") { - wsHost = protocol + location.hostname + ":3001"; - } else { - wsHost = protocol + location.host; - } + initSocketIO(bypass = false) { + // No need to re-init + if (this.socket.initedSocketIO) { + return; + } - socket = io(wsHost, { - transports: ["websocket"], - }); - - socket.on("info", (info) => { - this.info = info; - }); - - socket.on("setup", (monitorID, data) => { - this.$router.push("/setup") - }); - - socket.on("autoLogin", (monitorID, data) => { - this.loggedIn = true; - this.storage().token = "autoLogin"; - this.allowLoginDialog = false; - }); - - socket.on("monitorList", (data) => { - // Add Helper function - Object.entries(data).forEach(([monitorID, monitor]) => { - monitor.getUrl = () => { - try { - return new URL(monitor.url); - } catch (_) { - return null; - } - }; - }); - this.monitorList = data; - }); + // No need to connect to the socket.io for status page + if (! bypass && noSocketIOPage.includes(location.pathname)) { + return; + } + + this.socket.initedSocketIO = true; - socket.on("notificationList", (data) => { - this.notificationList = data; - }); + let protocol = (location.protocol === "https:") ? "wss://" : "ws://"; - socket.on("heartbeat", (data) => { - if (! (data.monitorID in this.heartbeatList)) { - this.heartbeatList[data.monitorID] = []; + let wsHost; + const env = process.env.NODE_ENV || "production"; + if (env === "development" || localStorage.dev === "dev") { + wsHost = protocol + location.hostname + ":3001"; + } else { + wsHost = protocol + location.host; } - this.heartbeatList[data.monitorID].push(data) + socket = io(wsHost, { + transports: ["websocket"], + }); - // Add to important list if it is important - // Also toast - if (data.important) { + socket.on("info", (info) => { + this.info = info; + }); - if (data.status === 0) { - toast.error(`[${this.monitorList[data.monitorID].name}] [DOWN] ${data.msg}`, { - timeout: false, - }); - } else if (data.status === 1) { - toast.success(`[${this.monitorList[data.monitorID].name}] [Up] ${data.msg}`, { - timeout: 20000, - }); - } else { - toast(`[${this.monitorList[data.monitorID].name}] ${data.msg}`); - } + socket.on("setup", (monitorID, data) => { + this.$router.push("/setup") + }); - if (! (data.monitorID in this.importantHeartbeatList)) { - this.importantHeartbeatList[data.monitorID] = []; + socket.on("autoLogin", (monitorID, data) => { + this.loggedIn = true; + this.storage().token = "autoLogin"; + this.allowLoginDialog = false; + }); + + socket.on("monitorList", (data) => { + // Add Helper function + Object.entries(data).forEach(([monitorID, monitor]) => { + monitor.getUrl = () => { + try { + return new URL(monitor.url); + } catch (_) { + return null; + } + }; + }); + this.monitorList = data; + }); + + socket.on("notificationList", (data) => { + this.notificationList = data; + }); + + socket.on("heartbeat", (data) => { + if (! (data.monitorID in this.heartbeatList)) { + this.heartbeatList[data.monitorID] = []; } - this.importantHeartbeatList[data.monitorID].unshift(data) - } - }); + this.heartbeatList[data.monitorID].push(data) + + // Add to important list if it is important + // Also toast + if (data.important) { + + if (data.status === 0) { + toast.error(`[${this.monitorList[data.monitorID].name}] [DOWN] ${data.msg}`, { + timeout: false, + }); + } else if (data.status === 1) { + toast.success(`[${this.monitorList[data.monitorID].name}] [Up] ${data.msg}`, { + timeout: 20000, + }); + } else { + toast(`[${this.monitorList[data.monitorID].name}] ${data.msg}`); + } - socket.on("heartbeatList", (monitorID, data, overwrite = false) => { - if (! (monitorID in this.heartbeatList) || overwrite) { - this.heartbeatList[monitorID] = data; - } else { - this.heartbeatList[monitorID] = data.concat(this.heartbeatList[monitorID]) - } - }); + if (! (data.monitorID in this.importantHeartbeatList)) { + this.importantHeartbeatList[data.monitorID] = []; + } - socket.on("avgPing", (monitorID, data) => { - this.avgPingList[monitorID] = data - }); + this.importantHeartbeatList[data.monitorID].unshift(data) + } + }); - socket.on("uptime", (monitorID, type, data) => { - this.uptimeList[`${monitorID}_${type}`] = data - }); + socket.on("heartbeatList", (monitorID, data, overwrite = false) => { + if (! (monitorID in this.heartbeatList) || overwrite) { + this.heartbeatList[monitorID] = data; + } else { + this.heartbeatList[monitorID] = data.concat(this.heartbeatList[monitorID]) + } + }); - socket.on("certInfo", (monitorID, data) => { - this.certInfoList[monitorID] = JSON.parse(data) - }); + socket.on("avgPing", (monitorID, data) => { + this.avgPingList[monitorID] = data + }); - socket.on("importantHeartbeatList", (monitorID, data, overwrite) => { - if (! (monitorID in this.importantHeartbeatList) || overwrite) { - this.importantHeartbeatList[monitorID] = data; - } else { - this.importantHeartbeatList[monitorID] = data.concat(this.importantHeartbeatList[monitorID]) - } - }); - - socket.on("connect_error", (err) => { - console.error(`Failed to connect to the backend. Socket.io connect_error: ${err.message}`); - this.connectionErrorMsg = `Cannot connect to the socket server. [${err}] Reconnecting...`; - this.socket.connected = false; - this.socket.firstConnect = false; - }); - - socket.on("disconnect", () => { - console.log("disconnect") - this.connectionErrorMsg = "Lost connection to the socket server. Reconnecting..."; - this.socket.connected = false; - }); - - socket.on("connect", () => { - console.log("connect") - this.socket.connectCount++; - this.socket.connected = true; - - // Reset Heartbeat list if it is re-connect - if (this.socket.connectCount >= 2) { - this.clearData() - } + socket.on("uptime", (monitorID, type, data) => { + this.uptimeList[`${monitorID}_${type}`] = data + }); - let token = this.storage().token; + socket.on("certInfo", (monitorID, data) => { + this.certInfoList[monitorID] = JSON.parse(data) + }); - if (token) { - if (token !== "autoLogin") { - this.loginByToken(token) + socket.on("importantHeartbeatList", (monitorID, data, overwrite) => { + if (! (monitorID in this.importantHeartbeatList) || overwrite) { + this.importantHeartbeatList[monitorID] = data; } else { + this.importantHeartbeatList[monitorID] = data.concat(this.importantHeartbeatList[monitorID]) + } + }); - // Timeout if it is not actually auto login - setTimeout(() => { - if (! this.loggedIn) { - this.allowLoginDialog = true; - this.$root.storage().removeItem("token"); - } - }, 5000); + socket.on("connect_error", (err) => { + console.error(`Failed to connect to the backend. Socket.io connect_error: ${err.message}`); + this.connectionErrorMsg = `Cannot connect to the socket server. [${err}] Reconnecting...`; + this.socket.connected = false; + this.socket.firstConnect = false; + }); + socket.on("disconnect", () => { + console.log("disconnect") + this.connectionErrorMsg = "Lost connection to the socket server. Reconnecting..."; + this.socket.connected = false; + }); + + socket.on("connect", () => { + console.log("connect") + this.socket.connectCount++; + this.socket.connected = true; + + // Reset Heartbeat list if it is re-connect + if (this.socket.connectCount >= 2) { + this.clearData() } - } else { - this.allowLoginDialog = true; - } - this.socket.firstConnect = false; - }); + let token = this.storage().token; - }, + if (token) { + if (token !== "autoLogin") { + this.loginByToken(token) + } else { - methods: { + // Timeout if it is not actually auto login + setTimeout(() => { + if (! this.loggedIn) { + this.allowLoginDialog = true; + this.$root.storage().removeItem("token"); + } + }, 5000); + + } + } else { + this.allowLoginDialog = true; + } + + this.socket.firstConnect = false; + }); + + }, storage() { return (this.remember) ? localStorage : sessionStorage; @@ -336,6 +357,14 @@ export default { localStorage.remember = (this.remember) ? "1" : "0" }, + // Reconnect the socket io, if status-page to dashboard + "$route.fullPath"(newValue, oldValue) { + if (noSocketIOPage.includes(newValue)) { + return; + } + this.initSocketIO(); + }, + }, } diff --git a/src/pages/StatusPage.vue b/src/pages/StatusPage.vue new file mode 100644 index 0000000..7cb493a --- /dev/null +++ b/src/pages/StatusPage.vue @@ -0,0 +1,36 @@ + + + From c387fb264ea19682c7038a2a5478e2fbecd79d8a Mon Sep 17 00:00:00 2001 From: Adam Stachowicz Date: Sat, 11 Sep 2021 10:50:56 +0200 Subject: [PATCH 023/189] Format JSON file before download --- src/pages/Settings.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Settings.vue b/src/pages/Settings.vue index b91d257..cd1a95b 100644 --- a/src/pages/Settings.vue +++ b/src/pages/Settings.vue @@ -383,7 +383,7 @@ export default { notificationList: this.$root.notificationList, monitorList: monitorList, } - exportData = JSON.stringify(exportData); + exportData = JSON.stringify(exportData, null, 4); let downloadItem = document.createElement("a"); downloadItem.setAttribute("href", "data:application/json;charset=utf-8," + encodeURI(exportData)); downloadItem.setAttribute("download", fileName); From 9e10f7d35f306f81568f6fe9f11036de6afa7b1e Mon Sep 17 00:00:00 2001 From: Nelson Chan Date: Sat, 11 Sep 2021 18:56:27 +0800 Subject: [PATCH 024/189] Fix: Fix events table width on mobile --- src/pages/DashboardHome.vue | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/pages/DashboardHome.vue b/src/pages/DashboardHome.vue index 9f9cda1..186184e 100644 --- a/src/pages/DashboardHome.vue +++ b/src/pages/DashboardHome.vue @@ -26,7 +26,7 @@
-
+
@@ -178,5 +178,10 @@ table { tr { transition: all ease-in-out 0.2ms; } + + @media (max-width: 550px) { + table-layout: fixed; + overflow-wrap: break-word; + } } From b709857e197e680e7bd0fa1e232b15c8248bb890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20S=C3=B8rensen?= Date: Sat, 11 Sep 2021 13:17:17 +0200 Subject: [PATCH 025/189] Better wording for Danish language --- src/languages/da-DK.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/languages/da-DK.js b/src/languages/da-DK.js index 2859655..bbc6af0 100644 --- a/src/languages/da-DK.js +++ b/src/languages/da-DK.js @@ -17,8 +17,8 @@ export default { Down: "Inaktiv", Pending: "Afventer", Unknown: "Ukendt", - Pause: "Pause", - pauseDashboardHome: "Pauset", + Pause: "Stands", + pauseDashboardHome: "Standset", Name: "Navn", Status: "Status", DateTime: "Dato / Tid", @@ -36,7 +36,7 @@ export default { hour: "Timer", "-hour": "-Timer", checkEverySecond: "Tjek hvert {0} sekund", - "Avg.": "Gennemsnit", + "Avg.": "Gns.", Response: " Respons", Ping: "Ping", "Monitor Type": "Overvåger Type", @@ -103,7 +103,7 @@ export default { "Resolver Server": "Navne-server", rrtypeDescription: "Vælg den type RR, du vil overvåge.", "Last Result": "Seneste resultat", - pauseMonitorMsg: "Er du sikker på, at du vil pause Overvågeren?", + pauseMonitorMsg: "Er du sikker på, at du vil standse Overvågeren?", "Create your admin account": "Opret din administratorkonto", "Repeat Password": "Gentag adgangskoden", "Resource Record Type": "Resource Record Type", From 39c7b5e748c37d926ef1362caf3fe116910770d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20S=C3=B8rensen?= Date: Sat, 11 Sep 2021 13:37:53 +0200 Subject: [PATCH 026/189] Added new Strings to Danish translation --- src/languages/da-DK.js | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/languages/da-DK.js b/src/languages/da-DK.js index bbc6af0..f6e257a 100644 --- a/src/languages/da-DK.js +++ b/src/languages/da-DK.js @@ -107,25 +107,25 @@ export default { "Create your admin account": "Opret din administratorkonto", "Repeat Password": "Gentag adgangskoden", "Resource Record Type": "Resource Record Type", - respTime: "Resp. Time (ms)", + respTime: "Resp. Tid (ms)", notAvailableShort: "N/A", - Create: "Create", - clearEventsMsg: "Are you sure want to delete all events for this monitor?", - clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?", - confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", - "Clear Data": "Clear Data", + Create: "Opret", + clearEventsMsg: "Er du sikker på vil slette alle events for denne Overvåger?", + clearHeartbeatsMsg: "Er du sikker på vil slette alle heartbeats for denne Overvåger?", + confirmClearStatisticsMsg: "Vil du helt sikkert slette ALLE statistikker?", + "Clear Data": "Ryd Data", Events: "Events", Heartbeats: "Heartbeats", - "Auto Get": "Auto Get", - enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.", - "Default enabled": "Default enabled", - "Also apply to existing monitors": "Also apply to existing monitors", - "Import/Export Backup": "Import/Export Backup", - Export: "Export", + "Auto Get": "Auto-hent", + enableDefaultNotificationDescription: "For hver ny overvåger aktiveres denne underretning som standard. Du kan stadig deaktivere underretningen separat for hver skærm.", + "Default enabled": "Standard aktiveret", + "Also apply to existing monitors": "Anvend også på eksisterende overvågere", + "Import/Export Backup": " Importér/Eksportér sikkerhedskopi", + Export: "Eksport", Import: "Import", - backupDescription: "You can backup all monitors and all notifications into a JSON file.", - backupDescription2: "PS: History and event data is not included.", - backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.", - alertNoFile: "Please select a file to import.", - alertWrongFileType: "Please select a JSON file." + backupDescription: "Du kan sikkerhedskopiere alle Overvågere og alle underretninger til en JSON-fil.", + backupDescription2: "PS: Historik og hændelsesdata er ikke inkluderet.", + backupDescription3: "Følsom data, f.eks. underretnings-tokener, er inkluderet i eksportfilen. Gem den sikkert.", + alertNoFile: "Vælg en fil der skal importeres.", + alertWrongFileType: "Vælg venligst en JSON-fil." } From 3e25f0e9d9470ef26219a48549704092109f4fd0 Mon Sep 17 00:00:00 2001 From: LouisLam Date: Sat, 11 Sep 2021 19:40:03 +0800 Subject: [PATCH 027/189] [Status Page] WIP: Checkpoint --- server/server.js | 29 ++++++++++-- server/util-server.js | 12 +++++ src/icon.js | 4 +- src/layouts/Layout.vue | 11 +++++ src/mixins/socket.js | 8 ++-- src/mixins/theme.js | 16 +++++-- src/pages/StatusPage.vue | 97 +++++++++++++++++++++++++++++++++++++--- 7 files changed, 159 insertions(+), 18 deletions(-) diff --git a/server/server.js b/server/server.js index 2949c4b..aec4528 100644 --- a/server/server.js +++ b/server/server.js @@ -26,7 +26,7 @@ console.log("Importing this project modules"); debug("Importing Monitor"); const Monitor = require("./model/monitor"); debug("Importing Settings"); -const { getSettings, setSettings, setting, initJWTSecret } = require("./util-server"); +const { getSettings, setSettings, setting, initJWTSecret, allowDevAllOrigin } = require("./util-server"); debug("Importing Notification"); const { Notification } = require("./notification"); @@ -127,7 +127,9 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString(); console.log("Adding route") + // *************************** // Normal Router here + // *************************** // Robots.txt app.get("/robots.txt", async (_request, response) => { @@ -147,7 +149,28 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString(); app.use("/", express.static("dist")); - // Universal Route Handler, must be at the end + // *************************** + // Public API + // *************************** + + // Status Page Config + app.get("/api/status-page/config", async (_request, response) => { + allowDevAllOrigin(response); + let config = getSettings("statusPage"); + + if (! config.statusPageTheme) { + config.statusPageTheme = "light"; + } + + response.json(config); + }); + + // Status Page Polling Data + app.get("/api/status-page", async (_request, response) => { + allowDevAllOrigin(response); + }); + + // Universal Route Handler, must be at the end of all express route. app.get("*", async (_request, response) => { response.send(indexHTML); }); @@ -172,7 +195,7 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString(); }); // *************************** - // Public API + // Public Socket API // *************************** socket.on("loginByToken", async (token, callback) => { diff --git a/server/util-server.js b/server/util-server.js index a2fef06..67bf130 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -5,6 +5,7 @@ const { debug } = require("../src/util"); const passwordHash = require("./password-hash"); const dayjs = require("dayjs"); const { Resolver } = require("dns"); +const { allowAllOrigin } = require("./util-server"); /** * Init or reset JWT secret @@ -271,3 +272,14 @@ exports.getTotalClientInRoom = (io, roomName) => { return 0; } } + +exports.allowDevAllOrigin = (res) => { + if (process.env.NODE_ENV === "development") { + exports.allowAllOrigin(res); + } +} + +exports.allowAllOrigin = (res) => { + res.header("Access-Control-Allow-Origin", "*"); + res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); +} diff --git a/src/icon.js b/src/icon.js index 58583f0..7e7c7dd 100644 --- a/src/icon.js +++ b/src/icon.js @@ -1,10 +1,10 @@ import { library } from "@fortawesome/fontawesome-svg-core" -import { faCog, faEdit, faPlus, faPause, faPlay, faTachometerAlt, faTrash, faList, faArrowAltCircleUp, faEye, faEyeSlash } from "@fortawesome/free-solid-svg-icons" +import { faCog, faEdit, faPlus, faPause, faPlay, faTachometerAlt, faTrash, faList, faArrowAltCircleUp, faEye, faEyeSlash, faCheckCircle, faStream } from "@fortawesome/free-solid-svg-icons" //import { fa } from '@fortawesome/free-regular-svg-icons' import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome" // Add Free Font Awesome Icons here // https://fontawesome.com/v5.15/icons?d=gallery&p=2&s=solid&m=free -library.add(faCog, faEdit, faPlus, faPause, faPlay, faTachometerAlt, faTrash, faList, faArrowAltCircleUp, faEye, faEyeSlash); +library.add(faCog, faEdit, faPlus, faPause, faPlay, faTachometerAlt, faTrash, faList, faArrowAltCircleUp, faEye, faEyeSlash, faCheckCircle, faStream); export { FontAwesomeIcon } diff --git a/src/layouts/Layout.vue b/src/layouts/Layout.vue index 467ae53..b7b8688 100644 --- a/src/layouts/Layout.vue +++ b/src/layouts/Layout.vue @@ -18,6 +18,11 @@