From 443d5cf55413b13b8610df88feb55ca8afff24f8 Mon Sep 17 00:00:00 2001 From: Peak Twilight <77903714+peaktwilight@users.noreply.github.com> Date: Sun, 15 Jun 2025 20:44:28 +0200 Subject: [PATCH 1/8] Improve UI: Edit Buttons in Status Page (#5920) Co-authored-by: Frank Elsinga --- src/layouts/Layout.vue | 2 +- src/pages/StatusPage.vue | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/layouts/Layout.vue b/src/layouts/Layout.vue index 9faedf589..e93a5159e 100644 --- a/src/layouts/Layout.vue +++ b/src/layouts/Layout.vue @@ -16,7 +16,7 @@ {{ $t("Uptime Kuma") }} - + {{ $t("New Update") }} diff --git a/src/pages/StatusPage.vue b/src/pages/StatusPage.vue index 116968282..e0df74fde 100644 --- a/src/pages/StatusPage.vue +++ b/src/pages/StatusPage.vue @@ -157,12 +157,12 @@
- - + {{ $t("Go to Dashboard") }} From 4962817795804ea7dd875f65999b528f9769a7b4 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Thu, 19 Jun 2025 14:29:43 +0800 Subject: [PATCH 2/8] [Eliminate Blocking] User related (#5928) --- server/auth.js | 2 +- server/model/user.js | 4 ++-- server/password-hash.js | 4 ++-- server/server.js | 2 +- server/socket-handlers/api-key-socket-handler.js | 2 +- server/util-server.js | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/server/auth.js b/server/auth.js index 597cf3d75..a4aed50b8 100644 --- a/server/auth.js +++ b/server/auth.js @@ -26,7 +26,7 @@ exports.login = async function (username, password) { // Upgrade the hash to bcrypt if (passwordHash.needRehash(user.password)) { await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [ - passwordHash.generate(password), + await passwordHash.generate(password), user.id, ]); } diff --git a/server/model/user.js b/server/model/user.js index 329402ff5..33277d485 100644 --- a/server/model/user.js +++ b/server/model/user.js @@ -14,7 +14,7 @@ class User extends BeanModel { */ static async resetPassword(userID, newPassword) { await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [ - passwordHash.generate(newPassword), + await passwordHash.generate(newPassword), userID ]); } @@ -25,7 +25,7 @@ class User extends BeanModel { * @returns {Promise} */ async resetPassword(newPassword) { - const hashedPassword = passwordHash.generate(newPassword); + const hashedPassword = await passwordHash.generate(newPassword); await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [ hashedPassword, diff --git a/server/password-hash.js b/server/password-hash.js index 83a23d9e6..19aec193b 100644 --- a/server/password-hash.js +++ b/server/password-hash.js @@ -5,10 +5,10 @@ const saltRounds = 10; /** * Hash a password * @param {string} password Password to hash - * @returns {string} Hash + * @returns {Promise} Hash */ exports.generate = function (password) { - return bcrypt.hashSync(password, saltRounds); + return bcrypt.hash(password, saltRounds); }; /** diff --git a/server/server.js b/server/server.js index e328ff470..5b2f41a2e 100644 --- a/server/server.js +++ b/server/server.js @@ -674,7 +674,7 @@ let needSetup = false; let user = R.dispense("user"); user.username = username; - user.password = passwordHash.generate(password); + user.password = await passwordHash.generate(password); await R.store(user); needSetup = false; diff --git a/server/socket-handlers/api-key-socket-handler.js b/server/socket-handlers/api-key-socket-handler.js index f76b90991..d88151294 100644 --- a/server/socket-handlers/api-key-socket-handler.js +++ b/server/socket-handlers/api-key-socket-handler.js @@ -20,7 +20,7 @@ module.exports.apiKeySocketHandler = (socket) => { checkLogin(socket); let clearKey = nanoid(40); - let hashedKey = passwordHash.generate(clearKey); + let hashedKey = await passwordHash.generate(clearKey); key["key"] = hashedKey; let bean = await APIKey.save(key, socket.userID); diff --git a/server/util-server.js b/server/util-server.js index 08df728ed..4cc833330 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -51,7 +51,7 @@ exports.initJWTSecret = async () => { jwtSecretBean.key = "jwtSecret"; } - jwtSecretBean.value = passwordHash.generate(genSecret()); + jwtSecretBean.value = await passwordHash.generate(genSecret()); await R.store(jwtSecretBean); return jwtSecretBean; }; From e0335ecfbdfc2c8009c33447f9eaa9e69eb88f34 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Thu, 19 Jun 2025 14:30:24 +0800 Subject: [PATCH 3/8] [Eliminate Blocking] Database related (#5926) --- server/database.js | 7 ++++--- server/socket-handlers/database-socket-handler.js | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/server/database.js b/server/database.js index 582f19c29..206c2ffdc 100644 --- a/server/database.js +++ b/server/database.js @@ -1,4 +1,5 @@ const fs = require("fs"); +const fsAsync = fs.promises; const { R } = require("redbean-node"); const { setSetting, setting } = require("./util-server"); const { log, sleep } = require("../src/util"); @@ -707,12 +708,12 @@ class Database { /** * Get the size of the database (SQLite only) - * @returns {number} Size of database + * @returns {Promise} Size of database */ - static getSize() { + static async getSize() { if (Database.dbConfig.type === "sqlite") { log.debug("db", "Database.getSize()"); - let stats = fs.statSync(Database.sqlitePath); + let stats = await fsAsync.stat(Database.sqlitePath); log.debug("db", stats); return stats.size; } diff --git a/server/socket-handlers/database-socket-handler.js b/server/socket-handlers/database-socket-handler.js index ee2394bf6..33f8f3195 100644 --- a/server/socket-handlers/database-socket-handler.js +++ b/server/socket-handlers/database-socket-handler.js @@ -14,7 +14,7 @@ module.exports.databaseSocketHandler = (socket) => { checkLogin(socket); callback({ ok: true, - size: Database.getSize(), + size: await Database.getSize(), }); } catch (error) { callback({ From b1e8d9b4d290a293dfef51784e87a3cb863550cd Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Thu, 19 Jun 2025 15:41:21 +0800 Subject: [PATCH 4/8] [Eliminate Blocking] Push example (#5925) --- server/socket-handlers/general-socket-handler.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/socket-handlers/general-socket-handler.js b/server/socket-handlers/general-socket-handler.js index 0c11de295..b996efe7b 100644 --- a/server/socket-handlers/general-socket-handler.js +++ b/server/socket-handlers/general-socket-handler.js @@ -4,7 +4,7 @@ const { sendInfo } = require("../client"); const { checkLogin } = require("../util-server"); const GameResolver = require("gamedig/lib/GameResolver"); const { testChrome } = require("../monitor-types/real-browser-monitor-type"); -const fs = require("fs"); +const fsAsync = require("fs").promises; const path = require("path"); let gameResolver = new GameResolver(); @@ -90,7 +90,7 @@ module.exports.generalSocketHandler = (socket, server) => { } }); - socket.on("getPushExample", (language, callback) => { + socket.on("getPushExample", async (language, callback) => { try { checkLogin(socket); if (!/^[a-z-]+$/.test(language)) { @@ -106,13 +106,13 @@ module.exports.generalSocketHandler = (socket, server) => { try { let dir = path.join("./extra/push-examples", language); - let files = fs.readdirSync(dir); + let files = await fsAsync.readdir(dir); for (let file of files) { if (file.startsWith("index.")) { callback({ ok: true, - code: fs.readFileSync(path.join(dir, file), "utf8"), + code: await fsAsync.readFile(path.join(dir, file), "utf8"), }); return; } From 5aeda2dab0cd93eb4dc989ece5fd423f17faf193 Mon Sep 17 00:00:00 2001 From: Eden Yemini Date: Fri, 20 Jun 2025 00:56:43 +0300 Subject: [PATCH 5/8] feat: render markdown in monitor descriptions (#5576) --- src/pages/Details.vue | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/pages/Details.vue b/src/pages/Details.vue index 17d32365c..1d068b92e 100644 --- a/src/pages/Details.vue +++ b/src/pages/Details.vue @@ -9,7 +9,8 @@
{{ monitor.id }}
-

{{ monitor.description }}

+ +

@@ -285,6 +286,8 @@ import Tag from "../components/Tag.vue"; import CertificateInfo from "../components/CertificateInfo.vue"; import { getMonitorRelativeURL } from "../util.ts"; import { URL } from "whatwg-url"; +import DOMPurify from "dompurify"; +import { marked } from "marked"; import { getResBaseURL } from "../util-frontend"; import { highlight, languages } from "prismjs/components/prism-core"; import "prismjs/components/prism-clike"; @@ -399,6 +402,14 @@ export default { screenshotURL() { return getResBaseURL() + this.monitor.screenshot + "?time=" + this.cacheTime; + }, + + descriptionHTML() { + if (this.monitor.description != null) { + return DOMPurify.sanitize(marked(this.monitor.description)); + } else { + return ""; + } } }, From 072226bde25616d2dafb015eb66278c6add15bf0 Mon Sep 17 00:00:00 2001 From: Ionys <9364594+Ionys320@users.noreply.github.com> Date: Sun, 22 Jun 2025 01:02:52 +0200 Subject: [PATCH 6/8] Improve i18n language matching (#5939) --- src/i18n.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/i18n.js b/src/i18n.js index bd7e3a122..3f4e98de3 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -75,11 +75,20 @@ export function currentLocale() { if (locale in messages) { return locale; } - // some locales are further specified such as "en-US". - // If we only have a generic locale for this, we can use it too - const genericLocale = locale.split("-")[0]; - if (genericLocale in messages) { - return genericLocale; + // If the locale is a 2-letter code, we can try to find a regional variant + // e.g. "fr" may not be in the messages, but "fr-FR" is + if (locale.length === 2) { + const regionalLocale = `${locale}-${locale.toUpperCase()}`; + if (regionalLocale in messages) { + return regionalLocale; + } + } else { + // Some locales are further specified such as "en-US". + // If we only have a generic locale for this, we can use it too + const genericLocale = locale.slice(0, 2); + if (genericLocale in messages) { + return genericLocale; + } } } return "en"; From 59d10062ca5957459e0cda53c0468c1514ace590 Mon Sep 17 00:00:00 2001 From: Frank Elsinga Date: Sun, 22 Jun 2025 14:49:24 +0200 Subject: [PATCH 7/8] chore: remove fluff from PR-template (#5941) --- .github/ISSUE_TEMPLATE/ask_for_help.yml | 2 +- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .github/ISSUE_TEMPLATE/feature_request.yml | 2 +- .github/ISSUE_TEMPLATE/security_issue.yml | 2 +- .github/PULL_REQUEST_TEMPLATE.md | 55 +++++----------------- 5 files changed, 17 insertions(+), 46 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/ask_for_help.yml b/.github/ISSUE_TEMPLATE/ask_for_help.yml index 45bd046e5..2156c5be0 100644 --- a/.github/ISSUE_TEMPLATE/ask_for_help.yml +++ b/.github/ISSUE_TEMPLATE/ask_for_help.yml @@ -3,7 +3,7 @@ name: ❓ Ask for help description: | Submit any question related to Uptime Kuma #title: "[Help]" -labels: ["help", "P3-low"] +labels: ["help"] body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 31cd6faf6..d0330c70a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -3,7 +3,7 @@ name: πŸ› Bug Report description: | Submit a bug report to help us improve #title: "[Bug]" -labels: ["bug", "P2-medium"] +labels: ["bug"] body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index c9ec4d093..4e1be15e1 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -3,7 +3,7 @@ name: πŸš€ Feature Request description: | Submit a proposal for a new feature # title: "[Feature]" -labels: ["feature-request", "P3-low"] +labels: ["feature-request"] body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/security_issue.yml b/.github/ISSUE_TEMPLATE/security_issue.yml index d49c0aaf5..247073102 100644 --- a/.github/ISSUE_TEMPLATE/security_issue.yml +++ b/.github/ISSUE_TEMPLATE/security_issue.yml @@ -3,7 +3,7 @@ name: πŸ›‘οΈ Security Issue description: | Notify Louis Lam about a security concern. Please do NOT include any sensitive details in this issue. # title: "Security Issue" -labels: ["security", "P1-high"] +labels: ["security"] assignees: [louislam] body: - type: markdown diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index f639c395e..dade3e76f 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,10 +1,10 @@ -**⚠️ Please Note: We do not accept all types of pull requests, and we want to ensure we don’t waste your time. Before submitting, make sure you have read our pull request guidelines: [Pull Request Rules](https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md#can-i-create-a-pull-request-for-uptime-kuma)** - ## ❗ Important Announcement
Click here for more details:

+**⚠️ Please Note: We do not accept all types of pull requests, and we want to ensure we don’t waste your time. Before submitting, make sure you have read our pull request guidelines: [Pull Request Rules](https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md#can-i-create-a-pull-request-for-uptime-kuma)** + ### 🚧 Temporary Delay in Feature Requests and Pull Request Reviews **At this time, we may be slower to respond to new feature requests and review pull requests. Existing requests and PRs will remain in the backlog but may not be prioritized immediately.** @@ -26,16 +26,22 @@ We appreciate your patience and understanding as we continue to improve Uptime K ## πŸ“‹ Overview -Provide a clear summary of the purpose and scope of this pull request: + - **What problem does this pull request address?** - - Please provide a detailed explanation here. - - **What features or functionality does this pull request introduce or enhance?** - - Please provide a detailed explanation here. +## πŸ”— Related Issues + + + +- Relates to #issue-number +- Resolves #issue-number + ## πŸ”„ Changes ### πŸ› οΈ Type of change @@ -52,19 +58,7 @@ Provide a clear summary of the purpose and scope of this pull request: - [ ] πŸ”§ Other (please specify): - Provide additional details here. -## πŸ”— Related Issues - - - -- Relates to #issue-number -- Resolves #issue-number -- Fixes #issue-number - -## πŸ“„ Checklist * +## πŸ“„ Checklist @@ -97,26 +91,3 @@ If not, remove this section. | `DOWN` | ![Before](image-link) | ![After](image-link) | | Certificate-expiry | ![Before](image-link) | ![After](image-link) | | Testing | ![Before](image-link) | ![After](image-link) | - -## ℹ️ Additional Context - -Provide any relevant details to assist reviewers in understanding the changes. - -
Click here for more details: -

- -**Key Considerations**: - -- **Design decisions** – Key choices or trade-offs made during development. -- **Alternative solutions** – Approaches considered but not implemented, along with reasons. -- **Relevant links** – Specifications, discussions, or resources that provide context. -- **Dependencies** – Related pull requests or issues that must be resolved before merging. -- **Additional context** – Any other details that may help reviewers understand the changes. - -Provide details here - -## πŸ’¬ Requested Feedback - - - -- `Mention documents needing feedback here` From b55d6e8911ea6ee5a7bdddc9f3106b6f5bade698 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Mon, 23 Jun 2025 15:24:49 +0800 Subject: [PATCH 8/8] Improve the experience of testing pull requests (#5942) --- docker/dockerfile | 4 ++++ extra/checkout-pr.js | 33 -------------------------------- extra/checkout-pr.mjs | 34 +++++++++++++++++++++++++++++++++ extra/kuma-pr/index.mjs | 26 +++++++++++++++++++++++++ extra/kuma-pr/package.json | 8 ++++++++ extra/kuma-pr/pr-lib.mjs | 39 ++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 7 files changed, 112 insertions(+), 34 deletions(-) delete mode 100644 extra/checkout-pr.js create mode 100644 extra/checkout-pr.mjs create mode 100644 extra/kuma-pr/index.mjs create mode 100644 extra/kuma-pr/package.json create mode 100644 extra/kuma-pr/pr-lib.mjs diff --git a/docker/dockerfile b/docker/dockerfile index d55f94f61..e2a301e7b 100644 --- a/docker/dockerfile +++ b/docker/dockerfile @@ -79,6 +79,10 @@ USER node RUN git config --global user.email "no-reply@no-reply.com" RUN git config --global user.name "PR Tester" RUN git clone https://github.com/louislam/uptime-kuma.git . + +# Hide the warning when running in detached head state +RUN git config --global advice.detachedHead false + RUN npm ci EXPOSE 3000 3001 diff --git a/extra/checkout-pr.js b/extra/checkout-pr.js deleted file mode 100644 index 0328770b1..000000000 --- a/extra/checkout-pr.js +++ /dev/null @@ -1,33 +0,0 @@ -const childProcess = require("child_process"); - -if (!process.env.UPTIME_KUMA_GH_REPO) { - console.error("Please set a repo to the environment variable 'UPTIME_KUMA_GH_REPO' (e.g. mhkarimi1383:goalert-notification)"); - process.exit(1); -} - -let inputArray = process.env.UPTIME_KUMA_GH_REPO.split(":"); - -if (inputArray.length !== 2) { - console.error("Invalid format. Please set a repo to the environment variable 'UPTIME_KUMA_GH_REPO' (e.g. mhkarimi1383:goalert-notification)"); -} - -let name = inputArray[0]; -let branch = inputArray[1]; - -console.log("Checkout pr"); - -// Checkout the pr -let result = childProcess.spawnSync("git", [ "remote", "add", name, `https://github.com/${name}/uptime-kuma` ]); - -console.log(result.stdout.toString()); -console.error(result.stderr.toString()); - -result = childProcess.spawnSync("git", [ "fetch", name, branch ]); - -console.log(result.stdout.toString()); -console.error(result.stderr.toString()); - -result = childProcess.spawnSync("git", [ "checkout", `${name}/${branch}`, "--force" ]); - -console.log(result.stdout.toString()); -console.error(result.stderr.toString()); diff --git a/extra/checkout-pr.mjs b/extra/checkout-pr.mjs new file mode 100644 index 000000000..653664477 --- /dev/null +++ b/extra/checkout-pr.mjs @@ -0,0 +1,34 @@ +import childProcess from "child_process"; +import { parsePrName } from "./kuma-pr/pr-lib.mjs"; + +let { name, branch } = parsePrName(process.env.UPTIME_KUMA_GH_REPO); + +console.log(`Checking out PR from ${name}:${branch}`); + +// Checkout the pr +let result = childProcess.spawnSync("git", [ "remote", "add", name, `https://github.com/${name}/uptime-kuma` ], { + stdio: "inherit" +}); + +if (result.status !== 0) { + console.error("Failed to add remote repository."); + process.exit(1); +} + +result = childProcess.spawnSync("git", [ "fetch", name, branch ], { + stdio: "inherit" +}); + +if (result.status !== 0) { + console.error("Failed to fetch the branch."); + process.exit(1); +} + +result = childProcess.spawnSync("git", [ "checkout", `${name}/${branch}`, "--force" ], { + stdio: "inherit" +}); + +if (result.status !== 0) { + console.error("Failed to checkout the branch."); + process.exit(1); +} diff --git a/extra/kuma-pr/index.mjs b/extra/kuma-pr/index.mjs new file mode 100644 index 000000000..bcda9d335 --- /dev/null +++ b/extra/kuma-pr/index.mjs @@ -0,0 +1,26 @@ +#!/usr/bin/env node +import { spawn } from "child_process"; +import { parsePrName } from "./pr-lib.mjs"; + +const prName = process.argv[2]; + +// Pre-check the prName here, so testers don't need to wait until the Docker image is pulled to see the error. +try { + parsePrName(prName); +} catch (error) { + console.error(error.message); + process.exit(1); +} + +spawn("docker", [ + "run", + "--rm", + "-it", + "-p", "3000:3000", + "-p", "3001:3001", + "--pull", "always", + "-e", `UPTIME_KUMA_GH_REPO=${prName}`, + "louislam/uptime-kuma:pr-test2" +], { + stdio: "inherit", +}); diff --git a/extra/kuma-pr/package.json b/extra/kuma-pr/package.json new file mode 100644 index 000000000..16f30b160 --- /dev/null +++ b/extra/kuma-pr/package.json @@ -0,0 +1,8 @@ +{ + "name": "kuma-pr", + "version": "1.0.0", + "type": "module", + "bin": { + "kuma-pr": "./index.mjs" + } +} diff --git a/extra/kuma-pr/pr-lib.mjs b/extra/kuma-pr/pr-lib.mjs new file mode 100644 index 000000000..1cb5050bf --- /dev/null +++ b/extra/kuma-pr/pr-lib.mjs @@ -0,0 +1,39 @@ +/** + * Parse : to an object. + * @param {string} prName : + * @returns {object} An object with name and branch properties. + */ +export function parsePrName(prName) { + let name = "louislam"; + let branch; + + const errorMessage = "Please set a repo to the environment variable 'UPTIME_KUMA_GH_REPO' (e.g. mhkarimi1383:goalert-notification)"; + + if (!prName) { + throw new Error(errorMessage); + } + + prName = prName.trim(); + if (prName === "") { + throw new Error(errorMessage); + } + + let inputArray = prName.split(":"); + + // Just realized that owner's prs are not prefixed with "louislam:" + if (inputArray.length === 1) { + branch = inputArray[0]; + + } else if (inputArray.length === 2) { + name = inputArray[0]; + branch = inputArray[1]; + + } else { + throw new Error("Invalid format. The format is like this: mhkarimi1383:goalert-notification"); + } + + return { + name, + branch + }; +} diff --git a/package.json b/package.json index 16837fa8a..97b7bc339 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "release-nightly": "node ./extra/release/nightly.mjs", "git-remove-tag": "git tag -d", "build-dist-and-restart": "npm run build && npm run start-server-dev", - "start-pr-test": "node extra/checkout-pr.js && npm install && npm run dev", + "start-pr-test": "node extra/checkout-pr.mjs && npm install && npm run dev", "build-healthcheck-armv7": "cross-env GOOS=linux GOARCH=arm GOARM=7 go build -x -o ./extra/healthcheck-armv7 ./extra/healthcheck.go", "deploy-demo-server": "node extra/deploy-demo-server.js", "sort-contributors": "node extra/sort-contributors.js",