Merge branch 'master' into feature/umami-analytics-status-page

This commit is contained in:
Frank Elsinga 2025-10-27 22:29:44 +01:00 committed by GitHub
commit fd07cf7f7b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 127 additions and 65 deletions

View File

@ -22,7 +22,7 @@ jobs:
strategy:
matrix:
os: [macos-latest, ubuntu-22.04, windows-latest, ARM64]
node: [ 18, 20 ]
node: [ 20, 24 ]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
@ -30,7 +30,7 @@ jobs:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node }}
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node }}
- run: npm install
@ -49,7 +49,7 @@ jobs:
strategy:
matrix:
os: [ ARMv7 ]
node: [ 18, 20 ]
node: [ 20, 22 ]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
@ -57,7 +57,7 @@ jobs:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node }}
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node }}
- run: npm ci --production
@ -70,7 +70,7 @@ jobs:
- uses: actions/checkout@v4
- name: Use Node.js 20
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 20
- run: npm install
@ -84,7 +84,7 @@ jobs:
- uses: actions/checkout@v4
- name: Use Node.js 20
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 20
- run: npm install

View File

@ -11,13 +11,13 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
node-version: [18]
node-version: [20]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'

View File

@ -32,7 +32,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Use Node.js 20
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 20

View File

@ -19,7 +19,4 @@ RUN apt update && \
curl -sL https://deb.nodesource.com/setup_18.x | bash && \
apt --yes --no-install-recommends install nodejs && \
node ./extra/build-healthcheck.js $TARGETPLATFORM && \
apt --yes remove nodejs && \
apt autoremove -y --purge && \
apt clean && \
rm -rf /var/lib/apt/lists/*
apt --yes remove nodejs

View File

@ -1,18 +1,15 @@
# Download Apprise deb package
FROM node:20-bookworm-slim AS download-apprise
FROM node:22-bookworm-slim AS download-apprise
WORKDIR /app
COPY ./extra/download-apprise.mjs ./download-apprise.mjs
RUN apt update && \
apt --yes --no-install-recommends install curl && \
npm install cheerio semver && \
node ./download-apprise.mjs && \
apt autoremove -y --purge && \
apt clean && \
rm -rf /var/lib/apt/lists/*
node ./download-apprise.mjs
# Base Image (Slim)
# If the image changed, the second stage image should be changed too
FROM node:20-bookworm-slim AS base2-slim
FROM node:22-bookworm-slim AS base2-slim
ARG TARGETPLATFORM
# Specify --no-install-recommends to skip unused dependencies, make the base much smaller!
@ -34,9 +31,8 @@ RUN apt update && \
curl \
sudo \
nscd && \
apt autoremove -y --purge && \
apt clean && \
rm -rf /var/lib/apt/lists/*
rm -rf /var/lib/apt/lists/* && \
apt --yes autoremove
# apprise = for notifications (Install from the deb package, as the stable one is too old) (workaround for #4867)
# Switching to testing repo is no longer working, as the testing repo is not bookworm anymore.
@ -45,10 +41,9 @@ RUN apt update && \
COPY --from=download-apprise /app/apprise.deb ./apprise.deb
RUN apt update && \
apt --yes --no-install-recommends install ./apprise.deb python3-paho-mqtt && \
rm -rf /var/lib/apt/lists/* && \
rm -f apprise.deb && \
apt autoremove -y --purge && \
apt clean && \
rm -rf /var/lib/apt/lists/*
apt --yes autoremove
# Install cloudflared
RUN curl https://pkg.cloudflare.com/cloudflare-main.gpg --output /usr/share/keyrings/cloudflare-main.gpg && \
@ -56,14 +51,14 @@ RUN curl https://pkg.cloudflare.com/cloudflare-main.gpg --output /usr/share/keyr
apt update && \
apt install --yes --no-install-recommends cloudflared && \
cloudflared version && \
apt autoremove -y --purge && \
apt clean && \
rm -rf /var/lib/apt/lists/*
rm -rf /var/lib/apt/lists/* && \
apt --yes autoremove
# For nscd
COPY ./docker/etc/nscd.conf /etc/nscd.conf
COPY ./docker/etc/sudoers /etc/sudoers
# Full Base Image
# MariaDB, Chromium and fonts
# Make sure to reuse the slim image here. Uncomment the above line if you want to build it from scratch.
@ -72,7 +67,6 @@ FROM louislam/uptime-kuma:base2-slim AS base2
ENV UPTIME_KUMA_ENABLE_EMBEDDED_MARIADB=1
RUN apt update && \
apt --yes --no-install-recommends install chromium fonts-indic fonts-noto fonts-noto-cjk mariadb-server && \
apt autoremove -y --purge && \
apt clean && \
rm -rf /var/lib/apt/lists/* && \
apt --yes autoremove && \
chown -R node:node /var/lib/mysql

View File

@ -70,10 +70,7 @@ RUN apt update \
&& chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
&& apt update \
&& apt --yes --no-install-recommends install git \
&& apt autoremove -y --purge \
&& apt clean \
&& rm -rf /var/lib/apt/lists/*
&& apt --yes --no-install-recommends install git
## Empty the directory, because we have to clone the Git repo.
RUN rm -rf ./* && chown node /app
@ -98,10 +95,7 @@ CMD ["npm", "run", "start-pr-test"]
FROM louislam/uptime-kuma:base2 AS upload-artifact
WORKDIR /
RUN apt update && \
apt --yes install curl file && \
apt autoremove -y --purge && \
apt clean && \
rm -rf /var/lib/apt/lists/*
apt --yes install curl file
COPY --from=build /app /app
@ -121,3 +115,4 @@ RUN chmod +x /app/extra/upload-github-release-asset.sh
# Dist only
RUN cd /app && tar -zcvf $DIST dist
RUN /app/extra/upload-github-release-asset.sh github_api_token=$GITHUB_TOKEN owner=louislam repo=uptime-kuma tag=$VERSION filename=/app/$DIST

View File

@ -7,7 +7,7 @@
"url": "https://github.com/louislam/uptime-kuma.git"
},
"engines": {
"node": "18 || >= 20.4.0"
"node": ">= 20.4.0"
},
"scripts": {
"lint:js": "eslint --ext \".js,.vue\" --ignore-path .gitignore .",
@ -27,7 +27,9 @@
"build": "vite build --config ./config/vite.config.js",
"test": "npm run test-backend && npm run test-e2e",
"test-with-build": "npm run build && npm test",
"test-backend": "cross-env TEST_BACKEND=1 node --test test/backend-test",
"test-backend": "node test/test-backend.mjs",
"test-backend-22": "cross-env TEST_BACKEND=1 node --test \"test/backend-test/**/*.js\"",
"test-backend-20": "cross-env TEST_BACKEND=1 node --test test/backend-test",
"test-e2e": "playwright test --config ./config/playwright.config.js",
"test-e2e-ui": "playwright test --config ./config/playwright.config.js --ui --ui-port=51063",
"playwright-codegen": "playwright codegen localhost:3000 --save-storage=./private/e2e-auth.json",

