Merge branch 'master' into fix-sync

This commit is contained in:
Louis Lam 2025-06-23 15:24:59 +08:00 committed by GitHub
commit 6278267681
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 175 additions and 105 deletions

View file

@ -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:

View file

@ -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:

View file

@ -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:

View file

@ -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

View file

@ -1,10 +1,10 @@
**⚠️ Please Note: We do not accept all types of pull requests, and we want to ensure we dont 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
<details><summary>Click here for more details:</summary>
</p>
**⚠️ Please Note: We do not accept all types of pull requests, and we want to ensure we dont 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:
<!-- 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
<!--
Please link any GitHub issues or tasks that this pull request addresses. Use the appropriate issue numbers or links.
-->
- 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
<!--
Please link any GitHub issues or tasks that this pull request addresses. Use the appropriate issue numbers or links.
**Note**: Include only issues directly related to this PR. Remove any irrelevant reference.
-->
- Relates to #issue-number
- Resolves #issue-number
- Fixes #issue-number
## 📄 Checklist *
## 📄 Checklist
<!-- Please select all options that apply -->
@ -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.
<details><summary>Click here for more details:</summary>
</p>
**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
<!-- If a part of our docs is unclear, you are unsure how to do something/.. this is where we would appreciate your feedback -->
- `Mention documents needing feedback here`

View file

@ -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

View file

@ -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());

34
extra/checkout-pr.mjs Normal file
View file

@ -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);
}

26
extra/kuma-pr/index.mjs Normal file
View file

@ -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",
});

View file

@ -0,0 +1,8 @@
{
"name": "kuma-pr",
"version": "1.0.0",
"type": "module",
"bin": {
"kuma-pr": "./index.mjs"
}
}

39
extra/kuma-pr/pr-lib.mjs Normal file
View file

@ -0,0 +1,39 @@
/**
* Parse <name>:<branch> to an object.
* @param {string} prName <name>:<branch>
* @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
};
}

View file

@ -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",

View file

@ -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,
]);
}

View file

@ -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<number>} 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;
}

View file

@ -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<void>}
*/
async resetPassword(newPassword) {
const hashedPassword = passwordHash.generate(newPassword);
const hashedPassword = await passwordHash.generate(newPassword);
await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [
hashedPassword,

View file

@ -5,10 +5,10 @@ const saltRounds = 10;
/**
* Hash a password
* @param {string} password Password to hash
* @returns {string} Hash
* @returns {Promise<string>} Hash
*/
exports.generate = function (password) {
return bcrypt.hashSync(password, saltRounds);
return bcrypt.hash(password, saltRounds);
};
/**

View file

@ -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;

View file

@ -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);

View file

@ -14,7 +14,7 @@ module.exports.databaseSocketHandler = (socket) => {
checkLogin(socket);
callback({
ok: true,
size: Database.getSize(),
size: await Database.getSize(),
});
} catch (error) {
callback({

View file

@ -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;
}

View file

@ -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;
};

View file

@ -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";

View file

@ -16,7 +16,7 @@
<span class="fs-4 title">{{ $t("Uptime Kuma") }}</span>
</router-link>
<a v-if="hasNewVersion" target="_blank" href="https://github.com/louislam/uptime-kuma/releases" class="btn btn-info me-3">
<a v-if="hasNewVersion" target="_blank" href="https://github.com/louislam/uptime-kuma/releases" class="btn btn-primary me-3">
<font-awesome-icon icon="arrow-alt-circle-up" /> {{ $t("New Update") }}
</a>

View file

@ -9,7 +9,8 @@
<div>{{ monitor.id }}</div>
</div>
</h1>
<p v-if="monitor.description">{{ monitor.description }}</p>
<!-- eslint-disable-next-line vue/no-v-html-->
<p v-if="monitor.description" v-html="descriptionHTML"></p>
<div class="d-flex">
<div class="tags">
<Tag v-for="tag in monitor.tags" :key="tag.id" :item="tag" :size="'sm'" />
@ -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 "";
}
}
},

View file

@ -157,12 +157,12 @@
<!-- Admin functions -->
<div v-if="hasToken" class="mb-4">
<div v-if="!enableEditMode">
<button class="btn btn-info me-2" data-testid="edit-button" @click="edit">
<button class="btn btn-primary me-2" data-testid="edit-button" @click="edit">
<font-awesome-icon icon="edit" />
{{ $t("Edit Status Page") }}
</button>
<a href="/manage-status-page" class="btn btn-info">
<a href="/manage-status-page" class="btn btn-primary">
<font-awesome-icon icon="tachometer-alt" />
{{ $t("Go to Dashboard") }}
</a>