diff --git a/.github/workflows/auto-test.yml b/.github/workflows/auto-test.yml index f59035442..14b80f234 100644 --- a/.github/workflows/auto-test.yml +++ b/.github/workflows/auto-test.yml @@ -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 diff --git a/.github/workflows/close-incorrect-issue.yml b/.github/workflows/close-incorrect-issue.yml index 3ef5ba378..9d4616931 100644 --- a/.github/workflows/close-incorrect-issue.yml +++ b/.github/workflows/close-incorrect-issue.yml @@ -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' diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 7e631ccd4..4dff3689d 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -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 diff --git a/docker/builder-go.dockerfile b/docker/builder-go.dockerfile index 857476988..3a9d78248 100644 --- a/docker/builder-go.dockerfile +++ b/docker/builder-go.dockerfile @@ -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 diff --git a/docker/debian-base.dockerfile b/docker/debian-base.dockerfile index 14072ef5b..10471af2a 100644 --- a/docker/debian-base.dockerfile +++ b/docker/debian-base.dockerfile @@ -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 diff --git a/docker/dockerfile b/docker/dockerfile index e19b8640e..e2a301e7b 100644 --- a/docker/dockerfile +++ b/docker/dockerfile @@ -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 + diff --git a/package.json b/package.json index 576068492..7f09ef86f 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/server/model/monitor.js b/server/model/monitor.js index 178d639cd..6e2b3c033 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -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); } diff --git a/server/monitor-types/real-browser-monitor-type.js b/server/monitor-types/real-browser-monitor-type.js index 2a2871d2c..07b6c8aac 100644 --- a/server/monitor-types/real-browser-monitor-type.js +++ b/server/monitor-types/real-browser-monitor-type.js @@ -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} 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; } diff --git a/server/notification-providers/pagerduty.js b/server/notification-providers/pagerduty.js index c60d782e7..385ad2af0 100644 --- a/server/notification-providers/pagerduty.js +++ b/server/notification-providers/pagerduty.js @@ -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, diff --git a/server/notification-providers/webhook.js b/server/notification-providers/webhook.js index 77ce229d8..ab7d5f069 100644 --- a/server/notification-providers/webhook.js +++ b/server/notification-providers/webhook.js @@ -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) { diff --git a/server/notification.js b/server/notification.js index 8ad62dc13..31028e3dd 100644 --- a/server/notification.js +++ b/server/notification.js @@ -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} 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"); } } diff --git a/server/server.js b/server/server.js index 55289b55a..6dba70a3e 100644 --- a/server/server.js +++ b/server/server.js @@ -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); } diff --git a/server/util-server.js b/server/util-server.js index ecad276b3..462b80578 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -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} 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; diff --git a/src/components/notifications/Webhook.vue b/src/components/notifications/Webhook.vue index 7775a3fdd..be51cc2c6 100644 --- a/src/components/notifications/Webhook.vue +++ b/src/components/notifications/Webhook.vue @@ -12,6 +12,21 @@
+ + +
+ {{ $parent.notification.httpMethod === 'get' ? $t("webhookGetMethodDesc") : $t("webhookPostMethodDesc") }} +
+
+ +
+
{{ $t("descriptionHelpText") }}
diff --git a/test/test-backend.mjs b/test/test-backend.mjs new file mode 100644 index 000000000..e285f7804 --- /dev/null +++ b/test/test-backend.mjs @@ -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" }); +}