From 595b35fb15141ad739be893e75b61aae0fd53b59 Mon Sep 17 00:00:00 2001 From: Ionys <9364594+Ionys320@users.noreply.github.com> Date: Sat, 2 Nov 2024 13:09:23 +0100 Subject: [PATCH 1/8] fix(tags): Fix the tag deletion (#5298) --- server/model/monitor.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/model/monitor.js b/server/model/monitor.js index 9a30a6689..3ad8cfafc 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -1522,7 +1522,7 @@ class Monitor extends BeanModel { */ static async getMonitorTag(monitorIDs) { return await R.getAll(` - SELECT monitor_tag.monitor_id, monitor_tag.tag_id, tag.name, tag.color + SELECT monitor_tag.monitor_id, monitor_tag.tag_id, monitor_tag.value, tag.name, tag.color FROM monitor_tag JOIN tag ON monitor_tag.tag_id = tag.id WHERE monitor_tag.monitor_id IN (${monitorIDs.map((_) => "?").join(",")}) @@ -1567,6 +1567,8 @@ class Monitor extends BeanModel { } tagsMap.get(row.monitor_id).push({ tag_id: row.tag_id, + monitor_id: row.monitor_id, + value: row.value, name: row.name, color: row.color }); From 5864c6dd88f274413ba9b54cba19bac16ac69e16 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Tue, 5 Nov 2024 20:25:08 +0800 Subject: [PATCH 2/8] Fix: Embedded MariaDB startup issue (#5283) --- server/database.js | 2 +- server/embedded-mariadb.js | 85 ++++++++++++++++++++++++++++---------- 2 files changed, 65 insertions(+), 22 deletions(-) diff --git a/server/database.js b/server/database.js index 3927d6db8..3b7646de8 100644 --- a/server/database.js +++ b/server/database.js @@ -296,7 +296,7 @@ class Database { client: "mysql2", connection: { socketPath: embeddedMariaDB.socketPath, - user: "node", + user: embeddedMariaDB.username, database: "kuma", timezone: "Z", typeCast: function (field, next) { diff --git a/server/embedded-mariadb.js b/server/embedded-mariadb.js index 8aa7134bf..7c9630dd7 100644 --- a/server/embedded-mariadb.js +++ b/server/embedded-mariadb.js @@ -14,9 +14,15 @@ class EmbeddedMariaDB { mariadbDataDir = "/app/data/mariadb"; - runDir = "/app/data/run/mariadb"; + runDir = "/app/data/run"; - socketPath = this.runDir + "/mysqld.sock"; + socketPath = this.runDir + "/mariadb.sock"; + + /** + * The username to connect to the MariaDB + * @type {string} + */ + username = null; /** * @type {ChildProcessWithoutNullStreams} @@ -46,16 +52,42 @@ class EmbeddedMariaDB { /** * Start the embedded MariaDB + * @throws {Error} If the current user is not "node" or "root" * @returns {Promise|void} A promise that resolves when the MariaDB is started or void if it is already started */ start() { + // Check if the current user is "node" or "root" + this.username = require("os").userInfo().username; + if (this.username !== "node" && this.username !== "root") { + throw new Error("Embedded Mariadb supports only 'node' or 'root' user, but the current user is: " + this.username); + } + + this.initDB(); + + this.startChildProcess(); + + return new Promise((resolve) => { + let interval = setInterval(() => { + if (this.started) { + clearInterval(interval); + resolve(); + } else { + log.info("mariadb", "Waiting for Embedded MariaDB to start..."); + } + }, 1000); + }); + } + + /** + * Start the child process + * @returns {void} + */ + startChildProcess() { if (this.childProcess) { log.info("mariadb", "Already started"); return; } - this.initDB(); - this.running = true; log.info("mariadb", "Starting Embedded MariaDB"); this.childProcess = childProcess.spawn(this.exec, [ @@ -63,6 +95,8 @@ class EmbeddedMariaDB { "--datadir=" + this.mariadbDataDir, `--socket=${this.socketPath}`, `--pid-file=${this.runDir}/mysqld.pid`, + // Don't add the following option, the mariadb will not report message to the console, which affects initDBAfterStarted() + // "--log-error=" + `${this.mariadbDataDir}/mariadb-error.log`, ]); this.childProcess.on("close", (code) => { @@ -72,8 +106,8 @@ class EmbeddedMariaDB { log.info("mariadb", "Stopped Embedded MariaDB: " + code); if (code !== 0) { - log.info("mariadb", "Try to restart Embedded MariaDB as it is not stopped by user"); - this.start(); + log.error("mariadb", "Try to restart Embedded MariaDB as it is not stopped by user"); + this.startChildProcess(); } }); @@ -86,7 +120,7 @@ class EmbeddedMariaDB { }); let handler = (data) => { - log.debug("mariadb", data.toString("utf-8")); + log.info("mariadb", data.toString("utf-8")); if (data.toString("utf-8").includes("ready for connections")) { this.initDBAfterStarted(); } @@ -94,17 +128,6 @@ class EmbeddedMariaDB { this.childProcess.stdout.on("data", handler); this.childProcess.stderr.on("data", handler); - - return new Promise((resolve) => { - let interval = setInterval(() => { - if (this.started) { - clearInterval(interval); - resolve(); - } else { - log.info("mariadb", "Waiting for Embedded MariaDB to start..."); - } - }, 1000); - }); } /** @@ -129,9 +152,11 @@ class EmbeddedMariaDB { recursive: true, }); - let result = childProcess.spawnSync("mysql_install_db", [ + let result = childProcess.spawnSync("mariadb-install-db", [ "--user=node", - "--ldata=" + this.mariadbDataDir, + "--auth-root-socket-user=node", + "--datadir=" + this.mariadbDataDir, + "--auth-root-authentication-method=socket", ]); if (result.status !== 0) { @@ -143,6 +168,17 @@ class EmbeddedMariaDB { } } + // Check the owner of the mariadb directory, and change it if necessary + let stat = fs.statSync(this.mariadbDataDir); + if (stat.uid !== 1000 || stat.gid !== 1000) { + fs.chownSync(this.mariadbDataDir, 1000, 1000); + } + + // Check the permission of the mariadb directory, and change it if it is not 755 + if (stat.mode !== 0o755) { + fs.chmodSync(this.mariadbDataDir, 0o755); + } + if (!fs.existsSync(this.runDir)) { log.info("mariadb", `Embedded MariaDB: ${this.runDir} is not found, create one now.`); fs.mkdirSync(this.runDir, { @@ -150,6 +186,13 @@ class EmbeddedMariaDB { }); } + stat = fs.statSync(this.runDir); + if (stat.uid !== 1000 || stat.gid !== 1000) { + fs.chownSync(this.runDir, 1000, 1000); + } + if (stat.mode !== 0o755) { + fs.chmodSync(this.runDir, 0o755); + } } /** @@ -159,7 +202,7 @@ class EmbeddedMariaDB { async initDBAfterStarted() { const connection = mysql.createConnection({ socketPath: this.socketPath, - user: "node", + user: this.username, }); let result = await connection.execute("CREATE DATABASE IF NOT EXISTS `kuma`"); From 5bcde56a0f98735dbe76b1e0c5f70556dfda39b7 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Tue, 5 Nov 2024 20:26:26 +0800 Subject: [PATCH 3/8] Publish docker images to ghcr.io (#5311) --- extra/release/beta.mjs | 15 +++++++-------- extra/release/final.mjs | 14 +++++++------- extra/release/lib.mjs | 23 ++++++++++++++++------- extra/release/nightly.mjs | 8 ++++---- 4 files changed, 34 insertions(+), 26 deletions(-) diff --git a/extra/release/beta.mjs b/extra/release/beta.mjs index ec3be7c68..7ea13b9b4 100644 --- a/extra/release/beta.mjs +++ b/extra/release/beta.mjs @@ -6,14 +6,13 @@ import { checkDocker, checkTagExists, checkVersionFormat, - dryRun, - getRepoName, + getRepoNames, pressAnyKey, execSync, uploadArtifacts, } from "./lib.mjs"; import semver from "semver"; -const repoName = getRepoName(); +const repoNames = getRepoNames(); const version = process.env.RELEASE_BETA_VERSION; const githubToken = process.env.RELEASE_GITHUB_TOKEN; @@ -39,7 +38,7 @@ if (semverIdentifier[0] !== "beta") { checkDocker(); // Check if the tag exists -await checkTagExists(repoName, version); +await checkTagExists(repoNames, version); // node extra/beta/update-version.js execSync("node ./extra/beta/update-version.js"); @@ -48,16 +47,16 @@ execSync("node ./extra/beta/update-version.js"); buildDist(); // Build slim image (rootless) -buildImage(repoName, [ "beta-slim-rootless", ver(version, "slim-rootless") ], "rootless", "BASE_IMAGE=louislam/uptime-kuma:base2-slim"); +buildImage(repoNames, [ "beta-slim-rootless", ver(version, "slim-rootless") ], "rootless", "BASE_IMAGE=louislam/uptime-kuma:base2-slim"); // Build full image (rootless) -buildImage(repoName, [ "beta-rootless", ver(version, "rootless") ], "rootless"); +buildImage(repoNames, [ "beta-rootless", ver(version, "rootless") ], "rootless"); // Build slim image -buildImage(repoName, [ "beta-slim", ver(version, "slim") ], "release", "BASE_IMAGE=louislam/uptime-kuma:base2-slim"); +buildImage(repoNames, [ "beta-slim", ver(version, "slim") ], "release", "BASE_IMAGE=louislam/uptime-kuma:base2-slim"); // Build full image -buildImage(repoName, [ "beta", version ], "release"); +buildImage(repoNames, [ "beta", version ], "release"); await pressAnyKey(); diff --git a/extra/release/final.mjs b/extra/release/final.mjs index b4435064f..bf0ec2b0c 100644 --- a/extra/release/final.mjs +++ b/extra/release/final.mjs @@ -6,11 +6,11 @@ import { checkDocker, checkTagExists, checkVersionFormat, - getRepoName, + getRepoNames, pressAnyKey, execSync, uploadArtifacts } from "./lib.mjs"; -const repoName = getRepoName(); +const repoNames = getRepoNames(); const version = process.env.RELEASE_VERSION; const githubToken = process.env.RELEASE_GITHUB_TOKEN; @@ -28,7 +28,7 @@ checkVersionFormat(version); checkDocker(); // Check if the tag exists -await checkTagExists(repoName, version); +await checkTagExists(repoNames, version); // node extra/beta/update-version.js execSync("node extra/update-version.js"); @@ -37,16 +37,16 @@ execSync("node extra/update-version.js"); buildDist(); // Build slim image (rootless) -buildImage(repoName, [ "2-slim-rootless", ver(version, "slim-rootless") ], "rootless", "BASE_IMAGE=louislam/uptime-kuma:base2-slim"); +buildImage(repoNames, [ "2-slim-rootless", ver(version, "slim-rootless") ], "rootless", "BASE_IMAGE=louislam/uptime-kuma:base2-slim"); // Build full image (rootless) -buildImage(repoName, [ "2-rootless", ver(version, "rootless") ], "rootless"); +buildImage(repoNames, [ "2-rootless", ver(version, "rootless") ], "rootless"); // Build slim image -buildImage(repoName, [ "next-slim", "2-slim", ver(version, "slim") ], "release", "BASE_IMAGE=louislam/uptime-kuma:base2-slim"); +buildImage(repoNames, [ "next-slim", "2-slim", ver(version, "slim") ], "release", "BASE_IMAGE=louislam/uptime-kuma:base2-slim"); // Build full image -buildImage(repoName, [ "next", "2", version ], "release"); +buildImage(repoNames, [ "next", "2", version ], "release"); await pressAnyKey(); diff --git a/extra/release/lib.mjs b/extra/release/lib.mjs index 49391900a..cd06cd370 100644 --- a/extra/release/lib.mjs +++ b/extra/release/lib.mjs @@ -24,8 +24,15 @@ export function checkDocker() { /** * Get Docker Hub repository name */ -export function getRepoName() { - return process.env.RELEASE_REPO_NAME || "louislam/uptime-kuma"; +export function getRepoNames() { + if (process.env.RELEASE_REPO_NAMES) { + // Split by comma + return process.env.RELEASE_REPO_NAMES.split(",").map((name) => name.trim()); + } + return [ + "louislam/uptime-kuma", + "ghcr.io/louislam/uptime-kuma", + ]; } /** @@ -42,7 +49,7 @@ export function buildDist() { /** * Build docker image and push to Docker Hub - * @param {string} repoName Docker Hub repository name + * @param {string[]} repoNames Docker Hub repository names * @param {string[]} tags Docker image tags * @param {string} target Dockerfile's target name * @param {string} buildArgs Docker build args @@ -50,7 +57,7 @@ export function buildDist() { * @param {string} platform Build platform * @returns {void} */ -export function buildImage(repoName, tags, target, buildArgs = "", dockerfile = "docker/dockerfile", platform = "linux/amd64,linux/arm64,linux/arm/v7") { +export function buildImage(repoNames, tags, target, buildArgs = "", dockerfile = "docker/dockerfile", platform = "linux/amd64,linux/arm64,linux/arm/v7") { let args = [ "buildx", "build", @@ -60,9 +67,11 @@ export function buildImage(repoName, tags, target, buildArgs = "", dockerfile = platform, ]; - // Add tags - for (let tag of tags) { - args.push("-t", `${repoName}:${tag}`); + for (let repoName of repoNames) { + // Add tags + for (let tag of tags) { + args.push("-t", `${repoName}:${tag}`); + } } args = [ diff --git a/extra/release/nightly.mjs b/extra/release/nightly.mjs index c6641bad7..54951ac42 100644 --- a/extra/release/nightly.mjs +++ b/extra/release/nightly.mjs @@ -1,7 +1,7 @@ -import { buildDist, buildImage, checkDocker, getRepoName } from "./lib.mjs"; +import { buildDist, buildImage, checkDocker, getRepoNames } from "./lib.mjs"; // Docker Hub repository name -const repoName = getRepoName(); +const repoNames = getRepoNames(); // Check if docker is running checkDocker(); @@ -10,7 +10,7 @@ checkDocker(); buildDist(); // Build full image (rootless) -buildImage(repoName, [ "nightly2-rootless" ], "nightly-rootless"); +buildImage(repoNames, [ "nightly2-rootless" ], "nightly-rootless"); // Build full image -buildImage(repoName, [ "nightly2" ], "nightly"); +buildImage(repoNames, [ "nightly2" ], "nightly"); From a7407a1b658065daa12a3d86ebf2f02a2e25a38a Mon Sep 17 00:00:00 2001 From: Frank Elsinga Date: Tue, 5 Nov 2024 13:35:42 +0100 Subject: [PATCH 4/8] fix not setting a default for json monitor operations (#5295) --- db/knex_migrations/2024-10-31-0000-fix-snmp-monitor.js | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 db/knex_migrations/2024-10-31-0000-fix-snmp-monitor.js diff --git a/db/knex_migrations/2024-10-31-0000-fix-snmp-monitor.js b/db/knex_migrations/2024-10-31-0000-fix-snmp-monitor.js new file mode 100644 index 000000000..0a605d38f --- /dev/null +++ b/db/knex_migrations/2024-10-31-0000-fix-snmp-monitor.js @@ -0,0 +1,7 @@ +exports.up = function (knex) { + return knex("monitor").whereNull("json_path_operator").update("json_path_operator", "=="); +}; +exports.down = function (knex) { + // changing the json_path_operator back to null for all "==" is not possible anymore + // we have lost the context which fields have been set explicitely in >= v2.0 and which would need to be reverted +}; From 13ea190298a22fdb29e70accdc63df231dcdb73f Mon Sep 17 00:00:00 2001 From: Daan Meijer Date: Tue, 5 Nov 2024 20:50:47 +0100 Subject: [PATCH 5/8] only allow for valid urls in slack button (#5312) --- server/notification-providers/slack.js | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/server/notification-providers/slack.js b/server/notification-providers/slack.js index 209c7c0c6..5e25a1fbc 100644 --- a/server/notification-providers/slack.js +++ b/server/notification-providers/slack.js @@ -1,7 +1,7 @@ const NotificationProvider = require("./notification-provider"); const axios = require("axios"); const { setSettings, setting } = require("../util-server"); -const { getMonitorRelativeURL, UP } = require("../../src/util"); +const { getMonitorRelativeURL, UP, log } = require("../../src/util"); class Slack extends NotificationProvider { name = "slack"; @@ -50,15 +50,20 @@ class Slack extends NotificationProvider { const address = this.extractAddress(monitorJSON); if (address) { - actions.push({ - "type": "button", - "text": { - "type": "plain_text", - "text": "Visit site", - }, - "value": "Site", - "url": address, - }); + try { + actions.push({ + "type": "button", + "text": { + "type": "plain_text", + "text": "Visit site", + }, + "value": "Site", + "url": new URL(address), + }); + + } catch (e) { + log.debug("slack", `Failed to parse address ${address} as URL`); + } } return actions; From 6899603eb7a2dc1cbcf6363f2eafb8c282cd4823 Mon Sep 17 00:00:00 2001 From: Abhigyan Mohanta <110360901+ABHIGYAN-MOHANTA@users.noreply.github.com> Date: Sat, 9 Nov 2024 21:00:08 +0530 Subject: [PATCH 6/8] fix #5314 Email Address Friendly Name Error (#5318) --- src/components/notifications/SendGrid.vue | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/notifications/SendGrid.vue b/src/components/notifications/SendGrid.vue index 18118f469..53f1f1137 100644 --- a/src/components/notifications/SendGrid.vue +++ b/src/components/notifications/SendGrid.vue @@ -5,20 +5,20 @@
- +
- +
- +
{{ $t("Separate multiple email addresses with commas") }}
- + {{ $t("Separate multiple email addresses with commas") }}
From 778363a948513f0f33977563384a1e27d5c7005d Mon Sep 17 00:00:00 2001 From: Dorence Deng Date: Mon, 11 Nov 2024 18:08:43 +0800 Subject: [PATCH 7/8] fix(notification): #5323 fix EditMonitor.vue useToast error (#5333) --- src/pages/EditMonitor.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index 4763f8722..a4f93bc2c 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -1065,7 +1065,7 @@ import { hostNameRegexPattern } from "../util-frontend"; import HiddenInput from "../components/HiddenInput.vue"; import EditMonitorConditions from "../components/EditMonitorConditions.vue"; -const toast = useToast; +const toast = useToast(); const pushTokenLength = 32; From 8a432ac93776f01a5464f70e7d34e223193aad2e Mon Sep 17 00:00:00 2001 From: Ionys <9364594+Ionys320@users.noreply.github.com> Date: Tue, 12 Nov 2024 19:00:09 +0100 Subject: [PATCH 8/8] fix(status page): Make sure the group deletion is correctly handled when `groupIDList` is empty (#5340) --- .../status-page-socket-handler.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/server/socket-handlers/status-page-socket-handler.js b/server/socket-handlers/status-page-socket-handler.js index 0804da15d..cbcc52b8f 100644 --- a/server/socket-handlers/status-page-socket-handler.js +++ b/server/socket-handlers/status-page-socket-handler.js @@ -220,13 +220,17 @@ module.exports.statusPageSocketHandler = (socket) => { // Delete groups that are not in the list log.debug("socket", "Delete groups that are not in the list"); - const slots = groupIDList.map(() => "?").join(","); + if (groupIDList.length === 0) { + await R.exec("DELETE FROM `group` WHERE status_page_id = ?", [ statusPage.id ]); + } else { + const slots = groupIDList.map(() => "?").join(","); - const data = [ - ...groupIDList, - statusPage.id - ]; - await R.exec(`DELETE FROM \`group\` WHERE id NOT IN (${slots}) AND status_page_id = ?`, data); + const data = [ + ...groupIDList, + statusPage.id + ]; + await R.exec(`DELETE FROM \`group\` WHERE id NOT IN (${slots}) AND status_page_id = ?`, data); + } const server = UptimeKumaServer.getInstance();