View File

@ -578,7 +578,8 @@ class Monitor extends BeanModel {
}
}
if (process.env.UPTIME_KUMA_LOG_RESPONSE_BODY_MONITOR_ID === this.id) {
// eslint-disable-next-line eqeqeq
if (process.env.UPTIME_KUMA_LOG_RESPONSE_BODY_MONITOR_ID == this.id) {
log.info("monitor", res.data);
}

View File

@ -2,13 +2,13 @@ const { MonitorType } = require("./monitor-type");
const { chromium } = require("playwright-core");
const { UP, log } = require("../../src/util");
const { Settings } = require("../settings");
const commandExistsSync = require("command-exists").sync;
const childProcess = require("child_process");
const path = require("path");
const Database = require("../database");
const jwt = require("jsonwebtoken");
const config = require("../config");
const { RemoteBrowser } = require("../remote-browser");
const { commandExists } = require("../util-server");
/**
* Cached instance of a browser
@ -122,7 +122,7 @@ async function prepareChromeExecutable(executablePath) {
executablePath = "/usr/bin/chromium";
// Install chromium in container via apt install
if ( !commandExistsSync(executablePath)) {
if (! await commandExists(executablePath)) {
await new Promise((resolve, reject) => {
log.info("Chromium", "Installing Chromium...");
let child = childProcess.exec("apt update && apt --yes --no-install-recommends install chromium fonts-indic fonts-noto fonts-noto-cjk");
@ -146,7 +146,7 @@ async function prepareChromeExecutable(executablePath) {
}
} else {
executablePath = findChrome(allowedList);
executablePath = await findChrome(allowedList);
}
} else {
// User specified a path
@ -160,20 +160,20 @@ async function prepareChromeExecutable(executablePath) {
/**
* Find the chrome executable
* @param {any[]} executables Executables to search through
* @returns {any} Executable
* @throws Could not find executable
* @param {string[]} executables Executables to search through
* @returns {Promise<string>} Executable
* @throws {Error} Could not find executable
*/
function findChrome(executables) {
async function findChrome(executables) {
// Use the last working executable, so we don't have to search for it again
if (lastAutoDetectChromeExecutable) {
if (commandExistsSync(lastAutoDetectChromeExecutable)) {
if (await commandExists(lastAutoDetectChromeExecutable)) {
return lastAutoDetectChromeExecutable;
}
}
for (let executable of executables) {
if (commandExistsSync(executable)) {
if (await commandExists(executable)) {
lastAutoDetectChromeExecutable = executable;
return executable;
}

View File

@ -23,9 +23,7 @@ class PagerDuty extends NotificationProvider {
if (heartbeatJSON.status === UP) {
const title = "Uptime Kuma Monitor ✅ Up";
const eventAction = notification.pagerdutyAutoResolve || null;
return this.postNotification(notification, title, heartbeatJSON.msg, monitorJSON, eventAction);
return this.postNotification(notification, title, heartbeatJSON.msg, monitorJSON, "resolve");
}
if (heartbeatJSON.status === DOWN) {
@ -63,10 +61,6 @@ class PagerDuty extends NotificationProvider {
*/
async postNotification(notification, title, body, monitorInfo, eventAction = "trigger") {
if (eventAction == null) {
return "No action required";
}
let monitorUrl;
if (monitorInfo.type === "port") {
monitorUrl = monitorInfo.hostname;
@ -79,6 +73,13 @@ class PagerDuty extends NotificationProvider {
monitorUrl = monitorInfo.url;
}
if (eventAction === "resolve") {
if (notification.pagerdutyAutoResolve === "0") {
return "no action required";
}
eventAction = notification.pagerdutyAutoResolve;
}
const options = {
method: "POST",
url: notification.pagerdutyIntegrationUrl,

View File

@ -12,6 +12,8 @@ class Webhook extends NotificationProvider {
const okMsg = "Sent Successfully.";
try {
const httpMethod = notification.httpMethod.toLowerCase() || "post";
let data = {
heartbeat: heartbeatJSON,
monitor: monitorJSON,
@ -21,7 +23,19 @@ class Webhook extends NotificationProvider {
headers: {}
};
if (notification.webhookContentType === "form-data") {
if (httpMethod === "get") {
config.params = {
msg: msg
};
if (heartbeatJSON) {
config.params.heartbeat = JSON.stringify(heartbeatJSON);
}
if (monitorJSON) {
config.params.monitor = JSON.stringify(monitorJSON);
}
} else if (notification.webhookContentType === "form-data") {
const formData = new FormData();
formData.append("data", JSON.stringify(data));
config.headers = formData.getHeaders();
@ -42,7 +56,13 @@ class Webhook extends NotificationProvider {
}
config = this.getAxiosConfigWithProxy(config);
await axios.post(notification.webhookURL, data, config);
if (httpMethod === "get") {
await axios.get(notification.webhookURL, config);
} else {
await axios.post(notification.webhookURL, data, config);
}
return okMsg;
} catch (error) {

View File

@ -81,6 +81,7 @@ const Brevo = require("./notification-providers/brevo");
const YZJ = require("./notification-providers/yzj");
const SMSPlanet = require("./notification-providers/sms-planet");
const SpugPush = require("./notification-providers/spugpush");
const { commandExists } = require("./util-server");
class Notification {
providerList = {};
@ -275,12 +276,10 @@ class Notification {
/**
* Check if apprise exists
* @returns {boolean} Does the command apprise exist?
* @returns {Promise<boolean>} Does the command apprise exist?
*/
static checkApprise() {
let commandExistsSync = require("command-exists").sync;
let exists = commandExistsSync("apprise");
return exists;
static async checkApprise() {
return await commandExists("apprise");
}
}

View File

@ -1503,7 +1503,7 @@ let needSetup = false;
socket.on("checkApprise", async (callback) => {
try {
checkLogin(socket);
callback(Notification.checkApprise());
callback(await Notification.checkApprise());
} catch (e) {
callback(false);
}

View File

@ -1116,3 +1116,19 @@ function fsExists(path) {
});
}
module.exports.fsExists = fsExists;
/**
* By default, command-exists will throw a null error if the command does not exist, which is ugly. The function makes it better.
* Read more: https://github.com/mathisonian/command-exists/issues/22
* @param {string} command Command to check
* @returns {Promise<boolean>} True if command exists, false otherwise
*/
async function commandExists(command) {
try {
await require("command-exists")(command);
return true;
} catch (e) {
return false;
}
}
module.exports.commandExists = commandExists;

View File

@ -12,6 +12,21 @@
</div>
<div class="mb-3">
<label for="webhook-http-method" class="form-label">{{ $t("HTTP Method") }}</label>
<select
id="webhook-http-method"
v-model="$parent.notification.httpMethod"
class="form-select"
>
<option value="post">POST</option>
<option value="get">GET</option>
</select>
<div class="form-text">
{{ $parent.notification.httpMethod === 'get' ? $t("webhookGetMethodDesc") : $t("webhookPostMethodDesc") }}
</div>
</div>
<div v-if="$parent.notification.httpMethod === 'post'" class="mb-3">
<label for="webhook-request-body" class="form-label">{{ $t("Request Body") }}</label>
<select
id="webhook-request-body"
@ -82,6 +97,11 @@ export default {
]);
}
},
mounted() {
if (typeof this.$parent.notification.httpMethod === "undefined") {
this.$parent.notification.httpMethod = "post";
}
},
};
</script>

View File

@ -306,6 +306,7 @@
"Show Tags": "Show Tags",
"Hide Tags": "Hide Tags",
"Description": "Description",
"descriptionHelpText": "Shown on the internal dashboard. Markdown is allowed and sanitized (preserves spaces and indentation) before display.",
"No monitors available.": "No monitors available.",
"Add one": "Add one",
"No Monitors": "No Monitors",
@ -928,6 +929,9 @@
"noGroupMonitorMsg": "Not Available. Create a Group Monitor First.",
"Close": "Close",
"Request Body": "Request Body",
"HTTP Method": "HTTP Method",
"webhookPostMethodDesc": "POST is good for most modern HTTP servers.",
"webhookGetMethodDesc": "GET sends data as query parameters and does not allow configuring a body. Useful for triggering Uptime Kuma Push monitors.",
"wayToGetFlashDutyKey": "To integrate Uptime Kuma with Flashduty: Go to Channels > Select a channel > Integrations > Add a new integration, choose Uptime Kuma, and copy the Push URL.",
"FlashDuty Severity": "Severity",
"FlashDuty Push URL": "Push URL",

View File

@ -819,6 +819,7 @@
<div class="my-3">
<label for="description" class="form-label">{{ $t("Description") }}</label>
<input id="description" v-model="monitor.description" type="text" class="form-control">
<div class="form-text">{{ $t("descriptionHelpText") }}</div>
</div>
<div class="my-3">

12
test/test-backend.mjs Normal file
View File

@ -0,0 +1,12 @@
import * as childProcess from "child_process";
const version = parseInt(process.version.slice(1).split(".")[0]);
/**
* Since Node.js 22 introduced a different "node --test" command with glob, we need to run different test commands based on the Node.js version.
*/
if (version < 22) {
childProcess.execSync("npm run test-backend-20", { stdio: "inherit" });
} else {
childProcess.execSync("npm run test-backend-22", { stdio: "inherit" });
}